summaryrefslogtreecommitdiff
path: root/cmds
diff options
context:
space:
mode:
Diffstat (limited to 'cmds')
-rw-r--r--cmds/am/Android.bp11
-rw-r--r--cmds/am/Android.mk11
-rw-r--r--cmds/bu/src/com/android/commands/bu/Backup.java4
-rw-r--r--cmds/hid/Android.bp1
-rw-r--r--cmds/hid/jni/Android.bp17
-rw-r--r--cmds/hid/jni/Android.mk18
-rw-r--r--cmds/idmap/create.cpp8
-rw-r--r--cmds/idmap/inspect.cpp4
-rw-r--r--cmds/incident/main.cpp21
-rw-r--r--cmds/incident_helper/IncidentHelper.cpp175
-rw-r--r--cmds/incident_helper/IncidentHelper.h22
-rw-r--r--cmds/incident_helper/ih_util.cpp24
-rw-r--r--cmds/incident_helper/ih_util.h24
-rw-r--r--cmds/incident_helper/main.cpp6
-rw-r--r--cmds/incident_helper/testdata/pagetypeinfo.txt10
-rw-r--r--cmds/incident_helper/tests/IncidentHelper_test.cpp72
-rw-r--r--cmds/incident_helper/tests/ih_util_test.cpp46
-rw-r--r--cmds/incidentd/Android.mk9
-rw-r--r--cmds/incidentd/incidentd.rc4
-rw-r--r--cmds/incidentd/src/EncodedBuffer.cpp195
-rw-r--r--cmds/incidentd/src/EncodedBuffer.h64
-rw-r--r--cmds/incidentd/src/FdBuffer.cpp79
-rw-r--r--cmds/incidentd/src/FdBuffer.h71
-rw-r--r--cmds/incidentd/src/Privacy.cpp86
-rw-r--r--cmds/incidentd/src/Privacy.h66
-rw-r--r--cmds/incidentd/src/Reporter.cpp83
-rw-r--r--cmds/incidentd/src/Reporter.h9
-rw-r--r--cmds/incidentd/src/Section.cpp173
-rw-r--r--cmds/incidentd/src/Section.h14
-rw-r--r--cmds/incidentd/src/io_util.cpp44
-rw-r--r--cmds/incidentd/src/io_util.h41
-rw-r--r--cmds/incidentd/src/protobuf.cpp34
-rw-r--r--cmds/incidentd/src/protobuf.h28
-rw-r--r--cmds/incidentd/src/section_list.h38
-rw-r--r--cmds/incidentd/tests/EncodedBuffer_test.cpp255
-rw-r--r--cmds/incidentd/tests/FdBuffer_test.cpp31
-rw-r--r--cmds/incidentd/tests/Reporter_test.cpp27
-rw-r--r--cmds/incidentd/tests/Section_test.cpp208
-rw-r--r--cmds/incidentd/tests/section_list.cpp22
-rw-r--r--cmds/interrupter/Android.bp11
-rw-r--r--cmds/interrupter/Android.mk23
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java2
-rw-r--r--cmds/screencap/screencap.cpp3
-rw-r--r--cmds/sm/src/com/android/commands/sm/Sm.java14
-rw-r--r--cmds/statsd/Android.mk143
-rw-r--r--cmds/statsd/src/AnomalyMonitor.cpp108
-rw-r--r--cmds/statsd/src/AnomalyMonitor.h138
-rw-r--r--cmds/statsd/src/DropboxReader.cpp126
-rw-r--r--cmds/statsd/src/DropboxReader.h52
-rw-r--r--cmds/statsd/src/DropboxWriter.cpp67
-rw-r--r--cmds/statsd/src/DropboxWriter.h72
-rw-r--r--cmds/statsd/src/LogEntryPrinter.cpp69
-rw-r--r--cmds/statsd/src/LogEntryPrinter.h62
-rw-r--r--cmds/statsd/src/LogReader.cpp150
-rw-r--r--cmds/statsd/src/LogReader.h88
-rw-r--r--cmds/statsd/src/StatsLogProcessor.cpp79
-rw-r--r--cmds/statsd/src/StatsLogProcessor.h61
-rw-r--r--cmds/statsd/src/StatsService.cpp292
-rw-r--r--cmds/statsd/src/StatsService.h101
-rw-r--r--cmds/statsd/src/indexed_priority_queue.h199
-rw-r--r--cmds/statsd/src/main.cpp151
-rw-r--r--cmds/statsd/src/parse_util.cpp121
-rw-r--r--cmds/statsd/src/parse_util.h28
-rw-r--r--cmds/statsd/src/stats_constants.proto39
-rw-r--r--cmds/statsd/src/stats_log.proto77
-rw-r--r--cmds/statsd/src/statsd_config.proto119
-rw-r--r--cmds/statsd/statsd.rc16
-rw-r--r--cmds/statsd/tests/LogReader_test.cpp24
-rw-r--r--cmds/statsd/tests/indexed_priority_queue_test.cpp188
69 files changed, 4311 insertions, 367 deletions
diff --git a/cmds/am/Android.bp b/cmds/am/Android.bp
new file mode 100644
index 000000000000..7eb4edfecbf9
--- /dev/null
+++ b/cmds/am/Android.bp
@@ -0,0 +1,11 @@
+// Copyright 2008 The Android Open Source Project
+//
+
+cc_library_host_static {
+ name: "libinstrumentation",
+ srcs: ["**/*.proto"],
+ proto: {
+ type: "full",
+ export_proto_headers: true,
+ },
+}
diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk
index 5586dd4e5b18..9411c3203ab8 100644
--- a/cmds/am/Android.mk
+++ b/cmds/am/Android.mk
@@ -16,14 +16,3 @@ LOCAL_SRC_FILES := am
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
-
-
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- $(call all-proto-files-under, proto)
-LOCAL_MODULE := libinstrumentation
-LOCAL_PROTOC_OPTIMIZE_TYPE := full
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
- $(call intermediates-dir-for,STATIC_LIBRARIES,libinstrumentation,HOST,,,)/proto/$(LOCAL_PATH)/proto
-include $(BUILD_HOST_STATIC_LIBRARY)
-
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index 345895b794a3..834658da8ccc 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -136,7 +136,9 @@ public final class Backup {
if (fd != null) {
try {
fd.close();
- } catch (IOException e) {}
+ } catch (IOException e) {
+ Log.e(TAG, "IO error closing output for backup: " + e.getMessage());
+ }
}
}
}
diff --git a/cmds/hid/Android.bp b/cmds/hid/Android.bp
new file mode 100644
index 000000000000..2b7963aa9425
--- /dev/null
+++ b/cmds/hid/Android.bp
@@ -0,0 +1 @@
+subdirs = ["jni"]
diff --git a/cmds/hid/jni/Android.bp b/cmds/hid/jni/Android.bp
new file mode 100644
index 000000000000..095cfc6ceb53
--- /dev/null
+++ b/cmds/hid/jni/Android.bp
@@ -0,0 +1,17 @@
+cc_library_shared {
+ name: "libhidcommand_jni",
+
+ srcs: ["com_android_commands_hid_Device.cpp"],
+
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libnativehelper",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+}
diff --git a/cmds/hid/jni/Android.mk b/cmds/hid/jni/Android.mk
deleted file mode 100644
index 86f4e012a943..000000000000
--- a/cmds/hid/jni/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- com_android_commands_hid_Device.cpp
-
-LOCAL_C_INCLUDES := \
- $(JNI_H_INCLUDE)
-
-LOCAL_LDLIBS += -landroid -llog -lnativehelper
-
-LOCAL_MODULE := libhidcommand_jni
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_CFLAGS += -Wall -Wextra -Werror
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp
index 524db14f7aab..f415f8f5dd75 100644
--- a/cmds/idmap/create.cpp
+++ b/cmds/idmap/create.cpp
@@ -104,13 +104,17 @@ fail:
}
}
- uint32_t cached_target_crc, cached_overlay_crc;
+ uint32_t version, cached_target_crc, cached_overlay_crc;
String8 cached_target_path, cached_overlay_path;
- if (!ResTable::getIdmapInfo(buf, N, NULL, &cached_target_crc, &cached_overlay_crc,
+ if (!ResTable::getIdmapInfo(buf, N, &version, &cached_target_crc, &cached_overlay_crc,
&cached_target_path, &cached_overlay_path)) {
return true;
}
+ if (version != ResTable::IDMAP_CURRENT_VERSION) {
+ return true;
+ }
+
if (cached_target_path != target_apk_path) {
return true;
}
diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp
index 154cb25a02a1..20005e2766d8 100644
--- a/cmds/idmap/inspect.cpp
+++ b/cmds/idmap/inspect.cpp
@@ -284,7 +284,9 @@ namespace {
if (err != NO_ERROR) {
return err;
}
- print("", "entry", data32, "%s/%s", type.string(), name.string());
+ if (data32 != ResTable_type::NO_ENTRY) {
+ print("", "entry", data32, "%s/%s", type.string(), name.string());
+ }
}
}
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index 47f1db89e1cb..519852dbe88b 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -25,6 +25,7 @@
#include <binder/IServiceManager.h>
#include <utils/Looper.h>
+#include <cstring>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
@@ -144,6 +145,16 @@ find_section(const char* name)
}
// ================================================================================
+static int
+get_dest(const char* arg)
+{
+ if (strcmp(arg, "LOCAL") == 0) return 0;
+ if (strcmp(arg, "EXPLICIT") == 0) return 1;
+ if (strcmp(arg, "AUTOMATIC") == 0) return 2;
+ return -1; // return the default value
+}
+
+// ================================================================================
static void
usage(FILE* out)
{
@@ -155,6 +166,7 @@ usage(FILE* out)
fprintf(out, " -b (default) print the report to stdout (in proto format)\n");
fprintf(out, " -d send the report into dropbox\n");
fprintf(out, " -l list available sections\n");
+ fprintf(out, " -p privacy spec, LOCAL, EXPLICIT or AUTOMATIC\n");
fprintf(out, "\n");
fprintf(out, " SECTION the field numbers of the incident report fields to include\n");
fprintf(out, "\n");
@@ -166,10 +178,11 @@ main(int argc, char** argv)
Status status;
IncidentReportArgs args;
enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT;
+ int dest = -1; // default
// Parse the args
int opt;
- while ((opt = getopt(argc, argv, "bhdl")) != -1) {
+ while ((opt = getopt(argc, argv, "bhdlp:")) != -1) {
switch (opt) {
case 'h':
usage(stdout);
@@ -183,6 +196,9 @@ main(int argc, char** argv)
case 'd':
destination = DEST_DROPBOX;
break;
+ case 'p':
+ dest = get_dest(optarg);
+ break;
default:
usage(stderr);
return 1;
@@ -210,8 +226,7 @@ main(int argc, char** argv)
}
}
}
-
-
+ args.setDest(dest);
// Start the thread pool.
sp<ProcessState> ps(ProcessState::self());
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
index fba5e662b7c1..7b06d42cbb55 100644
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ b/cmds/incident_helper/IncidentHelper.cpp
@@ -20,6 +20,7 @@
#include "ih_util.h"
#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
#include "frameworks/base/core/proto/android/os/procrank.pb.h"
#include <android-base/file.h>
@@ -32,6 +33,22 @@ using namespace android::os;
using namespace google::protobuf;
using namespace std;
+
+static const string TAB_DELIMITER = "\t";
+static const string COMMA_DELIMITER = ",";
+
+static inline int toInt(const string& s) {
+ return atoi(s.c_str());
+}
+
+static inline long toLong(const string& s) {
+ return atol(s.c_str());
+}
+
+/**
+ * Sets the given protobuf message when the field name matches one of the
+ * fields. It is useful to set values to proto from table-like plain texts.
+ */
static bool
SetTableField(::google::protobuf::Message* message, string field_name, string field_value) {
const Descriptor* descriptor = message->GetDescriptor();
@@ -43,16 +60,16 @@ SetTableField(::google::protobuf::Message* message, string field_name, string fi
reflection->SetString(message, field, field_value);
return true;
case FieldDescriptor::TYPE_INT64:
- reflection->SetInt64(message, field, atol(field_value.c_str()));
+ reflection->SetInt64(message, field, toLong(field_value));
return true;
case FieldDescriptor::TYPE_UINT64:
- reflection->SetUInt64(message, field, atol(field_value.c_str()));
+ reflection->SetUInt64(message, field, toLong(field_value));
return true;
case FieldDescriptor::TYPE_INT32:
- reflection->SetInt32(message, field, atoi(field_value.c_str()));
+ reflection->SetInt32(message, field, toInt(field_value));
return true;
case FieldDescriptor::TYPE_UINT32:
- reflection->SetUInt32(message, field, atoi(field_value.c_str()));
+ reflection->SetUInt32(message, field, toInt(field_value));
return true;
default:
// Add new scalar types
@@ -61,6 +78,21 @@ SetTableField(::google::protobuf::Message* message, string field_name, string fi
}
// ================================================================================
+status_t NoopParser::Parse(const int in, const int out) const
+{
+ string content;
+ if (!ReadFdToString(in, &content)) {
+ fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+ return -1;
+ }
+ if (!WriteStringToFd(content, out)) {
+ fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+ return -1;
+ }
+ return NO_ERROR;
+}
+
+// ================================================================================
status_t ReverseParser::Parse(const int in, const int out) const
{
string content;
@@ -78,8 +110,6 @@ status_t ReverseParser::Parse(const int in, const int out) const
}
// ================================================================================
-static const string KERNEL_WAKEUP_LINE_DELIMITER = "\t";
-
status_t KernelWakesParser::Parse(const int in, const int out) const {
Reader reader(in);
string line;
@@ -90,16 +120,16 @@ status_t KernelWakesParser::Parse(const int in, const int out) const {
KernelWakeSources proto;
// parse line by line
- while (reader.readLine(line)) {
+ while (reader.readLine(&line)) {
if (line.empty()) continue;
// parse head line
if (nline++ == 0) {
- header = parseHeader(line, KERNEL_WAKEUP_LINE_DELIMITER);
+ header = parseHeader(line, TAB_DELIMITER);
continue;
}
// parse for each record, the line delimiter is \t only!
- record = parseRecord(line, KERNEL_WAKEUP_LINE_DELIMITER);
+ record = parseRecord(line, TAB_DELIMITER);
if (record.size() != header.size()) {
// TODO: log this to incident report!
@@ -116,7 +146,7 @@ status_t KernelWakesParser::Parse(const int in, const int out) const {
}
}
- if (!reader.ok(line)) {
+ if (!reader.ok(&line)) {
fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
return -1;
}
@@ -132,7 +162,7 @@ status_t KernelWakesParser::Parse(const int in, const int out) const {
// ================================================================================
status_t ProcrankParser::Parse(const int in, const int out) const {
Reader reader(in);
- string line, content;
+ string line;
header_t header; // the header of /d/wakeup_sources
record_t record; // retain each record
int nline = 0;
@@ -140,7 +170,7 @@ status_t ProcrankParser::Parse(const int in, const int out) const {
Procrank proto;
// parse line by line
- while (reader.readLine(line)) {
+ while (reader.readLine(&line)) {
if (line.empty()) continue;
// parse head line
@@ -149,6 +179,15 @@ status_t ProcrankParser::Parse(const int in, const int out) const {
continue;
}
+ if (hasPrefix(&line, "ZRAM:")) {
+ proto.mutable_summary()->mutable_zram()->set_raw_text(line);
+ continue;
+ }
+ if (hasPrefix(&line, "RAM:")) {
+ proto.mutable_summary()->mutable_ram()->set_raw_text(line);
+ continue;
+ }
+
record = parseRecord(line);
if (record.size() != header.size()) {
if (record[record.size() - 1] == "TOTAL") { // TOTAL record
@@ -156,12 +195,6 @@ status_t ProcrankParser::Parse(const int in, const int out) const {
for (int i=1; i<=(int)record.size(); i++) {
SetTableField(total, header[header.size() - i], record[record.size() - i]);
}
- } else if (record[0] == "ZRAM:") {
- record = parseRecord(line, ":");
- proto.mutable_summary()->mutable_zram()->set_raw_text(record[1]);
- } else if (record[0] == "RAM:") {
- record = parseRecord(line, ":");
- proto.mutable_summary()->mutable_ram()->set_raw_text(record[1]);
} else {
fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
line.c_str());
@@ -178,7 +211,7 @@ status_t ProcrankParser::Parse(const int in, const int out) const {
}
}
- if (!reader.ok(line)) {
+ if (!reader.ok(&line)) {
fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
return -1;
}
@@ -189,4 +222,106 @@ status_t ProcrankParser::Parse(const int in, const int out) const {
}
fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
return NO_ERROR;
-} \ No newline at end of file
+}
+
+// ================================================================================
+status_t PageTypeInfoParser::Parse(const int in, const int out) const {
+ Reader reader(in);
+ string line;
+ bool migrateTypeSession = false;
+ int pageBlockOrder;
+ header_t blockHeader;
+
+ PageTypeInfo pageTypeInfo;
+
+ while (reader.readLine(&line)) {
+ if (line.empty()) {
+ migrateTypeSession = false;
+ blockHeader.clear();
+ continue;
+ }
+
+ if (hasPrefix(&line, "Page block order:")) {
+ pageBlockOrder = toInt(line);
+ pageTypeInfo.set_page_block_order(pageBlockOrder);
+ continue;
+ }
+ if (hasPrefix(&line, "Pages per block:")) {
+ pageTypeInfo.set_pages_per_block(toInt(line));
+ continue;
+ }
+ if (hasPrefix(&line, "Free pages count per migrate type at order")) {
+ migrateTypeSession = true;
+ continue;
+ }
+ if (hasPrefix(&line, "Number of blocks type")) {
+ blockHeader = parseHeader(line);
+ continue;
+ }
+
+ record_t record = parseRecord(line, COMMA_DELIMITER);
+ if (migrateTypeSession && record.size() == 3) {
+ MigrateTypeProto* migrateType = pageTypeInfo.add_migrate_types();
+ // expect part 0 starts with "Node"
+ if (hasPrefix(&record[0], "Node")) {
+ migrateType->set_node(toInt(record[0]));
+ } else goto ERROR;
+ // expect part 1 starts with "zone"
+ if (hasPrefix(&record[1], "zone")) {
+ migrateType->set_zone(record[1]);
+ } else goto ERROR;
+ // expect part 2 starts with "type"
+ if (hasPrefix(&record[2], "type")) {
+ // expect the rest of part 2 has number of (pageBlockOrder + 2) parts
+ // An example looks like:
+ // header line: type 0 1 2 3 4 5 6 7 8 9 10
+ // record line: Unmovable 426 279 226 1 1 1 0 0 2 2 0
+ // The pageBlockOrder = 10 and it's zero-indexed. so total parts
+ // are 10 + 1(zero-indexed) + 1(the type part) = 12.
+ record_t pageCounts = parseRecord(record[2]);
+ int pageCountsSize = pageBlockOrder + 2;
+ if ((int)pageCounts.size() != pageCountsSize) goto ERROR;
+
+ migrateType->set_type(pageCounts[0]);
+ for (auto i=1; i<pageCountsSize; i++) {
+ migrateType->add_free_pages_count(toInt(pageCounts[i]));
+ }
+ } else goto ERROR;
+ continue;
+ }
+
+ if (!blockHeader.empty() && record.size() == 2) {
+ BlockProto* block = pageTypeInfo.add_blocks();
+
+ if (hasPrefix(&record[0], "Node")) {
+ block->set_node(toInt(record[0]));
+ } else goto ERROR;
+
+ if (hasPrefix(&record[1], "zone")) {
+ record_t blockCounts = parseRecord(record[1]);
+ block->set_zone(blockCounts[0]);
+ for (size_t i=0; i<blockHeader.size(); i++) {
+ if (!SetTableField(block, blockHeader[i], blockCounts[i+1])) goto ERROR;
+ }
+ } else goto ERROR;
+
+ continue;
+ }
+
+ERROR: // print out error for this single line and continue parsing
+ fprintf(stderr, "[%s]Bad line: %s\n", this->name.string(), line.c_str());
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!pageTypeInfo.SerializeToFileDescriptor(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+
+ fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), pageTypeInfo.ByteSize());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/IncidentHelper.h
index f319c419fcd6..d24d7173aa26 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/IncidentHelper.h
@@ -36,6 +36,17 @@ public:
};
/**
+ * No op parser returns what it reads
+ */
+class NoopParser : public TextParserBase {
+public:
+ NoopParser() : TextParserBase(String8("NoopParser")) {};
+ ~NoopParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+/**
* This parser is used for testing only, results in timeout.
*/
class TimeoutParser : public TextParserBase {
@@ -69,6 +80,17 @@ public:
};
/**
+ * PageTypeInfo parser, parses text to protobuf in /proc/pageinfotype
+ */
+class PageTypeInfoParser : public TextParserBase {
+public:
+ PageTypeInfoParser() : TextParserBase(String8("PageTypeInfo")) {};
+ ~PageTypeInfoParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+/**
* Procrank parser, parses text produced by command procrank
*/
class ProcrankParser : public TextParserBase {
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/ih_util.cpp
index b2fda23ac391..2ab4b54e193f 100644
--- a/cmds/incident_helper/ih_util.cpp
+++ b/cmds/incident_helper/ih_util.cpp
@@ -18,6 +18,7 @@
#include "ih_util.h"
+#include <algorithm>
#include <sstream>
#include <unistd.h>
@@ -72,6 +73,20 @@ record_t parseRecord(const std::string& line, const std::string& delimiters) {
return record;
}
+bool hasPrefix(std::string* line, const char* key) {
+ const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
+ if (head == std::string::npos) return false;
+ auto i = 0;
+ auto j = head;
+ while (key[i] != '\0') {
+ if (j >= line->size() || key[i++] != line->at(j++)) {
+ return false;
+ }
+ }
+ line->assign(trim(line->substr(j)));
+ return true;
+}
+
Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
Reader::Reader(const int fd, const size_t capacity)
@@ -86,8 +101,9 @@ Reader::~Reader()
free(mBuf);
}
-bool Reader::readLine(std::string& line, const char newline) {
+bool Reader::readLine(std::string* line, const char newline) {
if (!ok(line)) return false; // bad status
+ line->clear();
std::stringstream ss;
while (!EOR()) {
// read if available
@@ -124,14 +140,14 @@ bool Reader::readLine(std::string& line, const char newline) {
if (mFlushed >= (int) mMaxSize) mFlushed = 0;
if (EOR() || meetsNewLine) {
- line.assign(ss.str());
+ line->assign(ss.str());
return true;
}
}
return false;
}
-bool Reader::ok(std::string& error) {
- error.assign(mStatus);
+bool Reader::ok(std::string* error) {
+ error->assign(mStatus);
return mStatus.empty();
}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/ih_util.h
index 5598eed8d824..ce5baeef0dc3 100644
--- a/cmds/incident_helper/ih_util.h
+++ b/cmds/incident_helper/ih_util.h
@@ -28,10 +28,30 @@ typedef std::string (*trans_func) (const std::string&);
const char DEFAULT_NEWLINE = '\n';
const std::string DEFAULT_WHITESPACE = " \t";
+/**
+ * When a text has a table format like this
+ * line 1: HeadA HeadB HeadC
+ * line 2: v1 v2 v3
+ * line 3: v11 v12 v13
+ *
+ * We want to parse the line in structure given the delimiter.
+ * parseHeader is used to parse the firse line of the table and returns a list of strings in lower case
+ * parseRecord is used to parse other lines and returns a list of strings
+ * empty strings are skipped
+ */
header_t parseHeader(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
/**
+ * When the line starts with the given key, the function returns true
+ * as well as the line argument is changed to the rest part of the original.
+ * e.g. "ZRAM: 6828K physical used for 31076K in swap (524284K total swap)" becomes
+ * "6828K physical used for 31076K in swap (524284K total swap)" when given key "ZRAM:",
+ * otherwise the line is not changed.
+ */
+bool hasPrefix(std::string* line, const char* key);
+
+/**
* Reader class reads data from given fd in streaming fashion.
* The buffer size is controlled by capacity parameter.
*/
@@ -42,8 +62,8 @@ public:
Reader(const int fd, const size_t capacity);
~Reader();
- bool readLine(std::string& line, const char newline = DEFAULT_NEWLINE);
- bool ok(std::string& error);
+ bool readLine(std::string* line, const char newline = DEFAULT_NEWLINE);
+ bool ok(std::string* error);
private:
int mFd; // set mFd to -1 when read EOF()
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/main.cpp
index 333344b8ce86..52ff77720d70 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/main.cpp
@@ -41,11 +41,15 @@ static TextParserBase* selectParser(int section) {
case -1:
return new TimeoutParser();
case 0:
+ return new NoopParser();
+ case 1: // 1 is reserved for incident header so it won't be section id
return new ReverseParser();
/* ========================================================================= */
- // IDs larger than 0 are reserved in incident.proto
+ // IDs larger than 1 are section ids reserved in incident.proto
case 2000:
return new ProcrankParser();
+ case 2001:
+ return new PageTypeInfoParser();
case 2002:
return new KernelWakesParser();
default:
diff --git a/cmds/incident_helper/testdata/pagetypeinfo.txt b/cmds/incident_helper/testdata/pagetypeinfo.txt
new file mode 100644
index 000000000000..d45ddc408c0f
--- /dev/null
+++ b/cmds/incident_helper/testdata/pagetypeinfo.txt
@@ -0,0 +1,10 @@
+Page block order: 10
+Pages per block: 1024
+
+Free pages count per migrate type at order 0 1 2 3 4 5 6 7 8 9 10
+Node 0, zone DMA, type Unmovable 426 279 226 1 1 1 0 0 2 2 0
+Node 0, zone Normal, type Reclaimable 953 773 437 154 92 26 15 14 12 7 0
+
+Number of blocks type Unmovable Reclaimable Movable CMA Reserve Isolate
+Node 0, zone DMA 74 9 337 41 1 0
+Node 0, zone Normal 70 12 423 0 1 0
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
index 04dd8de11a15..c44a163efa11 100644
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ b/cmds/incident_helper/tests/IncidentHelper_test.cpp
@@ -17,6 +17,7 @@
#include "IncidentHelper.h"
#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/pagetypeinfo.pb.h"
#include "frameworks/base/core/proto/android/os/procrank.pb.h"
#include <android-base/file.h>
@@ -29,6 +30,7 @@
using namespace android::base;
using namespace android::os;
+using namespace std;
using ::testing::StrEq;
using ::testing::Test;
using ::testing::internal::CaptureStderr;
@@ -42,8 +44,8 @@ public:
ASSERT_TRUE(tf.fd != -1);
}
- std::string getSerializedString(::google::protobuf::Message& message) {
- std::string expectedStr;
+ string getSerializedString(::google::protobuf::Message& message) {
+ string expectedStr;
message.SerializeToFileDescriptor(tf.fd);
ReadFileToString(tf.path, &expectedStr);
return expectedStr;
@@ -52,8 +54,8 @@ public:
protected:
TemporaryFile tf;
- const std::string kTestPath = GetExecutableDirectory();
- const std::string kTestDataPath = kTestPath + "/testdata/";
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
};
TEST_F(IncidentHelperTest, ReverseParser) {
@@ -69,7 +71,7 @@ TEST_F(IncidentHelperTest, ReverseParser) {
}
TEST_F(IncidentHelperTest, KernelWakesParser) {
- const std::string testFile = kTestDataPath + "kernel_wakeups.txt";
+ const string testFile = kTestDataPath + "kernel_wakeups.txt";
KernelWakesParser parser;
KernelWakeSources expected;
@@ -107,7 +109,7 @@ TEST_F(IncidentHelperTest, KernelWakesParser) {
}
TEST_F(IncidentHelperTest, ProcrankParser) {
- const std::string testFile = kTestDataPath + "procrank.txt";
+ const string testFile = kTestDataPath + "procrank.txt";
ProcrankParser parser;
Procrank expected;
@@ -159,7 +161,7 @@ TEST_F(IncidentHelperTest, ProcrankParser) {
}
TEST_F(IncidentHelperTest, ProcrankParserShortHeader) {
- const std::string testFile = kTestDataPath + "procrank_short.txt";
+ const string testFile = kTestDataPath + "procrank_short.txt";
ProcrankParser parser;
Procrank expected;
@@ -195,3 +197,59 @@ TEST_F(IncidentHelperTest, ProcrankParserShortHeader) {
EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
close(fd);
}
+
+TEST_F(IncidentHelperTest, PageTypeInfoParser) {
+ const string testFile = kTestDataPath + "pagetypeinfo.txt";
+ PageTypeInfoParser parser;
+ PageTypeInfo expected;
+
+ expected.set_page_block_order(10);
+ expected.set_pages_per_block(1024);
+
+ MigrateTypeProto* mt1 = expected.add_migrate_types();
+ mt1->set_node(0);
+ mt1->set_zone("DMA");
+ mt1->set_type("Unmovable");
+ int arr1[] = { 426, 279, 226, 1, 1, 1, 0, 0, 2, 2, 0};
+ for (auto i=0; i<11; i++) {
+ mt1->add_free_pages_count(arr1[i]);
+ }
+
+ MigrateTypeProto* mt2 = expected.add_migrate_types();
+ mt2->set_node(0);
+ mt2->set_zone("Normal");
+ mt2->set_type("Reclaimable");
+ int arr2[] = { 953, 773, 437, 154, 92, 26, 15, 14, 12, 7, 0};
+ for (auto i=0; i<11; i++) {
+ mt2->add_free_pages_count(arr2[i]);
+ }
+
+ BlockProto* block1 = expected.add_blocks();
+ block1->set_node(0);
+ block1->set_zone("DMA");
+ block1->set_unmovable(74);
+ block1->set_reclaimable(9);
+ block1->set_movable(337);
+ block1->set_cma(41);
+ block1->set_reserve(1);
+ block1->set_isolate(0);
+
+
+ BlockProto* block2 = expected.add_blocks();
+ block2->set_node(0);
+ block2->set_zone("Normal");
+ block2->set_unmovable(70);
+ block2->set_reclaimable(12);
+ block2->set_movable(423);
+ block2->set_cma(0);
+ block2->set_reserve(1);
+ block2->set_isolate(0);
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+} \ No newline at end of file
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index 3b9ed403b77a..da88ee3a0120 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -69,14 +69,14 @@ TEST(IhUtilTest, Reader) {
Reader r(tf.fd);
string line;
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq("test string"));
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq("second"));
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq("ooo"));
- ASSERT_FALSE(r.readLine(line));
- ASSERT_TRUE(r.ok(line));
+ ASSERT_FALSE(r.readLine(&line));
+ ASSERT_TRUE(r.ok(&line));
}
TEST(IhUtilTest, ReaderSmallBufSize) {
@@ -86,14 +86,14 @@ TEST(IhUtilTest, ReaderSmallBufSize) {
Reader r(tf.fd, 5);
string line;
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq("test string"));
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq("second"));
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq("ooiecccojreo"));
- ASSERT_FALSE(r.readLine(line));
- ASSERT_TRUE(r.ok(line));
+ ASSERT_FALSE(r.readLine(&line));
+ ASSERT_TRUE(r.ok(&line));
}
TEST(IhUtilTest, ReaderEmpty) {
@@ -103,10 +103,10 @@ TEST(IhUtilTest, ReaderEmpty) {
Reader r(tf.fd);
string line;
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq(""));
- ASSERT_FALSE(r.readLine(line));
- ASSERT_TRUE(r.ok(line));
+ ASSERT_FALSE(r.readLine(&line));
+ ASSERT_TRUE(r.ok(&line));
}
TEST(IhUtilTest, ReaderMultipleEmptyLines) {
@@ -116,35 +116,35 @@ TEST(IhUtilTest, ReaderMultipleEmptyLines) {
Reader r(tf.fd);
string line;
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq(""));
- ASSERT_TRUE(r.readLine(line));
+ ASSERT_TRUE(r.readLine(&line));
EXPECT_THAT(line, StrEq(""));
- ASSERT_FALSE(r.readLine(line));
+ ASSERT_FALSE(r.readLine(&line));
EXPECT_THAT(line, StrEq(""));
- ASSERT_TRUE(r.ok(line));
+ ASSERT_TRUE(r.ok(&line));
}
TEST(IhUtilTest, ReaderFailedNegativeFd) {
Reader r(-123);
string line;
- EXPECT_FALSE(r.readLine(line));
- EXPECT_FALSE(r.ok(line));
+ EXPECT_FALSE(r.readLine(&line));
+ EXPECT_FALSE(r.ok(&line));
EXPECT_THAT(line, StrEq("Negative fd"));
}
TEST(IhUtilTest, ReaderFailedZeroBufferSize) {
Reader r(23, 0);
string line;
- EXPECT_FALSE(r.readLine(line));
- EXPECT_FALSE(r.ok(line));
+ EXPECT_FALSE(r.readLine(&line));
+ EXPECT_FALSE(r.ok(&line));
EXPECT_THAT(line, StrEq("Zero buffer capacity"));
}
TEST(IhUtilTest, ReaderFailedBadFd) {
Reader r(1231432);
string line;
- EXPECT_FALSE(r.readLine(line));
- EXPECT_FALSE(r.ok(line));
+ EXPECT_FALSE(r.readLine(&line));
+ EXPECT_FALSE(r.ok(&line));
EXPECT_THAT(line, StrEq("Fail to read from fd"));
}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index 835a7b94507b..830bf9e66cde 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -23,10 +23,13 @@ include $(CLEAR_VARS)
LOCAL_MODULE := incidentd
LOCAL_SRC_FILES := \
+ src/EncodedBuffer.cpp \
src/FdBuffer.cpp \
src/IncidentService.cpp \
+ src/Privacy.cpp \
src/Reporter.cpp \
src/Section.cpp \
+ src/io_util.cpp \
src/main.cpp \
src/protobuf.cpp \
src/report_directory.cpp
@@ -69,7 +72,9 @@ LOCAL_GENERATED_SOURCES += $(GEN)
gen_src_dir:=
GEN:=
+ifeq ($(BUILD_WITH_INCIDENTD_RC), true)
LOCAL_INIT_RC := incidentd.rc
+endif
include $(BUILD_EXECUTABLE)
@@ -88,12 +93,16 @@ LOCAL_CFLAGS := -Werror -Wall -Wno-unused-variable -Wunused-parameter
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
LOCAL_SRC_FILES := \
+ src/EncodedBuffer.cpp \
src/FdBuffer.cpp \
+ src/Privacy.cpp \
src/Reporter.cpp \
src/Section.cpp \
+ src/io_util.cpp \
src/protobuf.cpp \
src/report_directory.cpp \
tests/section_list.cpp \
+ tests/EncodedBuffer_test.cpp \
tests/FdBuffer_test.cpp \
tests/Reporter_test.cpp \
tests/Section_test.cpp \
diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc
index c1eed7f702f8..fe38a71231dc 100644
--- a/cmds/incidentd/incidentd.rc
+++ b/cmds/incidentd/incidentd.rc
@@ -14,3 +14,7 @@
service incidentd /system/bin/incidentd
class main
+
+on post-fs-data
+ # Create directory for incidentd
+ mkdir /data/misc/incidents 0770 root root
diff --git a/cmds/incidentd/src/EncodedBuffer.cpp b/cmds/incidentd/src/EncodedBuffer.cpp
new file mode 100644
index 000000000000..e8f2c1171672
--- /dev/null
+++ b/cmds/incidentd/src/EncodedBuffer.cpp
@@ -0,0 +1,195 @@
+/*
+ * 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 "EncodedBuffer.h"
+#include "io_util.h"
+#include "protobuf.h"
+
+#include <deque>
+
+const size_t BUFFER_SIZE = 4 * 1024; // 4 KB
+
+/**
+ * Read varint from iterator, the iterator will point to next available byte.
+ * Return the number of bytes of the varint.
+ */
+static uint32_t
+read_raw_varint(FdBuffer::iterator* it)
+{
+ uint32_t val = 0;
+ int i = 0;
+ bool hasNext = true;
+ while (hasNext) {
+ hasNext = ((**it & 0x80) != 0);
+ val += (**it & 0x7F) << (7*i);
+ (*it)++;
+ i++;
+ }
+ return val;
+}
+
+/**
+ * Write the field to buf based on the wire type, iterator will point to next field.
+ * If skip is set to true, no data will be written to buf. Return number of bytes written.
+ */
+static size_t
+write_field_or_skip(FdBuffer::iterator* iter, vector<uint8_t>* buf, uint8_t wireType, bool skip)
+{
+ FdBuffer::iterator snapshot = iter->snapshot();
+ size_t bytesToWrite = 0;
+ uint32_t varint = 0;
+ switch (wireType) {
+ case WIRE_TYPE_VARINT:
+ varint = read_raw_varint(iter);
+ if(!skip) return write_raw_varint(buf, varint);
+ break;
+ case WIRE_TYPE_FIXED64:
+ bytesToWrite = 8;
+ break;
+ case WIRE_TYPE_LENGTH_DELIMITED:
+ bytesToWrite = read_raw_varint(iter);
+ if(!skip) write_raw_varint(buf, bytesToWrite);
+ break;
+ case WIRE_TYPE_FIXED32:
+ bytesToWrite = 4;
+ break;
+ }
+ if (skip) {
+ *iter += bytesToWrite;
+ } else {
+ for (size_t i=0; i<bytesToWrite; i++) {
+ buf->push_back(**iter);
+ (*iter)++;
+ }
+ }
+ return skip ? 0 : *iter - snapshot;
+}
+
+/**
+ * Strip next field based on its private policy and request spec, then stores data in buf.
+ * Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in FdBuffer.
+ *
+ * The iterator must point to the head of a protobuf formatted field for successful operation.
+ * After exit with NO_ERROR, iterator points to the next protobuf field's head.
+ */
+static status_t
+stripField(FdBuffer::iterator* iter, vector<uint8_t>* buf, const Privacy* parentPolicy, const PrivacySpec& spec)
+{
+ if (iter->outOfBound() || parentPolicy == NULL) return BAD_VALUE;
+
+ uint32_t varint = read_raw_varint(iter);
+ uint8_t wireType = read_wire_type(varint);
+ uint32_t fieldId = read_field_id(varint);
+ const Privacy* policy = parentPolicy->lookup(fieldId);
+
+ if (policy == NULL || !policy->IsMessageType() || !policy->HasChildren()) {
+ bool skip = !spec.CheckPremission(policy);
+ size_t amt = buf->size();
+ if (!skip) amt += write_header(buf, fieldId, wireType);
+ amt += write_field_or_skip(iter, buf, wireType, skip); // point to head of next field
+ return buf->size() != amt ? BAD_VALUE : NO_ERROR;
+ }
+ // current field is message type and its sub-fields have extra privacy policies
+ deque<vector<uint8_t>> q;
+ uint32_t msgSize = read_raw_varint(iter);
+ size_t finalSize = 0;
+ FdBuffer::iterator start = iter->snapshot();
+ while ((*iter - start) != (int)msgSize) {
+ vector<uint8_t> v;
+ status_t err = stripField(iter, &v, policy, spec);
+ if (err != NO_ERROR) return err;
+ if (v.empty()) continue;
+ q.push_back(v);
+ finalSize += v.size();
+ }
+
+ write_header(buf, fieldId, wireType);
+ write_raw_varint(buf, finalSize);
+ buf->reserve(finalSize); // reserve the size of the field
+ while (!q.empty()) {
+ vector<uint8_t> subField = q.front();
+ for (vector<uint8_t>::iterator it = subField.begin(); it != subField.end(); it++) {
+ buf->push_back(*it);
+ }
+ q.pop_front();
+ }
+ return NO_ERROR;
+}
+
+// ================================================================================
+EncodedBuffer::EncodedBuffer(const FdBuffer& buffer, const Privacy* policy)
+ : mFdBuffer(buffer),
+ mPolicy(policy),
+ mBuffers(),
+ mSize(0)
+{
+}
+
+EncodedBuffer::~EncodedBuffer()
+{
+}
+
+status_t
+EncodedBuffer::strip(const PrivacySpec& spec)
+{
+ // optimization when no strip happens
+ if (mPolicy == NULL || !mPolicy->HasChildren() || spec.RequireAll()) {
+ if (spec.CheckPremission(mPolicy)) mSize = mFdBuffer.size();
+ return NO_ERROR;
+ }
+
+ FdBuffer::iterator it = mFdBuffer.begin();
+ vector<uint8_t> field;
+ field.reserve(BUFFER_SIZE);
+
+ while (it != mFdBuffer.end()) {
+ status_t err = stripField(&it, &field, mPolicy, spec);
+ if (err != NO_ERROR) return err;
+ if (field.size() > BUFFER_SIZE) { // rotate to another chunk if buffer size exceeds
+ mBuffers.push_back(field);
+ mSize += field.size();
+ field.clear();
+ }
+ }
+ if (!field.empty()) {
+ mBuffers.push_back(field);
+ mSize += field.size();
+ }
+ return NO_ERROR;
+}
+
+void
+EncodedBuffer::clear()
+{
+ mSize = 0;
+ mBuffers.clear();
+}
+
+size_t
+EncodedBuffer::size() const { return mSize; }
+
+status_t
+EncodedBuffer::flush(int fd)
+{
+ if (size() == mFdBuffer.size()) return mFdBuffer.flush(fd);
+
+ for (vector<vector<uint8_t>>::iterator it = mBuffers.begin(); it != mBuffers.end(); it++) {
+ status_t err = write_all(fd, it->data(), it->size());
+ if (err != NO_ERROR) return err;
+ }
+ return NO_ERROR;
+}
+
diff --git a/cmds/incidentd/src/EncodedBuffer.h b/cmds/incidentd/src/EncodedBuffer.h
new file mode 100644
index 000000000000..ea8603a585d7
--- /dev/null
+++ b/cmds/incidentd/src/EncodedBuffer.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 ENCODED_BUFFER_H
+#define ENCODED_BUFFER_H
+
+#include "FdBuffer.h"
+#include "Privacy.h"
+
+#include <stdint.h>
+#include <vector>
+
+/**
+ * EncodedBuffer is constructed from FdBuffer which holds original protobuf formatted data and
+ * its privacy policy in its tagged proto message. The class strips PII-sensitive fields
+ * based on the request and holds stripped data in its buffer for output.
+ */
+class EncodedBuffer
+{
+public:
+ EncodedBuffer(const FdBuffer& buffer, const Privacy* policy);
+ ~EncodedBuffer();
+
+ /**
+ * Strip based on the request and hold data in its own buffer. Return NO_ERROR if strip succeeds.
+ */
+ status_t strip(const PrivacySpec& spec);
+
+ /**
+ * Clear encoded buffer so it can be reused by another request.
+ */
+ void clear();
+
+ /**
+ * Return the size of the stripped data.
+ */
+ size_t size() const;
+
+ /**
+ * Flush buffer to the given fd. NO_ERROR is returned if the flush succeeds.
+ */
+ status_t flush(int fd);
+
+private:
+ const FdBuffer& mFdBuffer;
+ const Privacy* mPolicy;
+ vector<vector<uint8_t>> mBuffers;
+ size_t mSize;
+};
+
+#endif // ENCODED_BUFFER_H \ No newline at end of file
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index 4d6a36cdba2e..bb399b57b8cd 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "incidentd"
#include "FdBuffer.h"
+#include "io_util.h"
#include <cutils/log.h>
#include <utils/SystemClock.h>
@@ -239,25 +240,32 @@ FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeou
}
size_t
-FdBuffer::size()
+FdBuffer::size() const
{
if (mBuffers.empty()) return 0;
return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten;
}
status_t
-FdBuffer::write(ReportRequestSet* reporter)
+FdBuffer::flush(int fd) const
{
- const int N = mBuffers.size() - 1;
- for (int i=0; i<N; i++) {
- reporter->write(mBuffers[i], BUFFER_SIZE);
+ size_t i=0;
+ status_t err = NO_ERROR;
+ for (i=0; i<mBuffers.size()-1; i++) {
+ err = write_all(fd, mBuffers[i], BUFFER_SIZE);
+ if (err != NO_ERROR) return err;
}
- reporter->write(mBuffers[N], mCurrentWritten);
- return NO_ERROR;
+ return write_all(fd, mBuffers[i], mCurrentWritten);
}
FdBuffer::iterator
-FdBuffer::end()
+FdBuffer::begin() const
+{
+ return iterator(*this, 0, 0);
+}
+
+FdBuffer::iterator
+FdBuffer::end() const
{
if (mBuffers.empty() || mCurrentWritten < 0) return begin();
if (mCurrentWritten == BUFFER_SIZE)
@@ -266,6 +274,17 @@ FdBuffer::end()
return FdBuffer::iterator(*this, mBuffers.size() - 1, mCurrentWritten);
}
+// ===============================================================================
+FdBuffer::iterator::iterator(const FdBuffer& buffer, ssize_t index, ssize_t offset)
+ : mFdBuffer(buffer),
+ mIndex(index),
+ mOffset(offset)
+{
+}
+
+FdBuffer::iterator&
+FdBuffer::iterator::operator=(iterator& other) const { return other; }
+
FdBuffer::iterator&
FdBuffer::iterator::operator+(size_t offset)
{
@@ -278,8 +297,50 @@ FdBuffer::iterator::operator+(size_t offset)
return *this;
}
+FdBuffer::iterator&
+FdBuffer::iterator::operator+=(size_t offset) { return *this + offset; }
+
+FdBuffer::iterator&
+FdBuffer::iterator::operator++() { return *this + 1; }
+
+FdBuffer::iterator
+FdBuffer::iterator::operator++(int) { return *this + 1; }
+
+bool
+FdBuffer::iterator::operator==(iterator other) const
+{
+ return mIndex == other.mIndex && mOffset == other.mOffset;
+}
+
+bool
+FdBuffer::iterator::operator!=(iterator other) const { return !(*this == other); }
+
+int
+FdBuffer::iterator::operator-(iterator other) const
+{
+ return (int)bytesRead() - (int)other.bytesRead();
+}
+
+FdBuffer::iterator::reference
+FdBuffer::iterator::operator*() const
+{
+ return mFdBuffer.mBuffers[mIndex][mOffset];
+}
+
+FdBuffer::iterator
+FdBuffer::iterator::snapshot() const
+{
+ return FdBuffer::iterator(mFdBuffer, mIndex, mOffset);
+}
+
size_t
-FdBuffer::iterator::bytesRead()
+FdBuffer::iterator::bytesRead() const
{
return mIndex * BUFFER_SIZE + mOffset;
}
+
+bool
+FdBuffer::iterator::outOfBound() const
+{
+ return bytesRead() > mFdBuffer.size();
+}
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index e9a53ffe513a..dfe39c62de42 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -17,8 +17,6 @@
#ifndef FD_BUFFER_H
#define FD_BUFFER_H
-#include "Reporter.h"
-
#include <utils/Errors.h>
#include <vector>
@@ -55,7 +53,7 @@ public:
/**
* Whether we timed out.
*/
- bool timedOut() { return mTimedOut; }
+ bool timedOut() const { return mTimedOut; }
/**
* If more than 4 MB is read, we truncate the data and return success.
@@ -65,23 +63,22 @@ public:
* happens, truncated() will return true so it can be marked. If the data is
* exactly 4 MB, truncated is still set. Sorry.
*/
- bool truncated() { return mTruncated; }
+ bool truncated() const { return mTruncated; }
/**
* How much data was read.
*/
- size_t size();
+ size_t size() const;
/**
- * [Deprecated] Write the data that we recorded to the fd given.
- * TODO: remove it once the iterator api is working
+ * Flush all the data to given file descriptor;
*/
- status_t write(ReportRequestSet* requests);
+ status_t flush(int fd) const;
/**
* How long the read took in milliseconds.
*/
- int64_t durationMs() { return mFinishTime - mStartTime; }
+ int64_t durationMs() const { return mFinishTime - mStartTime; }
/**
* Read data stored in FdBuffer
@@ -89,30 +86,31 @@ public:
class iterator;
friend class iterator;
class iterator : public std::iterator<std::random_access_iterator_tag, uint8_t> {
+ public:
+ iterator(const FdBuffer& buffer, ssize_t index, ssize_t offset);
+ iterator& operator=(iterator& other) const;
+ iterator& operator+(size_t offset);
+ iterator& operator+=(size_t offset);
+ iterator& operator++();
+ iterator operator++(int);
+ bool operator==(iterator other) const;
+ bool operator!=(iterator other) const;
+ int operator-(iterator other) const;
+ reference operator*() const;
+
+ // return the snapshot of the current iterator
+ iterator snapshot() const;
+ // how many bytes are read
+ size_t bytesRead() const;
+ // random access could make the iterator out of bound
+ bool outOfBound() const;
private:
- FdBuffer& mFdBuffer;
+ const FdBuffer& mFdBuffer;
size_t mIndex;
size_t mOffset;
- public:
- explicit iterator(FdBuffer& buffer, ssize_t index, ssize_t offset)
- : mFdBuffer(buffer), mIndex(index), mOffset(offset) {}
- iterator& operator=(iterator& other) { return other; }
- iterator& operator+(size_t offset); // this is implemented in .cpp
- iterator& operator+=(size_t offset) { return *this + offset; }
- iterator& operator++() { return *this + 1; }
- iterator operator++(int) { return *this + 1; }
- bool operator==(iterator other) const {
- return mIndex == other.mIndex && mOffset == other.mOffset;
- }
- bool operator!=(iterator other) const { return !(*this == other); }
- reference operator*() const { return mFdBuffer.mBuffers[mIndex][mOffset]; }
-
- // random access could make the iterator out of bound
- size_t bytesRead();
- bool outOfBound() { return bytesRead() > mFdBuffer.size(); };
};
- iterator begin() { return iterator(*this, 0, 0); }
- iterator end();
+ iterator begin() const;
+ iterator end() const;
private:
vector<uint8_t*> mBuffers;
@@ -123,19 +121,4 @@ private:
bool mTruncated;
};
-class Fpipe {
-public:
- Fpipe() {}
- bool close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
- ~Fpipe() { close(); }
-
- inline bool init() { return pipe(mFds) != -1; }
- inline int readFd() const { return mFds[0]; }
- inline int writeFd() const { return mFds[1]; }
-
-private:
- int mFds[2];
-};
-
-
#endif // FD_BUFFER_H
diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp
new file mode 100644
index 000000000000..e7969e78e9e4
--- /dev/null
+++ b/cmds/incidentd/src/Privacy.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 "Privacy.h"
+
+#include <stdlib.h>
+
+// DESTINATION enum value
+const uint8_t DEST_LOCAL = 0;
+const uint8_t DEST_EXPLICIT = 1;
+const uint8_t DEST_AUTOMATIC = 2;
+
+// type of the field, identitical to protobuf definition
+const uint8_t TYPE_STRING = 9;
+const uint8_t TYPE_MESSAGE = 11;
+
+bool
+Privacy::IsMessageType() const { return type == TYPE_MESSAGE; }
+
+bool
+Privacy::IsStringType() const { return type == TYPE_STRING; }
+
+bool
+Privacy::HasChildren() const { return children != NULL && children[0] != NULL; }
+
+const Privacy*
+Privacy::lookup(uint32_t fieldId) const
+{
+ if (children == NULL) return NULL;
+ for (int i=0; children[i] != NULL; i++) {
+ if (children[i]->field_id == fieldId) return children[i];
+ // This assumes the list's field id is in ascending order and must be true.
+ if (children[i]->field_id > fieldId) return NULL;
+ }
+ return NULL;
+}
+
+static bool allowDest(const uint8_t dest, const uint8_t policy)
+{
+ switch (policy) {
+ case DEST_LOCAL:
+ return dest == DEST_LOCAL;
+ case DEST_EXPLICIT:
+ return dest == DEST_LOCAL || dest == DEST_EXPLICIT;
+ case DEST_AUTOMATIC:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+PrivacySpec::operator<(const PrivacySpec& other) const
+{
+ return dest < other.dest;
+}
+
+bool
+PrivacySpec::CheckPremission(const Privacy* privacy) const
+{
+ uint8_t policy = privacy == NULL ? DEST_DEFAULT_VALUE : privacy->dest;
+ return allowDest(dest, policy);
+}
+
+bool
+PrivacySpec::RequireAll() const { return dest == DEST_LOCAL; }
+
+PrivacySpec new_spec_from_args(int dest) {
+ if (dest < 0) return PrivacySpec();
+ return PrivacySpec(dest);
+}
+
+PrivacySpec get_default_dropbox_spec() { return PrivacySpec(DEST_AUTOMATIC); } \ No newline at end of file
diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h
new file mode 100644
index 000000000000..7f1977e3c835
--- /dev/null
+++ b/cmds/incidentd/src/Privacy.h
@@ -0,0 +1,66 @@
+/*
+ * 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 PRIVACY_H
+#define PRIVACY_H
+
+#include <stdint.h>
+
+// This is the default value of DEST enum
+const uint8_t DEST_DEFAULT_VALUE = 1;
+
+/*
+ * In order not to depend on libprotobuf-cpp-full nor libplatformprotos in incidentd,
+ * privacy options's data structure are explicitly redefined in this file.
+ */
+struct Privacy {
+ uint32_t field_id;
+ uint8_t type;
+ // ignore parent's privacy flags if children are set, NULL-terminated
+ Privacy** children;
+
+ // the following fields are identitical to
+ // frameworks/base/libs/incident/proto/android/privacy.proto
+ uint8_t dest;
+ const char** patterns; // only set when type is string
+
+ bool IsMessageType() const;
+ bool IsStringType() const;
+ bool HasChildren() const;
+ const Privacy* lookup(uint32_t fieldId) const;
+};
+
+/**
+ * PrivacySpec defines the request has what level of privacy authorization.
+ * For example, a device without user consent should only be able to upload AUTOMATIC fields.
+ */
+class PrivacySpec {
+public:
+ const uint8_t dest;
+
+ PrivacySpec() : dest(DEST_DEFAULT_VALUE) {}
+ PrivacySpec(uint8_t dest) : dest(dest) {}
+
+ bool operator<(const PrivacySpec& other) const;
+
+ bool CheckPremission(const Privacy* privacy) const;
+ bool RequireAll() const;
+};
+
+PrivacySpec new_spec_from_args(int dest);
+PrivacySpec get_default_dropbox_spec();
+
+#endif // PRIVACY_H
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 4ffc11984224..11347e22d88e 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "incidentd"
#include "Reporter.h"
-#include "protobuf.h"
#include "report_directory.h"
#include "section_list.h"
@@ -38,20 +37,6 @@
static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/";
// ================================================================================
-static status_t write_all(int fd, uint8_t const* buf, size_t size)
-{
- while (size > 0) {
- ssize_t amt = ::write(fd, buf, size);
- if (amt < 0) {
- return -errno;
- }
- size -= amt;
- buf += amt;
- }
- return NO_ERROR;
-}
-
-// ================================================================================
ReportRequest::ReportRequest(const IncidentReportArgs& a,
const sp<IIncidentReportStatusListener> &l, int f)
:args(a),
@@ -65,11 +50,16 @@ ReportRequest::~ReportRequest()
{
}
+bool
+ReportRequest::ok()
+{
+ return fd >= 0 && err == NO_ERROR;
+}
+
// ================================================================================
ReportRequestSet::ReportRequestSet()
:mRequests(),
mSections(),
- mWritableCount(0),
mMainFd(-1)
{
}
@@ -84,45 +74,12 @@ ReportRequestSet::add(const sp<ReportRequest>& request)
{
mRequests.push_back(request);
mSections.merge(request->args);
- mWritableCount++;
}
void
ReportRequestSet::setMainFd(int fd)
{
mMainFd = fd;
- mWritableCount++;
-}
-
-status_t
-ReportRequestSet::write(uint8_t const* buf, size_t size)
-{
- status_t err = EBADF;
-
- // The streaming ones
- int const N = mRequests.size();
- for (int i=N-1; i>=0; i--) {
- sp<ReportRequest> request = mRequests[i];
- if (request->fd >= 0 && request->err == NO_ERROR) {
- err = write_all(request->fd, buf, size);
- if (err != NO_ERROR) {
- request->err = err;
- mWritableCount--;
- }
- }
- }
-
- // The dropbox file
- if (mMainFd >= 0) {
- err = write_all(mMainFd, buf, size);
- if (err != NO_ERROR) {
- mMainFd = -1;
- mWritableCount--;
- }
- }
-
- // Return an error only when there are no FDs to write.
- return mWritableCount > 0 ? NO_ERROR : err;
}
bool
@@ -164,6 +121,7 @@ Reporter::runReport()
status_t err = NO_ERROR;
bool needMainFd = false;
int mainFd = -1;
+ HeaderSection headers;
// See if we need the main file
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
@@ -176,7 +134,7 @@ Reporter::runReport()
// Create the directory
if (!isTest) err = create_directory(mIncidentDirectory);
if (err != NO_ERROR) {
- goto done;
+ goto DONE;
}
// If there are too many files in the directory (for whatever reason),
@@ -187,7 +145,7 @@ Reporter::runReport()
// Open the file.
err = create_file(&mainFd);
if (err != NO_ERROR) {
- goto done;
+ goto DONE;
}
// Add to the set
@@ -202,24 +160,7 @@ Reporter::runReport()
}
// Write the incident headers
- for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
- const sp<ReportRequest> request = (*it);
- const vector<vector<int8_t>>& headers = request->args.headers();
-
- for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end();
- buf++) {
- int fd = request->fd >= 0 ? request->fd : mainFd;
-
- uint8_t buffer[20];
- uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER,
- buf->size());
- write_all(fd, buffer, p-buffer);
-
- write_all(fd, (uint8_t const*)buf->data(), buf->size());
- // If there was an error now, there will be an error later and we will remove
- // it from the list then.
- }
- }
+ headers.Execute(&batch);
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
@@ -240,7 +181,7 @@ Reporter::runReport()
if (err != NO_ERROR) {
ALOGW("Incident section %s (%d) failed. Stopping report.",
(*section)->name.string(), id);
- goto done;
+ goto DONE;
}
// Notify listener of starting
@@ -254,7 +195,7 @@ Reporter::runReport()
}
}
-done:
+DONE:
// Close the file.
if (mainFd >= 0) {
close(mainFd);
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index 509611c34d4b..2615c6202d3d 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -40,6 +40,8 @@ struct ReportRequest : public virtual RefBase
ReportRequest(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener> &listener, int fd);
virtual ~ReportRequest();
+
+ bool ok(); // returns true if the request is ok for write.
};
// ================================================================================
@@ -52,21 +54,16 @@ public:
void add(const sp<ReportRequest>& request);
void setMainFd(int fd);
- // Write to all of the fds for the requests. If a write fails, it stops
- // writing to that fd and returns NO_ERROR. When we are out of fds to write
- // to it returns an error.
- status_t write(uint8_t const* buf, size_t size);
-
typedef vector<sp<ReportRequest>>::iterator iterator;
iterator begin() { return mRequests.begin(); }
iterator end() { return mRequests.end(); }
+ int mainFd() { return mMainFd; }
bool containsSection(int id);
private:
vector<sp<ReportRequest>> mRequests;
IncidentReportArgs mSections;
- int mWritableCount;
int mMainFd;
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index ac87fe3b1e40..166fef08441a 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -16,11 +16,18 @@
#define LOG_TAG "incidentd"
+#include "EncodedBuffer.h"
+#include "FdBuffer.h"
+#include "Privacy.h"
#include "Section.h"
+
+#include "io_util.h"
#include "protobuf.h"
+#include "section_list.h"
#include <private/android_filesystem_config.h>
#include <binder/IServiceManager.h>
+#include <map>
#include <mutex>
#include <wait.h>
#include <unistd.h>
@@ -32,7 +39,7 @@ const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
const char* INCIDENT_HELPER = "/system/bin/incident_helper";
static pid_t
-forkAndExecuteIncidentHelper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
+fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
{
const char* ihArgs[] { INCIDENT_HELPER, "-s", String8::format("%d", id).string(), NULL };
@@ -66,14 +73,15 @@ forkAndExecuteIncidentHelper(const int id, const char* name, Fpipe& p2cPipe, Fpi
return pid;
}
-static status_t killChild(pid_t pid) {
+// ================================================================================
+static status_t kill_child(pid_t pid) {
int status;
kill(pid, SIGKILL);
if (waitpid(pid, &status, 0) == -1) return -1;
return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
}
-static status_t waitForChild(pid_t pid) {
+static status_t wait_child(pid_t pid) {
int status;
bool died = false;
// wait for child to report status up to 1 seconds
@@ -82,13 +90,98 @@ static status_t waitForChild(pid_t pid) {
// sleep for 0.2 second
nanosleep(&WAIT_INTERVAL_NS, NULL);
}
- if (!died) return killChild(pid);
+ if (!died) return kill_child(pid);
return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
}
+// ================================================================================
+static const Privacy*
+get_privacy_of_section(int id)
+{
+ int l = 0;
+ int r = PRIVACY_POLICY_COUNT - 1;
+ while (l <= r) {
+ int mid = (l + r) >> 1;
+ const Privacy* p = PRIVACY_POLICY_LIST[mid];
+
+ if (p->field_id < (uint32_t)id) {
+ l = mid + 1;
+ } else if (p->field_id > (uint32_t)id) {
+ r = mid - 1;
+ } else {
+ return p;
+ }
+ }
+ return NULL;
+}
+
+// ================================================================================
+static status_t
+write_section_header(int fd, int sectionId, size_t size)
+{
+ uint8_t buf[20];
+ uint8_t *p = write_length_delimited_tag_header(buf, sectionId, size);
+ return write_all(fd, buf, p-buf);
+}
+
+static status_t
+write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* requests)
+{
+ status_t err = -EBADF;
+ EncodedBuffer encodedBuffer(buffer, get_privacy_of_section(id));
+ int writeable = 0;
+
+ // The streaming ones, group requests by spec in order to save unnecessary strip operations
+ map<PrivacySpec, vector<sp<ReportRequest>>> requestsBySpec;
+ for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) {
+ sp<ReportRequest> request = *it;
+ if (!request->ok() || !request->args.containsSection(id)) {
+ continue; // skip invalid request
+ }
+ PrivacySpec spec = new_spec_from_args(request->args.dest());
+ requestsBySpec[spec].push_back(request);
+ }
+
+ for (map<PrivacySpec, vector<sp<ReportRequest>>>::iterator mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) {
+ PrivacySpec spec = mit->first;
+ err = encodedBuffer.strip(spec);
+ if (err != NO_ERROR) return err; // it means the encodedBuffer data is corrupted.
+ if (encodedBuffer.size() == 0) continue;
+
+ for (vector<sp<ReportRequest>>::iterator it = mit->second.begin(); it != mit->second.end(); it++) {
+ sp<ReportRequest> request = *it;
+ err = write_section_header(request->fd, id, encodedBuffer.size());
+ if (err != NO_ERROR) { request->err = err; continue; }
+ err = encodedBuffer.flush(request->fd);
+ if (err != NO_ERROR) { request->err = err; continue; }
+ writeable++;
+ ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, encodedBuffer.size(), request->fd, spec.dest);
+ }
+ encodedBuffer.clear();
+ }
+
+ // The dropbox file
+ if (requests->mainFd() >= 0) {
+ err = encodedBuffer.strip(get_default_dropbox_spec());
+ if (err != NO_ERROR) return err; // the buffer data is corrupted.
+ if (encodedBuffer.size() == 0) goto DONE;
+
+ err = write_section_header(requests->mainFd(), id, encodedBuffer.size());
+ if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; }
+ err = encodedBuffer.flush(requests->mainFd());
+ if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; }
+ writeable++;
+ ALOGD("Section %d flushed %zu bytes to dropbox %d", id, encodedBuffer.size(), requests->mainFd());
+ }
+
+DONE:
+ // only returns error if there is no fd to write to.
+ return writeable > 0 ? NO_ERROR : err;
+}
// ================================================================================
Section::Section(int i, const int64_t timeoutMs)
- :id(i), timeoutMs(timeoutMs)
+ :id(i),
+ timeoutMs(timeoutMs)
{
}
@@ -96,24 +189,50 @@ Section::~Section()
{
}
+// ================================================================================
+HeaderSection::HeaderSection()
+ :Section(FIELD_ID_INCIDENT_HEADER, 0)
+{
+}
+
+HeaderSection::~HeaderSection()
+{
+}
+
status_t
-Section::WriteHeader(ReportRequestSet* requests, size_t size) const
+HeaderSection::Execute(ReportRequestSet* requests) const
{
- ssize_t amt;
- uint8_t buf[20];
- uint8_t* p = write_length_delimited_tag_header(buf, this->id, size);
- return requests->write(buf, p-buf);
+ for (ReportRequestSet::iterator it=requests->begin(); it!=requests->end(); it++) {
+ const sp<ReportRequest> request = *it;
+ const vector<vector<int8_t>>& headers = request->args.headers();
+
+ for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); buf++) {
+ if (buf->empty()) continue;
+
+ // So the idea is only requests with negative fd are written to dropbox file.
+ int fd = request->fd >= 0 ? request->fd : requests->mainFd();
+ write_section_header(fd, FIELD_ID_INCIDENT_HEADER, buf->size());
+ write_all(fd, (uint8_t const*)buf->data(), buf->size());
+ // If there was an error now, there will be an error later and we will remove
+ // it from the list then.
+ }
+ }
+ return NO_ERROR;
}
// ================================================================================
FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
- : Section(id, timeoutMs), mFilename(filename) {
+ :Section(id, timeoutMs),
+ mFilename(filename)
+{
name = filename;
}
FileSection::~FileSection() {}
-status_t FileSection::Execute(ReportRequestSet* requests) const {
+status_t
+FileSection::Execute(ReportRequestSet* requests) const
+{
// read from mFilename first, make sure the file is available
// add O_CLOEXEC to make sure it is closed when exec incident helper
int fd = open(mFilename, O_RDONLY | O_CLOEXEC);
@@ -131,7 +250,7 @@ status_t FileSection::Execute(ReportRequestSet* requests) const {
return -errno;
}
- pid_t pid = forkAndExecuteIncidentHelper(this->id, this->name.string(), p2cPipe, c2pPipe);
+ pid_t pid = fork_execute_incident_helper(this->id, this->name.string(), p2cPipe, c2pPipe);
if (pid == -1) {
ALOGW("FileSection '%s' failed to fork", this->name.string());
return -errno;
@@ -143,11 +262,11 @@ status_t FileSection::Execute(ReportRequestSet* requests) const {
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s, kill: %s",
this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
- strerror(-killChild(pid)));
+ strerror(-kill_child(pid)));
return readStatus;
}
- status_t ihStatus = waitForChild(pid);
+ status_t ihStatus = wait_child(pid);
if (ihStatus != NO_ERROR) {
ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-ihStatus));
return ihStatus;
@@ -155,8 +274,7 @@ status_t FileSection::Execute(ReportRequestSet* requests) const {
ALOGD("FileSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
(int)buffer.durationMs());
- WriteHeader(requests, buffer.size());
- status_t err = buffer.write(requests);
+ status_t err = write_report_requests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err));
return err;
@@ -313,8 +431,7 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const
// Write the data that was collected
ALOGD("WorkerThreadSection '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
(int)buffer.durationMs());
- WriteHeader(requests, buffer.size());
- err = buffer.write(requests);
+ err = write_report_requests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err));
return err;
@@ -324,7 +441,8 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const
}
// ================================================================================
-void CommandSection::init(const char* command, va_list args)
+void
+CommandSection::init(const char* command, va_list args)
{
va_list copied_args;
int numOfArgs = 0;
@@ -350,7 +468,7 @@ void CommandSection::init(const char* command, va_list args)
}
CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...)
- : Section(id, timeoutMs)
+ :Section(id, timeoutMs)
{
va_list args;
va_start(args, command);
@@ -359,7 +477,7 @@ CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* comm
}
CommandSection::CommandSection(int id, const char* command, ...)
- : Section(id)
+ :Section(id)
{
va_list args;
va_start(args, command);
@@ -401,7 +519,7 @@ CommandSection::Execute(ReportRequestSet* requests) const
ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno));
_exit(err); // exit with command error code
}
- pid_t ihPid = forkAndExecuteIncidentHelper(this->id, this->name.string(), cmdPipe, ihPipe);
+ pid_t ihPid = fork_execute_incident_helper(this->id, this->name.string(), cmdPipe, ihPipe);
if (ihPid == -1) {
ALOGW("CommandSection '%s' failed to fork", this->name.string());
return -errno;
@@ -413,14 +531,14 @@ CommandSection::Execute(ReportRequestSet* requests) const
ALOGW("CommandSection '%s' failed to read data from incident helper: %s, "
"timedout: %s, kill command: %s, kill incident helper: %s",
this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
- strerror(-killChild(cmdPid)), strerror(-killChild(ihPid)));
+ strerror(-kill_child(cmdPid)), strerror(-kill_child(ihPid)));
return readStatus;
}
// TODO: wait for command here has one trade-off: the failed status of command won't be detected until
// buffer timeout, but it has advatage on starting the data stream earlier.
- status_t cmdStatus = waitForChild(cmdPid);
- status_t ihStatus = waitForChild(ihPid);
+ status_t cmdStatus = wait_child(cmdPid);
+ status_t ihStatus = wait_child(ihPid);
if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incident helper: %s",
this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
@@ -429,8 +547,7 @@ CommandSection::Execute(ReportRequestSet* requests) const
ALOGD("CommandSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
(int)buffer.durationMs());
- WriteHeader(requests, buffer.size());
- status_t err = buffer.write(requests);
+ status_t err = write_report_requests(this->id, buffer, requests);
if (err != NO_ERROR) {
ALOGW("CommandSection '%s' failed writing: %s", this->name.string(), strerror(-err));
return err;
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 93b4848f5bd8..0a1e03eb6f41 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -17,7 +17,7 @@
#ifndef SECTIONS_H
#define SECTIONS_H
-#include "FdBuffer.h"
+#include "Reporter.h"
#include <stdarg.h>
#include <utils/String8.h>
@@ -42,8 +42,18 @@ public:
virtual ~Section();
virtual status_t Execute(ReportRequestSet* requests) const = 0;
+};
- status_t WriteHeader(ReportRequestSet* requests, size_t size) const;
+/**
+ * Section that generates incident headers.
+ */
+class HeaderSection : public Section
+{
+public:
+ HeaderSection();
+ virtual ~HeaderSection();
+
+ virtual status_t Execute(ReportRequestSet* requests) const;
};
/**
diff --git a/cmds/incidentd/src/io_util.cpp b/cmds/incidentd/src/io_util.cpp
new file mode 100644
index 000000000000..f043d367d982
--- /dev/null
+++ b/cmds/incidentd/src/io_util.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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 "io_util.h"
+
+#include <unistd.h>
+
+status_t write_all(int fd, uint8_t const* buf, size_t size)
+{
+ while (size > 0) {
+ ssize_t amt = ::write(fd, buf, size);
+ if (amt < 0) {
+ return -errno;
+ }
+ size -= amt;
+ buf += amt;
+ }
+ return NO_ERROR;
+}
+
+Fpipe::Fpipe() {}
+
+Fpipe::~Fpipe() { close(); }
+
+bool Fpipe::close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
+
+bool Fpipe::init() { return pipe(mFds) != -1; }
+
+int Fpipe::readFd() const { return mFds[0]; }
+
+int Fpipe::writeFd() const { return mFds[1]; }
diff --git a/cmds/incidentd/src/io_util.h b/cmds/incidentd/src/io_util.h
new file mode 100644
index 000000000000..320dd6c386d2
--- /dev/null
+++ b/cmds/incidentd/src/io_util.h
@@ -0,0 +1,41 @@
+/*
+ * 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 IO_UTIL_H
+#define IO_UTIL_H
+
+#include <stdint.h>
+#include <utils/Errors.h>
+
+using namespace android;
+
+status_t write_all(int fd, uint8_t const* buf, size_t size);
+
+class Fpipe {
+public:
+ Fpipe();
+ ~Fpipe();
+
+ bool init();
+ bool close();
+ int readFd() const;
+ int writeFd() const;
+
+private:
+ int mFds[2];
+};
+
+#endif // IO_UTIL_H \ No newline at end of file
diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp
index b865339a9b98..4fffec1e80b9 100644
--- a/cmds/incidentd/src/protobuf.cpp
+++ b/cmds/incidentd/src/protobuf.cpp
@@ -16,8 +16,17 @@
#include "protobuf.h"
+uint8_t read_wire_type(uint32_t varint)
+{
+ return (uint8_t) (varint & 0x07);
+}
+
+uint32_t read_field_id(uint32_t varint)
+{
+ return varint >> 3;
+}
-uint8_t*
+uint8_t*
write_raw_varint(uint8_t* buf, uint32_t val)
{
uint8_t* p = buf;
@@ -32,7 +41,7 @@ write_raw_varint(uint8_t* buf, uint32_t val)
}
}
-uint8_t*
+uint8_t*
write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size)
{
buf = write_raw_varint(buf, (fieldId << 3) | 2);
@@ -40,3 +49,24 @@ write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size)
return buf;
}
+size_t
+write_raw_varint(vector<uint8_t>* buf, uint32_t val)
+{
+ size_t size = 0;
+ while (true) {
+ size++;
+ if ((val & ~0x7F) == 0) {
+ buf->push_back((uint8_t) val);
+ return size;
+ } else {
+ buf->push_back((uint8_t)((val & 0x7F) | 0x80));
+ val >>= 7;
+ }
+ }
+}
+
+size_t
+write_header(vector<uint8_t>* buf, uint32_t fieldId, uint8_t wireType)
+{
+ return write_raw_varint(buf, (fieldId << 3) | wireType);
+} \ No newline at end of file
diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h
index f196ddc11967..263c864dc2cc 100644
--- a/cmds/incidentd/src/protobuf.h
+++ b/cmds/incidentd/src/protobuf.h
@@ -18,6 +18,24 @@
#define PROTOBUF_H
#include <stdint.h>
+#include <vector>
+
+using namespace std;
+
+const uint8_t WIRE_TYPE_VARINT = 0;
+const uint8_t WIRE_TYPE_FIXED64 = 1;
+const uint8_t WIRE_TYPE_LENGTH_DELIMITED = 2;
+const uint8_t WIRE_TYPE_FIXED32 = 5;
+
+/**
+ * Read the wire type from varint, it is the smallest 3 bits.
+ */
+uint8_t read_wire_type(uint32_t varint);
+
+/**
+ * read field id from varint, it is varint >> 3;
+ */
+uint32_t read_field_id(uint32_t varint);
/**
* Write a varint into the buffer. Return the next position to write at.
@@ -32,6 +50,16 @@ uint8_t* write_raw_varint(uint8_t* buf, uint32_t val);
*/
uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size);
+/**
+ * Write a varint into a vector. Return the size of the varint.
+ */
+size_t write_raw_varint(vector<uint8_t>* buf, uint32_t val);
+
+/**
+ * Write a protobuf header. Return the size of the header.
+ */
+size_t write_header(vector<uint8_t>* buf, uint32_t fieldId, uint8_t wireType);
+
enum {
// IncidentProto.header
FIELD_ID_INCIDENT_HEADER = 1
diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h
index 1abdb5284001..da82b008fc9b 100644
--- a/cmds/incidentd/src/section_list.h
+++ b/cmds/incidentd/src/section_list.h
@@ -17,50 +17,22 @@
#ifndef SECTION_LIST_H
#define SECTION_LIST_H
+#include "Privacy.h"
#include "Section.h"
/**
* This is the mapping of section IDs to the commands that are run to get those commands.
- * The section IDs are guaranteed in ascending order
+ * The section IDs are guaranteed in ascending order, NULL-terminated.
*/
extern const Section* SECTION_LIST[];
-/*
- * In order not to use libprotobuf-cpp-full nor libplatformprotos in incidentd
- * privacy options's data structure are explicityly redefined in this file.
- */
-
-// DESTINATION enum
-extern const uint8_t DEST_LOCAL;
-extern const uint8_t DEST_EXPLICIT;
-extern const uint8_t DEST_AUTOMATIC;
-
-// This is the default value of DEST enum
-// field with this value doesn't generate Privacy to save too much generated code
-extern const uint8_t DEST_DEFAULT_VALUE;
-
-// type of the field, identitical to protobuf definition
-extern const uint8_t TYPE_STRING;
-extern const uint8_t TYPE_MESSAGE;
-
-struct Privacy {
- int field_id;
- uint8_t type;
-
- // the following two fields are identitical to
- // frameworks/base/libs/incident/proto/android/privacy.proto
- uint8_t dest;
- const char** patterns;
-
- // ignore parent's privacy flags if children are set, NULL-terminated
- const Privacy** children;
-};
-
/**
* This is the mapping of section IDs to each section's privacy policy.
- * The section IDs are guaranteed in ascending order
+ * The section IDs are guaranteed in ascending order, not NULL-terminated since size is provided.
*/
extern const Privacy* PRIVACY_POLICY_LIST[];
+extern const int PRIVACY_POLICY_COUNT;
+
#endif // SECTION_LIST_H
diff --git a/cmds/incidentd/tests/EncodedBuffer_test.cpp b/cmds/incidentd/tests/EncodedBuffer_test.cpp
new file mode 100644
index 000000000000..37a938a6de07
--- /dev/null
+++ b/cmds/incidentd/tests/EncodedBuffer_test.cpp
@@ -0,0 +1,255 @@
+// 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 "EncodedBuffer.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string.h>
+
+using namespace android;
+using namespace android::base;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStdout;
+
+const uint8_t LOCAL = 0;
+const uint8_t EXPLICIT = 1;
+const uint8_t AUTOMATIC = 2;
+
+const uint8_t OTHER_TYPE = 1;
+const uint8_t STRING_TYPE = 9;
+const uint8_t MESSAGE_TYPE = 11;
+const string STRING_FIELD_0 = "\x02\viamtestdata";
+const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
+const string STRING_FIELD_2 = "\x12\vwhatthefuck";
+const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
+const string FIX32_FIELD_4 = "\x25\xff\xff\xff\xff"; // -1
+const string MESSAGE_FIELD_5 = "\x2a\x10" + VARINT_FIELD_1 + STRING_FIELD_2;
+
+class EncodedBufferTest : public Test {
+public:
+ virtual ~EncodedBufferTest() {
+ // Delete in reverse order of construction, to be consistent with
+ // regular allocation/deallocation.
+ while (!privacies.empty()) {
+ delete privacies.back();
+ privacies.pop_back();
+ }
+ }
+
+ virtual void SetUp() override {
+ ASSERT_NE(tf.fd, -1);
+ }
+
+ void writeToFdBuffer(string str) {
+ ASSERT_TRUE(WriteStringToFile(str, tf.path, false));
+ ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
+ }
+
+ void assertBuffer(EncodedBuffer& buf, string expected) {
+ ASSERT_EQ(buf.size(), expected.size());
+ CaptureStdout();
+ ASSERT_EQ(buf.flush(STDOUT_FILENO), NO_ERROR);
+ ASSERT_THAT(GetCapturedStdout(), StrEq(expected));
+ }
+
+ void assertStrip(uint8_t dest, string expected, Privacy* policy) {
+ PrivacySpec spec(dest);
+ EncodedBuffer encodedBuf(buffer, policy);
+ ASSERT_EQ(encodedBuf.strip(spec), NO_ERROR);
+ assertBuffer(encodedBuf, expected);
+ }
+
+ void assertStripByFields(uint8_t dest, string expected, int size, Privacy* privacy, ...) {
+ Privacy* list[size+1];
+ list[0] = privacy;
+ va_list args;
+ va_start(args, privacy);
+ for (int i=1; i<size; i++) {
+ Privacy* p = va_arg(args, Privacy*);
+ list[i] = p;
+ }
+ va_end(args);
+ list[size] = NULL;
+ assertStrip(dest, expected, create_message_privacy(300, list));
+ }
+
+ Privacy* create_privacy(uint32_t field_id, uint8_t type, uint8_t dest) {
+ Privacy* p = new_uninit_privacy();
+ p->field_id = field_id;
+ p->type = type;
+ p->children = NULL;
+ p->dest = dest;
+ p->patterns = NULL;
+ return p;
+ }
+
+ Privacy* create_message_privacy(uint32_t field_id, Privacy** children) {
+ Privacy* p = new_uninit_privacy();
+ p->field_id = field_id;
+ p->type = MESSAGE_TYPE;
+ p->children = children;
+ p->dest = EXPLICIT;
+ p->patterns = NULL;
+ return p;
+ }
+
+ Privacy* create_string_privacy(uint32_t field_id, uint8_t dest, const char** patterns) {
+ Privacy* p = new_uninit_privacy();
+ p->field_id = field_id;
+ p->type = STRING_TYPE;
+ p->children = NULL;
+ p->dest = dest;
+ p->patterns = patterns;
+ return p;
+ }
+
+ FdBuffer buffer;
+private:
+ TemporaryFile tf;
+ // Littering this code with unique_ptr (or similar) is ugly, so we just
+ // mass-free everything after the test completes.
+ std::vector<Privacy *> privacies;
+
+ Privacy *new_uninit_privacy() {
+ Privacy* p = new Privacy;
+ privacies.push_back(p);
+ return p;
+ }
+};
+
+TEST_F(EncodedBufferTest, NullFieldPolicy) {
+ writeToFdBuffer(STRING_FIELD_0);
+ assertStrip(EXPLICIT, STRING_FIELD_0, create_string_privacy(300, AUTOMATIC, NULL));
+}
+
+TEST_F(EncodedBufferTest, StripSpecNotAllowed) {
+ writeToFdBuffer(STRING_FIELD_0);
+ assertStripByFields(AUTOMATIC, "", 1, create_privacy(0, STRING_TYPE, EXPLICIT));
+}
+
+TEST_F(EncodedBufferTest, StripVarintField) {
+ writeToFdBuffer(VARINT_FIELD_1);
+ assertStripByFields(EXPLICIT, "", 1, create_privacy(1, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripLengthDelimitedField_String) {
+ writeToFdBuffer(STRING_FIELD_2);
+ assertStripByFields(EXPLICIT, "", 1, create_privacy(2, STRING_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripFixed64Field) {
+ writeToFdBuffer(FIX64_FIELD_3);
+ assertStripByFields(EXPLICIT, "", 1, create_privacy(3, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripFixed32Field) {
+ writeToFdBuffer(FIX32_FIELD_4);
+ assertStripByFields(EXPLICIT, "", 1, create_privacy(4, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripLengthDelimitedField_Message) {
+ writeToFdBuffer(MESSAGE_FIELD_5);
+ assertStripByFields(EXPLICIT, "", 1, create_privacy(5, MESSAGE_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, NoStripVarintField) {
+ writeToFdBuffer(VARINT_FIELD_1);
+ assertStripByFields(EXPLICIT, VARINT_FIELD_1, 1, create_privacy(1, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripLengthDelimitedField_String) {
+ writeToFdBuffer(STRING_FIELD_2);
+ assertStripByFields(EXPLICIT, STRING_FIELD_2, 1, create_privacy(2, STRING_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripFixed64Field) {
+ writeToFdBuffer(FIX64_FIELD_3);
+ assertStripByFields(EXPLICIT, FIX64_FIELD_3, 1, create_privacy(3, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripFixed32Field) {
+ writeToFdBuffer(FIX32_FIELD_4);
+ assertStripByFields(EXPLICIT, FIX32_FIELD_4, 1, create_privacy(4, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, NoStripLengthDelimitedField_Message) {
+ writeToFdBuffer(MESSAGE_FIELD_5);
+ assertStripByFields(EXPLICIT, MESSAGE_FIELD_5, 1, create_privacy(5, MESSAGE_TYPE, AUTOMATIC));
+}
+
+TEST_F(EncodedBufferTest, StripVarintAndString) {
+ writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
+ + FIX64_FIELD_3 + FIX32_FIELD_4);
+ string expected = STRING_FIELD_0 + FIX64_FIELD_3 + FIX32_FIELD_4;
+ assertStripByFields(EXPLICIT, expected, 2,
+ create_privacy(1, OTHER_TYPE, LOCAL), create_privacy(2, STRING_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripVarintAndFixed64) {
+ writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
+ + FIX64_FIELD_3 + FIX32_FIELD_4);
+ string expected = STRING_FIELD_0 + STRING_FIELD_2 + FIX32_FIELD_4;
+ assertStripByFields(EXPLICIT, expected, 2,
+ create_privacy(1, OTHER_TYPE, LOCAL), create_privacy(3, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(EncodedBufferTest, StripVarintInNestedMessage) {
+ writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5);
+ Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+ string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
+ assertStripByFields(EXPLICIT, expected, 1, create_message_privacy(5, list));
+}
+
+TEST_F(EncodedBufferTest, StripFix64AndVarintInNestedMessage) {
+ writeToFdBuffer(STRING_FIELD_0 + FIX64_FIELD_3 + MESSAGE_FIELD_5);
+ Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+ string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
+ assertStripByFields(EXPLICIT, expected, 2, create_privacy(3, OTHER_TYPE, LOCAL), create_message_privacy(5, list));
+}
+
+TEST_F(EncodedBufferTest, ClearAndStrip) {
+ string data = STRING_FIELD_0 + VARINT_FIELD_1;
+ writeToFdBuffer(data);
+ Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+ EncodedBuffer encodedBuf(buffer, create_message_privacy(300, list));
+ PrivacySpec spec1(EXPLICIT), spec2(LOCAL);
+
+ ASSERT_EQ(encodedBuf.strip(spec1), NO_ERROR);
+ assertBuffer(encodedBuf, STRING_FIELD_0);
+ ASSERT_EQ(encodedBuf.strip(spec2), NO_ERROR);
+ assertBuffer(encodedBuf, data);
+}
+
+TEST_F(EncodedBufferTest, BadDataInFdBuffer) {
+ writeToFdBuffer("iambaddata");
+ Privacy* list[] = { create_privacy(4, OTHER_TYPE, AUTOMATIC), NULL };
+ EncodedBuffer encodedBuf(buffer, create_message_privacy(300, list));
+ PrivacySpec spec;
+ ASSERT_EQ(encodedBuf.strip(spec), BAD_VALUE);
+}
+
+TEST_F(EncodedBufferTest, BadDataInNestedMessage) {
+ writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5 + "aoeoe");
+ Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+ Privacy* field5[] = { create_message_privacy(5, list), NULL };
+ EncodedBuffer encodedBuf(buffer, create_message_privacy(300, field5));
+ PrivacySpec spec;
+ ASSERT_EQ(encodedBuf.strip(spec), BAD_VALUE);
+}
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index 403a2abf670a..d1436b2cc36f 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -15,10 +15,11 @@
#define LOG_TAG "incidentd"
#include "FdBuffer.h"
+#include "io_util.h"
#include <android-base/file.h>
#include <android-base/test_utils.h>
-#include <gmock/gmock.h>
+#include <fcntl.h>
#include <gtest/gtest.h>
#include <signal.h>
#include <string.h>
@@ -30,10 +31,7 @@ const std::string HEAD = "[OK]";
using namespace android;
using namespace android::base;
-using ::testing::StrEq;
using ::testing::Test;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStdout;
class FdBufferTest : public Test {
public:
@@ -50,12 +48,13 @@ public:
}
void AssertBufferContent(const char* expected) {
- ReportRequestSet requests;
- requests.setMainFd(STDOUT_FILENO);
-
- CaptureStdout();
- ASSERT_EQ(NO_ERROR, buffer.write(&requests));
- EXPECT_THAT(GetCapturedStdout(), StrEq(expected));
+ int i=0;
+ FdBuffer::iterator it = buffer.begin();
+ while (expected[i] != '\0') {
+ ASSERT_EQ(*it, expected[i++]);
+ it++;
+ }
+ ASSERT_EQ(it, buffer.end());
}
bool DoDataStream(int rFd, int wFd) {
@@ -99,6 +98,16 @@ TEST_F(FdBufferTest, IterateEmpty) {
EXPECT_TRUE(it.outOfBound());
}
+TEST_F(FdBufferTest, IteratorSnapshot) {
+ FdBuffer::iterator it = buffer.begin();
+ it += 4;
+ FdBuffer::iterator snapshot = it.snapshot();
+ it += 5;
+ EXPECT_TRUE(snapshot != it);
+ EXPECT_EQ(it - snapshot, 5);
+ EXPECT_EQ(snapshot - it, -5);
+}
+
TEST_F(FdBufferTest, ReadAndIterate) {
std::string testdata = "FdBuffer test string";
ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
@@ -227,7 +236,7 @@ TEST_F(FdBufferTest, ReadInStreamEmpty) {
TEST_F(FdBufferTest, ReadInStreamMoreThan4MB) {
const std::string testFile = kTestDataPath + "morethan4MB.txt";
size_t fourMB = (size_t) 4 * 1024 * 1024;
- int fd = open(testFile.c_str(), O_RDONLY);
+ int fd = open(testFile.c_str(), O_RDONLY | O_CLOEXEC);
ASSERT_NE(fd, -1);
int pid = fork();
ASSERT_TRUE(pid != -1);
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
index a77474199d27..5d074bcb0e4c 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -76,8 +76,7 @@ public:
};
protected:
- IBinder* onAsBinder() override { return nullptr; };
-
+ virtual IBinder* onAsBinder() override { return nullptr; };
};
class ReporterTest : public Test {
@@ -127,29 +126,7 @@ TEST_F(ReporterTest, IncidentReportArgs) {
TEST_F(ReporterTest, ReportRequestSetEmpty) {
requests.setMainFd(STDOUT_FILENO);
-
- CaptureStdout();
- requests.write((uint8_t *) "abcdef", 6);
- EXPECT_THAT(GetCapturedStdout(), StrEq("abcdef"));
-}
-
-TEST_F(ReporterTest, WriteToStreamFdAndMainFd) {
- TemporaryFile tf;
- IncidentReportArgs args;
- sp<ReportRequest> r = new ReportRequest(args, l, tf.fd);
-
- requests.add(r);
- requests.setMainFd(STDOUT_FILENO);
-
- const char* data = "abcdef";
-
- CaptureStdout();
- requests.write((uint8_t *) data, 6);
- EXPECT_THAT(GetCapturedStdout(), StrEq(data));
-
- string content;
- ASSERT_TRUE(ReadFileToString(tf.path, &content));
- EXPECT_THAT(content, StrEq(data));
+ ASSERT_EQ(requests.mainFd(), STDOUT_FILENO);
}
TEST_F(ReporterTest, RunReportEmpty) {
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 93771ff30b64..25b05b2b1518 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -22,31 +22,80 @@
#include <gtest/gtest.h>
#include <string.h>
+const int TIMEOUT_PARSER = -1;
+const int NOOP_PARSER = 0;
+const int REVERSE_PARSER = 1;
+
const int QUICK_TIMEOUT_MS = 100;
+const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
+const string STRING_FIELD_2 = "\x12\vwhatthefuck";
+const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
+
using namespace android::base;
+using namespace android::binder;
using namespace std;
using ::testing::StrEq;
using ::testing::internal::CaptureStdout;
using ::testing::internal::GetCapturedStdout;
// NOTICE: this test requires /system/bin/incident_helper is installed.
-TEST(SectionTest, WriteHeader) {
- int id = 13; // expect output is 13 << 3 & 2 = 106 --> \x6a in ASCII
- FileSection s(id, ""); // ignore the path, just used to test the header
+
+class SimpleListener : public IIncidentReportStatusListener
+{
+public:
+ SimpleListener() {};
+ virtual ~SimpleListener() {};
+
+ virtual Status onReportStarted() { return Status::ok(); };
+ virtual Status onReportSectionStatus(int /*section*/, int /*status*/) { return Status::ok(); };
+ virtual Status onReportFinished() { return Status::ok(); };
+ virtual Status onReportFailed() { return Status::ok(); };
+
+protected:
+ virtual IBinder* onAsBinder() override { return nullptr; };
+};
+
+TEST(SectionTest, HeaderSection) {
+ TemporaryFile output2;
+ HeaderSection hs;
ReportRequestSet requests;
+ IncidentReportArgs args1, args2;
+ args1.addSection(1);
+ args1.addSection(2);
+ args2.setAll(true);
+
+ vector<int8_t> head1;
+ head1.push_back('a');
+ head1.push_back('x');
+ head1.push_back('e');
+
+ vector<int8_t> head2;
+ head2.push_back('p');
+ head2.push_back('u');
+ head2.push_back('p');
+
+ args1.addHeader(head1);
+ args1.addHeader(head2);
+ args2.addHeader(head2);
+
+ requests.add(new ReportRequest(args1, new SimpleListener(), -1));
+ requests.add(new ReportRequest(args2, new SimpleListener(), output2.fd));
requests.setMainFd(STDOUT_FILENO);
+ string content;
CaptureStdout();
- ASSERT_EQ(NO_ERROR, s.WriteHeader(&requests, 300));
- // According to protobuf encoding, 300 is "1010 1100 0000 0010" -> \xac \x02
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x6a\xac\x02"));
+ ASSERT_EQ(NO_ERROR, hs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x3" "axe\n\x03pup"));
+
+ EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_THAT(content, StrEq("\n\x03pup"));
}
TEST(SectionTest, FileSection) {
TemporaryFile tf;
- FileSection fs(0, tf.path);
+ FileSection fs(REVERSE_PARSER, tf.path);
ReportRequestSet requests;
ASSERT_TRUE(tf.fd != -1);
@@ -58,13 +107,13 @@ TEST(SectionTest, FileSection) {
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
// The input string is reversed in incident helper
// The length is 11, in 128Varint it is "0000 1011" -> \v
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\vatadtsetmai"));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\vatadtsetmai"));
}
TEST(SectionTest, FileSectionTimeout) {
TemporaryFile tf;
// id -1 is timeout parser
- FileSection fs(-1, tf.path, QUICK_TIMEOUT_MS);
+ FileSection fs(TIMEOUT_PARSER, tf.path, QUICK_TIMEOUT_MS);
ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
}
@@ -84,36 +133,165 @@ TEST(SectionTest, CommandSectionConstructor) {
}
TEST(SectionTest, CommandSectionEcho) {
- CommandSection cs(0, "/system/bin/echo", "about", NULL);
+ CommandSection cs(REVERSE_PARSER, "/system/bin/echo", "about", NULL);
ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\x06\ntuoba"));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\x06\ntuoba"));
}
TEST(SectionTest, CommandSectionCommandTimeout) {
- CommandSection cs(0, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
+ CommandSection cs(NOOP_PARSER, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
TEST(SectionTest, CommandSectionIncidentHelperTimeout) {
- CommandSection cs(-1, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
+ CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
TEST(SectionTest, CommandSectionBadCommand) {
- CommandSection cs(0, "echo", "about", NULL);
+ CommandSection cs(NOOP_PARSER, "echo", "about", NULL);
ReportRequestSet requests;
ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
}
TEST(SectionTest, CommandSectionBadCommandAndTimeout) {
- CommandSection cs(-1, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
+ CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
ReportRequestSet requests;
// timeout will return first
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, TestFilterPiiTaggedFields) {
+ TemporaryFile tf;
+ FileSection fs(NOOP_PARSER, tf.path);
+ ReportRequestSet requests;
+
+ ASSERT_TRUE(tf.fd != -1);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path, false));
+
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+}
+
+TEST(SectionTest, TestBadFdRequest) {
+ TemporaryFile input;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args;
+ args.setAll(true);
+ args.setDest(0);
+ sp<ReportRequest> badFdRequest = new ReportRequest(args, new SimpleListener(), 1234567);
+ requests.add(badFdRequest);
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+ EXPECT_EQ(badFdRequest->err, -EBADF);
+}
+
+TEST(SectionTest, TestBadRequests) {
+ TemporaryFile input;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args;
+ args.setAll(true);
+ args.setDest(0);
+ requests.add(new ReportRequest(args, new SimpleListener(), -1));
+ EXPECT_EQ(fs.Execute(&requests), -EBADF);
+}
+
+TEST(SectionTest, TestMultipleRequests) {
+ TemporaryFile input, output1, output2, output3;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+
+ ASSERT_TRUE(input.fd != -1);
+ ASSERT_TRUE(output1.fd != -1);
+ ASSERT_TRUE(output2.fd != -1);
+ ASSERT_TRUE(output3.fd != -1);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args1, args2, args3;
+ args1.setAll(true);
+ args1.setDest(0); // LOCAL
+ args2.setAll(true); // default to explicit
+ sp<SimpleListener> l = new SimpleListener();
+ requests.add(new ReportRequest(args1, l, output1.fd));
+ requests.add(new ReportRequest(args2, l, output2.fd));
+ requests.add(new ReportRequest(args3, l, output3.fd));
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+
+ string content, expect;
+ expect = VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3;
+ char c = (char) expect.size();
+ EXPECT_TRUE(ReadFileToString(output1.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+
+ expect = STRING_FIELD_2 + FIX64_FIELD_3;
+ c = (char) expect.size();
+ EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+
+ // because args3 doesn't set section, so it should receive nothing
+ EXPECT_TRUE(ReadFileToString(output3.path, &content));
+ EXPECT_THAT(content, StrEq(""));
+}
+
+TEST(SectionTest, TestMultipleRequestsBySpec) {
+ TemporaryFile input, output1, output2, output3;
+ FileSection fs(NOOP_PARSER, input.path);
+ ReportRequestSet requests;
+
+ ASSERT_TRUE(input.fd != -1);
+ ASSERT_TRUE(output1.fd != -1);
+ ASSERT_TRUE(output2.fd != -1);
+ ASSERT_TRUE(output3.fd != -1);
+
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+
+ IncidentReportArgs args1, args2, args3, args4;
+ args1.setAll(true);
+ args2.setAll(true);
+ args4.setAll(true);
+ sp<SimpleListener> l = new SimpleListener();
+ requests.add(new ReportRequest(args1, l, output1.fd));
+ requests.add(new ReportRequest(args2, l, output2.fd));
+ requests.add(new ReportRequest(args3, l, output3.fd));
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
+
+ string content, expect;
+ expect = STRING_FIELD_2 + FIX64_FIELD_3;
+ char c = (char) expect.size();
+
+ // output1 and output2 are the same
+ EXPECT_TRUE(ReadFileToString(output1.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+ EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_THAT(content, StrEq(string("\x02") + c + expect));
+
+ // because args3 doesn't set section, so it should receive nothing
+ EXPECT_TRUE(ReadFileToString(output3.path, &content));
+ EXPECT_THAT(content, StrEq(""));
} \ No newline at end of file
diff --git a/cmds/incidentd/tests/section_list.cpp b/cmds/incidentd/tests/section_list.cpp
index f0053355bd24..e47b61cf3854 100644
--- a/cmds/incidentd/tests/section_list.cpp
+++ b/cmds/incidentd/tests/section_list.cpp
@@ -4,3 +4,25 @@
const Section* SECTION_LIST[] = {
NULL
};
+
+const uint8_t LOCAL = 0;
+const uint8_t EXPLICIT = 1;
+const uint8_t AUTOMATIC = 2;
+
+Privacy sub_field_1 { 1, 1, NULL, LOCAL, NULL };
+Privacy sub_field_2 { 2, 9, NULL, AUTOMATIC, NULL };
+
+Privacy* list[] = {
+ &sub_field_1,
+ &sub_field_2,
+ NULL };
+
+Privacy field_0 { 0, 11, list, EXPLICIT, NULL };
+Privacy field_1 { 1, 9, NULL, AUTOMATIC, NULL };
+
+const Privacy* PRIVACY_POLICY_LIST[] = {
+ &field_0,
+ &field_1
+};
+
+const int PRIVACY_POLICY_COUNT = 2; \ No newline at end of file
diff --git a/cmds/interrupter/Android.bp b/cmds/interrupter/Android.bp
new file mode 100644
index 000000000000..d68e7fe37535
--- /dev/null
+++ b/cmds/interrupter/Android.bp
@@ -0,0 +1,11 @@
+cc_library_shared {
+ name: "interrupter",
+ host_supported: true,
+ srcs: ["interrupter.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/cmds/interrupter/Android.mk b/cmds/interrupter/Android.mk
deleted file mode 100644
index 97a96bfc8e25..000000000000
--- a/cmds/interrupter/Android.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- interrupter.c
-LOCAL_MODULE := interrupter
-LOCAL_MODULE_TAGS := eng tests
-LOCAL_LDFLAGS := -ldl
-LOCAL_CFLAGS := -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- interrupter.c
-LOCAL_MODULE := interrupter
-LOCAL_MODULE_TAGS := eng tests
-LOCAL_LDFLAGS := -ldl
-LOCAL_CFLAGS := -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_HOST_SHARED_LIBRARY)
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index ad989dee7b55..79faa1bf97a2 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -636,7 +636,7 @@ public final class Pm {
out = session.openWrite(splitName, 0, sizeBytes);
int total = 0;
- byte[] buffer = new byte[65536];
+ byte[] buffer = new byte[1024 * 1024];
int c;
while ((c = in.read(buffer)) != -1) {
total += c;
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 35f8bbb57e50..6ded24648353 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -101,9 +101,6 @@ static uint32_t dataSpaceToInt(android_dataspace d)
static status_t notifyMediaScanner(const char* fileName) {
String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
- String8 fileUrl("\"");
- fileUrl.append(fileName);
- fileUrl.append("\"");
cmd.append(fileName);
cmd.append(" > /dev/null");
int result = system(cmd.string());
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 658d662de5e1..a9a4118a8e98 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -16,6 +16,10 @@
package com.android.commands.sm;
+import static android.os.storage.StorageManager.PROP_ADOPTABLE_FBE;
+import static android.os.storage.StorageManager.PROP_HAS_ADOPTABLE;
+import static android.os.storage.StorageManager.PROP_VIRTUAL_DISK;
+
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -134,7 +138,15 @@ public final class Sm {
}
public void runHasAdoptable() {
- System.out.println(SystemProperties.getBoolean(StorageManager.PROP_HAS_ADOPTABLE, false));
+ final boolean hasHardware = SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false)
+ || SystemProperties.getBoolean(PROP_VIRTUAL_DISK, false);
+ final boolean hasSoftware;
+ if (StorageManager.isFileEncryptedNativeOnly()) {
+ hasSoftware = SystemProperties.getBoolean(PROP_ADOPTABLE_FBE, false);
+ } else {
+ hasSoftware = true;
+ }
+ System.out.println(hasHardware && hasSoftware);
}
public void runGetPrimaryStorageUuid() throws RemoteException {
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
new file mode 100644
index 000000000000..b9ee7ff201d5
--- /dev/null
+++ b/cmds/statsd/Android.mk
@@ -0,0 +1,143 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# ================
+# proto static lib
+# ================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := statsd_proto
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-proto-files-under, src)
+
+LOCAL_PROTOC_FLAGS :=
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
+
+include $(BUILD_STATIC_LIBRARY)
+
+STATSD_PROTO_INCLUDES := $(local-generated-sources-dir)/src/$(LOCAL_PATH)
+
+# =========
+# statsd
+# =========
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := statsd
+
+LOCAL_SRC_FILES := \
+ ../../core/java/android/os/IStatsCompanionService.aidl \
+ ../../core/java/android/os/IStatsManager.aidl \
+ src/StatsService.cpp \
+ src/AnomalyMonitor.cpp \
+ src/LogEntryPrinter.cpp \
+ src/LogReader.cpp \
+ src/main.cpp \
+ src/DropboxWriter.cpp \
+ src/parse_util.cpp \
+ src/StatsLogProcessor.cpp \
+ src/stats_log.proto \
+ src/statsd_config.proto \
+ src/stats_constants.proto \
+ src/DropboxReader.cpp \
+
+
+LOCAL_CFLAGS += \
+ -Wall \
+ -Werror \
+ -Wno-missing-field-initializers \
+ -Wno-unused-variable \
+ -Wno-unused-function \
+ -Wno-unused-parameter
+
+ifeq (debug,)
+ LOCAL_CFLAGS += \
+ -g -O0
+else
+ # optimize for size (protobuf glop can get big)
+ LOCAL_CFLAGS += \
+ -Os
+endif
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/src \
+ STATSD_PROTO_INCLUDES
+
+LOCAL_STATIC_LIBRARIES := statsd_proto
+
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ libbinder \
+ libcutils \
+ libincident \
+ liblog \
+ libselinux \
+ libutils \
+ libservices \
+ libandroidfw \
+ libprotobuf-cpp-lite \
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+#LOCAL_INIT_RC := statsd.rc
+
+include $(BUILD_EXECUTABLE)
+
+# ==============
+# statsd_test
+# ==============
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := statsd_test
+LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS += \
+ -Wall \
+ -Werror \
+ -Wno-missing-field-initializers \
+ -Wno-unused-variable \
+ -Wno-unused-function \
+ -Wno-unused-parameter
+
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/src \
+ STATSD_PROTO_INCLUDES
+
+LOCAL_SRC_FILES := \
+ ../../core/java/android/os/IStatsCompanionService.aidl \
+ ../../core/java/android/os/IStatsManager.aidl \
+ src/StatsService.cpp \
+ tests/indexed_priority_queue_test.cpp \
+ src/LogEntryPrinter.cpp \
+ src/LogReader.cpp \
+ tests/LogReader_test.cpp \
+
+LOCAL_STATIC_LIBRARIES := \
+ libgmock \
+ statsd_proto
+
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ libbinder \
+ libcutils \
+ liblog \
+ libselinux \
+ libutils \
+ libprotobuf-cpp-lite \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/cmds/statsd/src/AnomalyMonitor.cpp b/cmds/statsd/src/AnomalyMonitor.cpp
new file mode 100644
index 000000000000..2d3454a831f9
--- /dev/null
+++ b/cmds/statsd/src/AnomalyMonitor.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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 LOG_TAG "AnomalyMonitor"
+#define DEBUG true
+
+#include "AnomalyMonitor.h"
+
+#include <cutils/log.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AnomalyMonitor::AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec)
+ : mRegisteredAlarmTimeSec(0),
+ mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec) {
+}
+
+AnomalyMonitor::~AnomalyMonitor() {
+}
+
+void AnomalyMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+ std::lock_guard<std::mutex> lock(mLock);
+ sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+ mStatsCompanionService = statsCompanionService;
+ if (statsCompanionService == nullptr) {
+ if (DEBUG) ALOGD("Erasing link to statsCompanionService");
+ return;
+ }
+ if (DEBUG) ALOGD("Creating link to statsCompanionService");
+ const sp<const AnomalyAlarm> top = mPq.top();
+ if (top != nullptr) {
+ updateRegisteredAlarmTime_l(top->timestampSec);
+ }
+}
+
+void AnomalyMonitor::add(sp<const AnomalyAlarm> alarm) {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (alarm == nullptr) {
+ ALOGW("Asked to add a null alarm.");
+ return;
+ }
+ if (alarm->timestampSec < 1) {
+ // forbidden since a timestamp 0 is used to indicate no alarm registered
+ ALOGW("Asked to add a 0-time alarm.");
+ return;
+ }
+ // TODO: Ensure that refractory period is respected.
+ if (DEBUG) ALOGD("Adding alarm with time %u", alarm->timestampSec);
+ mPq.push(alarm);
+ if (mRegisteredAlarmTimeSec < 1 ||
+ alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) {
+ updateRegisteredAlarmTime_l(alarm->timestampSec);
+ }
+}
+
+void AnomalyMonitor::remove(sp<const AnomalyAlarm> alarm) {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (alarm == nullptr) {
+ ALOGW("Asked to remove a null alarm.");
+ return;
+ }
+ if (DEBUG) ALOGD("Removing alarm with time %u", alarm->timestampSec);
+ mPq.remove(alarm);
+ if (mPq.empty()) {
+ if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
+ mRegisteredAlarmTimeSec = 0;
+ if (mStatsCompanionService != nullptr) {
+ mStatsCompanionService->cancelAnomalyAlarm();
+ }
+ return;
+ }
+ uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec;
+ if (DEBUG) ALOGD("Soonest alarm is %u", soonestAlarmTimeSec);
+ if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) {
+ updateRegisteredAlarmTime_l(soonestAlarmTimeSec);
+ }
+}
+
+void AnomalyMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) {
+ if (DEBUG) ALOGD("Updating reg alarm time to %u", timestampSec);
+ mRegisteredAlarmTimeSec = timestampSec;
+ if (mStatsCompanionService != nullptr) {
+ mStatsCompanionService->setAnomalyAlarm(secToMs(mRegisteredAlarmTimeSec));
+ }
+}
+
+int64_t AnomalyMonitor::secToMs(uint32_t timeSec) {
+ return ((int64_t) timeSec) * 1000;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/AnomalyMonitor.h b/cmds/statsd/src/AnomalyMonitor.h
new file mode 100644
index 000000000000..e89afa8a6497
--- /dev/null
+++ b/cmds/statsd/src/AnomalyMonitor.h
@@ -0,0 +1,138 @@
+/*
+ * 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 ANOMALY_MONITOR_H
+#define ANOMALY_MONITOR_H
+
+#include <indexed_priority_queue.h>
+#include <android/os/IStatsCompanionService.h>
+#include <utils/RefBase.h>
+
+#include <queue>
+#include <vector>
+
+using namespace android;
+
+using android::os::IStatsCompanionService;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Represents an alarm, associated with some aggregate metric, holding a
+ * projected time at which the metric is expected to exceed its anomaly
+ * threshold.
+ * Timestamps are in seconds since epoch in a uint32, so will fail in year 2106.
+ */
+struct AnomalyAlarm : public RefBase {
+ AnomalyAlarm(uint32_t timestampSec) : timestampSec(timestampSec) {
+ }
+
+ const uint32_t timestampSec;
+
+ /** AnomalyAlarm a is smaller (higher priority) than b if its timestamp is sooner. */
+ struct SmallerTimestamp {
+ bool operator()(sp<const AnomalyAlarm> a, sp<const AnomalyAlarm> b) const {
+ return (a->timestampSec < b->timestampSec);
+ }
+ };
+};
+
+/**
+ * Manages alarms for Anomaly Detection.
+ */
+class AnomalyMonitor : public RefBase {
+ public:
+ /**
+ * @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs
+ * from the registered alarm by more than this amount, update the registered
+ * alarm.
+ */
+ AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec);
+ ~AnomalyMonitor();
+
+ /**
+ * Tells AnomalyMonitor what IStatsCompanionService to use and, if
+ * applicable, immediately registers an existing alarm with it.
+ * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
+ * update IStatsCompanionService (until such time as it is set non-null).
+ */
+ void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+
+ /**
+ * Adds the given alarm (reference) to the queue.
+ */
+ void add(sp<const AnomalyAlarm> alarm);
+
+ /**
+ * Removes the given alarm (reference) from the queue.
+ * Note that alarm comparison is reference-based; if another alarm exists
+ * with the same timestampSec, that alarm will still remain in the queue.
+ */
+ void remove(sp<const AnomalyAlarm> alarm);
+
+ /**
+ * Returns the projected alarm timestamp that is registered with
+ * StatsCompanionService. This may not be equal to the soonest alarm,
+ * but should be within minDiffToUpdateRegisteredAlarmTimeSec of it.
+ */
+ uint32_t getRegisteredAlarmTimeSec() const {
+ return mRegisteredAlarmTimeSec;
+ }
+
+ private:
+ std::mutex mLock;
+
+ /**
+ * Timestamp (seconds since epoch) of the alarm registered with
+ * StatsCompanionService. This, in general, may not be equal to the soonest
+ * alarm stored in mPq, but should be within minUpdateTimeSec of it.
+ * A value of 0 indicates that no alarm is currently registered.
+ */
+ uint32_t mRegisteredAlarmTimeSec;
+
+ /**
+ * Priority queue of alarms, prioritized by soonest alarm.timestampSec.
+ */
+ indexed_priority_queue<AnomalyAlarm, AnomalyAlarm::SmallerTimestamp> mPq;
+
+ /**
+ * Binder interface for communicating with StatsCompanionService.
+ */
+ sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+ /**
+ * Amount by which the soonest projected alarm must differ from
+ * mRegisteredAlarmTimeSec before updateRegisteredAlarmTime_l is called.
+ */
+ uint32_t mMinUpdateTimeSec;
+
+ /**
+ * Updates the alarm registered with StatsCompanionService to the given time.
+ * Also correspondingly updates mRegisteredAlarmTimeSec.
+ */
+ void updateRegisteredAlarmTime_l(uint32_t timestampSec);
+
+ /** Converts uint32 timestamp in seconds to a Java long in msec. */
+ int64_t secToMs(uint32_t timeSec);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // ANOMALY_MONITOR_H \ No newline at end of file
diff --git a/cmds/statsd/src/DropboxReader.cpp b/cmds/statsd/src/DropboxReader.cpp
new file mode 100644
index 000000000000..307e7712e5aa
--- /dev/null
+++ b/cmds/statsd/src/DropboxReader.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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 <android/os/DropBoxManager.h>
+#include <android-base/file.h>
+#include <androidfw/ZipUtils.h>
+
+#include "DropboxReader.h"
+
+using android::sp;
+using android::String16;
+using android::binder::Status;
+using android::base::unique_fd;
+using android::os::DropBoxManager;
+using android::ZipUtils;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+status_t DropboxReader::readStatsLogs(FILE* out, const string& tag, long msec) {
+ sp<DropBoxManager> dropbox = new DropBoxManager();
+ StatsLogReport logReport;
+
+ long timestamp = msec;
+ // instead of while(true), put a hard limit 1000. Dropbox won't have more than 1000 files.
+ for(int i = 0; i < 1000; i++ ) {
+ DropBoxManager::Entry entry;
+ Status status = dropbox->getNextEntry(String16(tag.c_str()),
+ timestamp, &entry);
+ if (!status.isOk()) {
+ ALOGD("No more entries, or failed to read. We can't tell unfortunately.");
+ return android::OK;
+ }
+
+ const unique_fd& fd = entry.getFd();
+
+ // use this timestamp for next query.
+ timestamp = entry.getTimestamp();
+
+ if (entry.getFlags() & DropBoxManager::IS_GZIPPED) {
+ if (!parseFromGzipFile(fd, logReport)) {
+ // Failed to parse from the file. Continue to fetch the next entry.
+ continue;
+ }
+ } else {
+ if (!parseFromFile(fd, logReport)) {
+ // Failed to parse from the file. Continue to fetch the next entry.
+ continue;
+ }
+ }
+
+ printLog(out, logReport);
+ }
+ return android::OK;
+}
+
+bool DropboxReader::parseFromGzipFile(const unique_fd& fd, StatsLogReport& logReport) {
+ FILE *file = fdopen(fd, "r");
+ bool result = false;
+ bool scanResult;
+ int method;
+ long compressedLen;
+ long uncompressedLen;
+ unsigned long crc32;
+ scanResult = ZipUtils::examineGzip(file, &method, &uncompressedLen,
+ &compressedLen, &crc32);
+ if (scanResult && method == kCompressDeflated) {
+ vector<uint8_t> buf(uncompressedLen);
+ if (ZipUtils::inflateToBuffer(file, &buf[0], uncompressedLen, compressedLen)) {
+ if (logReport.ParseFromArray(&buf[0], uncompressedLen)) {
+ result = true;
+ }
+ }
+ } else {
+ ALOGE("This isn't a valid deflated gzip file");
+ }
+ fclose(file);
+ return result;
+}
+
+// parse a non zipped file.
+bool DropboxReader::parseFromFile(const unique_fd& fd, StatsLogReport& logReport) {
+ string content;
+ if (!android::base::ReadFdToString(fd, &content)) {
+ ALOGE("Failed to read file");
+ return false;
+ }
+ if (!logReport.ParseFromString(content)) {
+ ALOGE("failed to parse log entry from data");
+ return false;
+ }
+ return true;
+}
+
+void DropboxReader::printLog(FILE* out, const StatsLogReport& logReport) {
+ fprintf(out, "start_time_msec=%lld, end_time_msec=%lld, ",
+ logReport.start_report_millis(), logReport.end_report_millis());
+ for (int i = 0; i < logReport.event_metrics().data_size(); i++) {
+ EventMetricData eventMetricData = logReport.event_metrics().data(i);
+ for (int j = 0; j < eventMetricData.key_value_pair_size(); j++) {
+ fprintf(out, "key=%d, ", eventMetricData.key_value_pair(j).key());
+ fprintf(out, "value_str=%s ", eventMetricData.key_value_pair(j).value_str().c_str());
+ fprintf(out, "value_int=%lld ", eventMetricData.key_value_pair(j).value_int());
+ fprintf(out, "value_float=%f ", eventMetricData.key_value_pair(j).value_float());
+ }
+ }
+ fprintf(out, "\n");
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/DropboxReader.h b/cmds/statsd/src/DropboxReader.h
new file mode 100644
index 000000000000..b7f8739c9ad6
--- /dev/null
+++ b/cmds/statsd/src/DropboxReader.h
@@ -0,0 +1,52 @@
+/*
+ * 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 DROPBOX_READER_H
+#define DROPBOX_READER_H
+
+#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
+
+#include <stdint.h>
+#include <stdio.h>
+
+using android::base::unique_fd;
+using android::status_t;
+using std::string;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class DropboxReader {
+public:
+ // msec is the start timestamp.
+ static status_t readStatsLogs(FILE* out, const string& tag, long msec);
+
+private:
+ static bool parseFromFile(const unique_fd& fd, StatsLogReport& logReport);
+ static bool parseFromGzipFile(const unique_fd& fd, StatsLogReport& logReport);
+ static void printLog(FILE* out, const StatsLogReport& logReport);
+ enum {
+ kCompressStored = 0, // no compression
+ kCompressDeflated = 8, // standard deflate
+ };
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif //DROPBOX_READER_H
diff --git a/cmds/statsd/src/DropboxWriter.cpp b/cmds/statsd/src/DropboxWriter.cpp
new file mode 100644
index 000000000000..b9d48fa362d5
--- /dev/null
+++ b/cmds/statsd/src/DropboxWriter.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 <android/os/DropBoxManager.h>
+
+#include "DropboxWriter.h"
+
+using android::binder::Status;
+using android::os::DropBoxManager;
+using android::sp;
+using android::String16;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DropboxWriter::DropboxWriter(const string& tag)
+ : mTag(tag), mLogReport(), mBufferSize(0) {
+}
+
+void DropboxWriter::addStatsLogReport(const StatsLogReport& log) {
+ mLogReport = log;
+ flushIfNecessary(log);
+ mBufferSize += log.ByteSize();
+}
+
+void DropboxWriter::flushIfNecessary(const StatsLogReport& log) {
+ // TODO: Decide to flush depending on the serialized size of the StatsLogReport.
+ // if (entry.ByteSize() + mBufferSize > kMaxSerializedBytes) {
+ // flush();
+ // }
+ flush();
+}
+
+void DropboxWriter::flush() {
+ // now we get an exact byte size of the output
+ const int numBytes = mLogReport.ByteSize();
+ vector<uint8_t> buffer(numBytes);
+ sp<DropBoxManager> dropbox = new DropBoxManager();
+ mLogReport.SerializeToArray(&buffer[0], numBytes);
+ Status status = dropbox->addData(String16(mTag.c_str()), &buffer[0],
+ numBytes, 0 /* no flag */);
+ if (!status.isOk()) {
+ ALOGE("failed to write to dropbox");
+ //TODO: What to do if flush fails??
+ }
+ mLogReport.Clear();
+ mBufferSize = 0;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/DropboxWriter.h b/cmds/statsd/src/DropboxWriter.h
new file mode 100644
index 000000000000..59629fb65b22
--- /dev/null
+++ b/cmds/statsd/src/DropboxWriter.h
@@ -0,0 +1,72 @@
+/*
+ * 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 DROPBOX_WRITER_H
+#define DROPBOX_WRITER_H
+
+#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
+
+using std::string;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class DropboxWriter {
+public:
+ /* tag will be part of the file name, and used as the key to build the file index inside
+ DropBoxManagerService.
+ */
+ DropboxWriter(const string& tag);
+
+ void addStatsLogReport(const StatsLogReport& log);
+
+ /* Request a flush to dropbox. */
+ void flush();
+
+private:
+ /* Max *serialized* size of the logs kept in memory before flushing to dropbox.
+ Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
+ So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
+ is higher than its serialized size. DropboxManager will compress the file when the data is
+ larger than 4KB. So the final file size is less than this number.
+ */
+ static const size_t kMaxSerializedBytes = 16 * 1024;
+
+ const string mTag;
+
+ /* Data that was captured for a single metric over a given interval of time. */
+ StatsLogReport mLogReport;
+
+ /* Current *serialized* size of the logs kept in memory.
+ To save computation, we will not calculate the size of the StatsLogReport every time when a new
+ entry is added, which would recursively call ByteSize() on every log entry. Instead, we keep
+ the sum of all individual stats log entry sizes. The size of a proto is approximately the sum
+ of the size of all member protos.
+ */
+ size_t mBufferSize = 0;
+
+ /* Check if the buffer size exceeds the max buffer size when the new entry is added, and flush
+ the logs to dropbox if true. */
+ void flushIfNecessary(const StatsLogReport& log);
+
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif //DROPBOX_WRITER_H
diff --git a/cmds/statsd/src/LogEntryPrinter.cpp b/cmds/statsd/src/LogEntryPrinter.cpp
new file mode 100644
index 000000000000..c877b0545795
--- /dev/null
+++ b/cmds/statsd/src/LogEntryPrinter.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 <LogEntryPrinter.h>
+
+#include <log/event_tag_map.h>
+#include <log/logprint.h>
+#include <utils/Errors.h>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+LogEntryPrinter::LogEntryPrinter(int out)
+ :m_out(out)
+{
+ // Initialize the EventTagMap, which is how we know the names of the numeric event tags.
+ // If this fails, we can't print well, but something will print.
+ m_tags = android_openEventTagMap(NULL);
+
+ // Printing format
+ m_format = android_log_format_new();
+ android_log_setPrintFormat(m_format, FORMAT_THREADTIME);
+}
+
+LogEntryPrinter::~LogEntryPrinter()
+{
+ if (m_tags != NULL) {
+ android_closeEventTagMap(m_tags);
+ }
+ android_log_format_free(m_format);
+}
+
+void
+LogEntryPrinter::OnLogEvent(const log_msg& msg)
+{
+ status_t err;
+ AndroidLogEntry entry;
+ char buf[1024];
+
+ err = android_log_processBinaryLogBuffer(&(const_cast<log_msg*>(&msg)->entry_v1),
+ &entry, m_tags, buf, sizeof(buf));
+ if (err == NO_ERROR) {
+ android_log_printLogLine(m_format, m_out, &entry);
+ } else {
+ printf("log entry: %s\n", buf);
+ fflush(stdout);
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
diff --git a/cmds/statsd/src/LogEntryPrinter.h b/cmds/statsd/src/LogEntryPrinter.h
new file mode 100644
index 000000000000..ed720dcd17ac
--- /dev/null
+++ b/cmds/statsd/src/LogEntryPrinter.h
@@ -0,0 +1,62 @@
+/*
+ * 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 LOG_ENTRY_PRINTER_H
+#define LOG_ENTRY_PRINTER_H
+
+#include "LogReader.h"
+
+#include <log/logprint.h>
+
+#include <stdio.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Decodes the log entry and prints it to the supplied file descriptor.
+ */
+class LogEntryPrinter : public LogListener
+{
+public:
+ LogEntryPrinter(int out);
+ virtual ~LogEntryPrinter();
+
+ virtual void OnLogEvent(const log_msg& msg);
+
+private:
+ /**
+ * Where to write to.
+ */
+ int m_out;
+
+ /**
+ * Numeric to string tag name mapping.
+ */
+ EventTagMap* m_tags;
+
+ /**
+ * Pretty printing format.
+ */
+ AndroidLogFormat* m_format;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // LOG_ENTRY_PRINTER_H
diff --git a/cmds/statsd/src/LogReader.cpp b/cmds/statsd/src/LogReader.cpp
new file mode 100644
index 000000000000..c9164f914cd2
--- /dev/null
+++ b/cmds/statsd/src/LogReader.cpp
@@ -0,0 +1,150 @@
+/*
+ * 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 "LogReader.h"
+
+#include <log/log_read.h>
+
+#include <utils/Errors.h>
+
+#include <time.h>
+#include <unistd.h>
+
+using namespace android;
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#define SNOOZE_INITIAL_MS 100
+#define SNOOZE_MAX_MS (10 * 60 * 1000) // Ten minutes
+
+
+// ================================================================================
+LogListener::LogListener()
+{
+}
+
+LogListener::~LogListener()
+{
+}
+
+
+// ================================================================================
+LogReader::LogReader()
+{
+}
+
+LogReader::~LogReader()
+{
+}
+
+void
+LogReader::AddListener(const sp<LogListener>& listener)
+{
+ m_listeners.push_back(listener);
+}
+
+void
+LogReader::Run()
+{
+ int nextSnoozeMs = SNOOZE_INITIAL_MS;
+
+ // In an ideal world, this outer loop will only ever run one iteration, but it
+ // exists to handle crashes in logd. The inner loop inside connect_and_read()
+ // reads from logd forever, but if that read fails, we fall out to the outer
+ // loop, do the backoff (resetting the backoff timeout if we successfully read
+ // something), and then try again.
+ while (true) {
+ // Connect and read
+ int lineCount = connect_and_read();
+
+ // Figure out how long to sleep.
+ if (lineCount > 0) {
+ // If we managed to read at least one line, reset the backoff
+ nextSnoozeMs = SNOOZE_INITIAL_MS;
+ } else {
+ // Otherwise, expontial backoff
+ nextSnoozeMs *= 1.5f;
+ if (nextSnoozeMs > 10 * 60 * 1000) {
+ // Don't wait for toooo long.
+ nextSnoozeMs = SNOOZE_MAX_MS;
+ }
+ }
+
+ // Sleep
+ timespec ts;
+ timespec rem;
+ ts.tv_sec = nextSnoozeMs / 1000;
+ ts.tv_nsec = (nextSnoozeMs % 1000) * 1000000L;
+ while (nanosleep(&ts, &rem) == -1) {
+ if (errno == EINTR) {
+ ts = rem;
+ }
+ // other errors are basically impossible
+ }
+ }
+}
+
+int
+LogReader::connect_and_read()
+{
+ int lineCount = 0;
+ status_t err;
+ logger_list* loggers;
+ logger* eventLogger;
+
+ // Prepare the logging context
+ loggers = android_logger_list_alloc(ANDROID_LOG_RDONLY,
+ /* don't stop after N lines */ 0,
+ /* no pid restriction */ 0);
+
+ // Open the buffer(s)
+ eventLogger = android_logger_open(loggers, LOG_ID_STATS);
+
+ // Read forever
+ if (eventLogger) {
+ while (true) {
+ log_msg msg;
+
+ // Read a message
+ err = android_logger_list_read(loggers, &msg);
+ if (err < 0) {
+ fprintf(stderr, "logcat read failure: %s\n", strerror(err));
+ break;
+ }
+
+ // Record that we read one (used above to know how to snooze).
+ lineCount++;
+
+ // Call the listeners
+ for (vector<sp<LogListener> >::iterator it = m_listeners.begin();
+ it != m_listeners.end(); it++) {
+ (*it)->OnLogEvent(msg);
+ }
+ }
+ }
+
+ // Free the logger list and close the individual loggers
+ android_logger_list_free(loggers);
+
+ return lineCount;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/LogReader.h b/cmds/statsd/src/LogReader.h
new file mode 100644
index 000000000000..4c2afe8ba43f
--- /dev/null
+++ b/cmds/statsd/src/LogReader.h
@@ -0,0 +1,88 @@
+/*
+ * 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 LOGREADER_H
+#define LOGREADER_H
+
+#include <log/log_read.h>
+#include <utils/RefBase.h>
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Callback for LogReader
+ */
+class LogListener : public virtual android::RefBase
+{
+public:
+ LogListener();
+ virtual ~LogListener();
+
+ // TODO: Rather than using log_msg, which doesn't have any real internal structure
+ // here, we should pull this out into our own LogEntry class.
+ virtual void OnLogEvent(const log_msg& msg) = 0;
+};
+
+/**
+ * Class to read logs from logd.
+ */
+class LogReader : public virtual android::RefBase
+{
+public:
+ /**
+ * Construct the LogReader with a pointer back to the StatsService
+ */
+ LogReader();
+
+ /**
+ * Destructor.
+ */
+ virtual ~LogReader();
+
+ /**
+ * Add a LogListener class.
+ */
+ void AddListener(const android::sp<LogListener>& listener);
+
+ /**
+ * Run the main LogReader loop
+ */
+ void Run();
+
+private:
+ /**
+ * List of listeners to call back on when we do get an event.
+ */
+ std::vector<android::sp<LogListener> > m_listeners;
+
+ /**
+ * Connect to a single instance of logd, and read until there's a read error.
+ * Logd can crash, exit, be killed etc.
+ *
+ * Returns the number of lines that were read.
+ */
+ int connect_and_read();
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // LOGREADER_H
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
new file mode 100644
index 000000000000..1ae23ef8af13
--- /dev/null
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 <StatsLogProcessor.h>
+
+#include <log/log_event_list.h>
+#include <utils/Errors.h>
+#include <parse_util.h>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StatsLogProcessor::StatsLogProcessor() : m_dropbox_writer("all-logs")
+{
+ // Initialize the EventTagMap, which is how we know the names of the numeric event tags.
+ // If this fails, we can't print well, but something will print.
+ m_tags = android_openEventTagMap(NULL);
+
+ // Printing format
+ m_format = android_log_format_new();
+ android_log_setPrintFormat(m_format, FORMAT_THREADTIME);
+}
+
+StatsLogProcessor::~StatsLogProcessor()
+{
+ if (m_tags != NULL) {
+ android_closeEventTagMap(m_tags);
+ }
+ android_log_format_free(m_format);
+}
+
+void
+StatsLogProcessor::OnLogEvent(const log_msg& msg)
+{
+ status_t err;
+ AndroidLogEntry entry;
+ char buf[1024];
+
+ err = android_log_processBinaryLogBuffer(&(const_cast<log_msg*>(&msg)->entry_v1),
+ &entry, m_tags, buf, sizeof(buf));
+
+ // dump all statsd logs to dropbox for now.
+ // TODO: Add filtering, aggregation, etc.
+ if (err == NO_ERROR) {
+ StatsLogReport logReport;
+ logReport.set_start_report_millis(entry.tv_sec / 1000 + entry.tv_nsec / 1000 / 1000);
+ EventMetricData *eventMetricData = logReport.mutable_event_metrics()->add_data();
+ *eventMetricData = parse(msg);
+
+ m_dropbox_writer.addStatsLogReport(logReport);
+ }
+}
+
+void
+StatsLogProcessor::UpdateConfig(const int config_source, StatsdConfig config)
+{
+ m_configs[config_source] = config;
+ ALOGD("Updated configuration for source %i", config_source);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
new file mode 100644
index 000000000000..a6d182cac47a
--- /dev/null
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -0,0 +1,61 @@
+/*
+ * 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 STATS_LOG_PROCESSOR_H
+#define STATS_LOG_PROCESSOR_H
+
+#include "parse_util.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StatsLogProcessor : public LogListener
+{
+public:
+ StatsLogProcessor();
+ virtual ~StatsLogProcessor();
+
+ virtual void OnLogEvent(const log_msg& msg);
+
+ virtual void UpdateConfig(const int config_source, StatsdConfig config);
+
+private:
+ /**
+ * Numeric to string tag name mapping.
+ */
+ EventTagMap* m_tags;
+
+ /**
+ * Pretty printing format.
+ */
+ AndroidLogFormat* m_format;
+
+ DropboxWriter m_dropbox_writer;
+
+ /**
+ * Configs that have been specified, keyed by the source. This allows us to over-ride the config
+ * from a source later.
+ */
+ std::unordered_map<int, StatsdConfig> m_configs;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif //STATS_LOG_PROCESSOR_H
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
new file mode 100644
index 000000000000..965c9b7192de
--- /dev/null
+++ b/cmds/statsd/src/StatsService.cpp
@@ -0,0 +1,292 @@
+/*
+ * 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 "statsd"
+#define DEBUG true
+
+#include "StatsService.h"
+#include "DropboxReader.h"
+
+#include <android-base/file.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <cutils/log.h>
+#include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
+#include <private/android_filesystem_config.h>
+#include <utils/Looper.h>
+#include <utils/String16.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StatsService::StatsService(const sp<Looper>& handlerLooper)
+ : mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Change this based on the config
+{
+ ALOGD("stats service constructed");
+}
+
+StatsService::~StatsService()
+{
+}
+
+status_t
+StatsService::setProcessor(const sp<StatsLogProcessor>& main_processor) {
+ m_processor = main_processor;
+ ALOGD("stats service set to processor %p", m_processor.get());
+ return NO_ERROR;
+}
+
+// Implement our own because the default binder implementation isn't
+// properly handling SHELL_COMMAND_TRANSACTION
+status_t
+StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ status_t err;
+
+ switch (code) {
+ case SHELL_COMMAND_TRANSACTION: {
+ int in = data.readFileDescriptor();
+ int out = data.readFileDescriptor();
+ int err = data.readFileDescriptor();
+ int argc = data.readInt32();
+ Vector<String8> args;
+ for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
+ args.add(String8(data.readString16()));
+ }
+ sp<IShellCallback> shellCallback = IShellCallback::asInterface(
+ data.readStrongBinder());
+ sp<IResultReceiver> resultReceiver = IResultReceiver::asInterface(
+ data.readStrongBinder());
+
+ FILE* fin = fdopen(in, "r");
+ FILE* fout = fdopen(out, "w");
+ FILE* ferr = fdopen(err, "w");
+
+ if (fin == NULL || fout == NULL || ferr == NULL) {
+ resultReceiver->send(NO_MEMORY);
+ } else {
+ err = command(fin, fout, ferr, args);
+ resultReceiver->send(err);
+ }
+
+ if (fin != NULL) {
+ fflush(fin);
+ fclose(fin);
+ }
+ if (fout != NULL) {
+ fflush(fout);
+ fclose(fout);
+ }
+ if (fout != NULL) {
+ fflush(ferr);
+ fclose(ferr);
+ }
+
+ return NO_ERROR;
+ }
+ default: {
+ return BnStatsManager::onTransact(code, data, reply, flags);
+ }
+ }
+}
+
+status_t
+StatsService::dump(int fd, const Vector<String16>& args)
+{
+ FILE* out = fdopen(fd, "w");
+ if (out == NULL) {
+ return NO_MEMORY; // the fd is already open
+ }
+
+ fprintf(out, "StatsService::dump:");
+ ALOGD("StatsService::dump:");
+ const int N = args.size();
+ for (int i=0; i<N; i++) {
+ fprintf(out, " %s", String8(args[i]).string());
+ ALOGD(" %s", String8(args[i]).string());
+ }
+ fprintf(out, "\n");
+
+ fclose(out);
+ return NO_ERROR;
+}
+
+status_t
+StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args)
+{
+ if (args.size() > 0) {
+ if (!args[0].compare(String8("print-stats-log")) && args.size() > 1) {
+ return doPrintStatsLog(out, args);
+ }
+ if (!args[0].compare(String8("config"))) {
+ return doLoadConfig(in);
+ }
+ }
+
+ printCmdHelp(out);
+ return NO_ERROR;
+}
+
+status_t
+StatsService::doLoadConfig(FILE* in)
+{
+ string content;
+ if (!android::base::ReadFdToString(fileno(in), &content)) {
+ return UNKNOWN_ERROR;
+ }
+ StatsdConfig config;
+ if (config.ParseFromString(content)) {
+ ALOGD("Config parsed from command line: %s", config.SerializeAsString().c_str());
+ m_processor->UpdateConfig(0, config);
+ return NO_ERROR;
+ } else {
+ ALOGD("Config failed to be parsed");
+ return UNKNOWN_ERROR;
+ }
+}
+
+Status
+StatsService::informAnomalyAlarmFired()
+{
+ if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired was called");
+
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(Status::EX_SECURITY,
+ "Only system uid can call informAnomalyAlarmFired");
+ }
+
+ if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
+ // TODO: check through all counters/timers and see if an anomaly has indeed occurred.
+
+ return Status::ok();
+}
+
+Status
+StatsService::informPollAlarmFired()
+{
+ if (DEBUG) ALOGD("StatsService::informPollAlarmFired was called");
+
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(Status::EX_SECURITY,
+ "Only system uid can call informPollAlarmFired");
+ }
+
+ if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
+ // TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
+
+ return Status::ok();
+}
+
+Status
+StatsService::systemRunning()
+{
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(Status::EX_SECURITY,
+ "Only system uid can call systemRunning");
+ }
+
+ // When system_server is up and running, schedule the dropbox task to run.
+ ALOGD("StatsService::systemRunning");
+
+ sayHiToStatsCompanion();
+
+ return Status::ok();
+}
+
+void
+StatsService::sayHiToStatsCompanion()
+{
+ // TODO: This method needs to be private. It is temporarily public and unsecured for testing purposes.
+ sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+ if (statsCompanion != nullptr) {
+ if (DEBUG) ALOGD("Telling statsCompanion that statsd is ready");
+ statsCompanion->statsdReady();
+ } else {
+ if (DEBUG) ALOGD("Could not access statsCompanion");
+ }
+}
+
+sp<IStatsCompanionService>
+StatsService::getStatsCompanionService() {
+ sp<IStatsCompanionService> statsCompanion = nullptr;
+ // Get statscompanion service from service manager
+ const sp<IServiceManager> sm(defaultServiceManager());
+ if (sm != nullptr) {
+ const String16 name("statscompanion");
+ statsCompanion = interface_cast<IStatsCompanionService>(sm->checkService(name));
+ if (statsCompanion == nullptr) {
+ ALOGW("statscompanion service unavailable!");
+ return nullptr;
+ }
+ }
+ return statsCompanion;
+}
+
+Status
+StatsService::statsCompanionReady()
+{
+ if (DEBUG) ALOGD("StatsService::statsCompanionReady was called");
+
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(Status::EX_SECURITY,
+ "Only system uid can call statsCompanionReady");
+ }
+
+ sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
+ if (statsCompanion == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER,
+ "statscompanion unavailable despite it contacting statsd!");
+ }
+ if (DEBUG) ALOGD("StatsService::statsCompanionReady linking to statsCompanion.");
+ IInterface::asBinder(statsCompanion)->linkToDeath(new StatsdDeathRecipient(mAnomalyMonitor));
+ mAnomalyMonitor->setStatsCompanionService(statsCompanion);
+
+ return Status::ok();
+}
+
+void
+StatsdDeathRecipient::binderDied(const wp<IBinder>& who) {
+ ALOGW("statscompanion service died");
+ mAnmlyMntr->setStatsCompanionService(nullptr);
+}
+
+status_t
+StatsService::doPrintStatsLog(FILE* out, const Vector<String8>& args) {
+ long msec = 0;
+
+ if (args.size() > 2) {
+ msec = strtol(args[2].string(), NULL, 10);
+ }
+ return DropboxReader::readStatsLogs(out, args[1].string(), msec);
+}
+
+void
+StatsService::printCmdHelp(FILE* out) {
+ fprintf(out, "Usage:\n");
+ fprintf(out, "\t print-stats-log [tag_required] [timestamp_nsec_optional]\n");
+ fprintf(out, "\t config\t Loads a new config from command-line (must be proto in wire-encoded format).\n");
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
new file mode 100644
index 000000000000..57276d2425e9
--- /dev/null
+++ b/cmds/statsd/src/StatsService.h
@@ -0,0 +1,101 @@
+/*
+ * 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 STATS_SERVICE_H
+#define STATS_SERVICE_H
+
+#include "AnomalyMonitor.h"
+#include "StatsLogProcessor.h"
+
+#include <android/os/BnStatsManager.h>
+#include <android/os/IStatsCompanionService.h>
+#include <binder/IResultReceiver.h>
+#include <binder/IShellCallback.h>
+#include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
+#include <utils/Looper.h>
+
+#include <deque>
+#include <mutex>
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace android::os;
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StatsService : public BnStatsManager {
+public:
+ StatsService(const sp<Looper>& handlerLooper);
+ virtual ~StatsService();
+
+ virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t command(FILE* in, FILE* out, FILE* err, Vector<String8>& args);
+
+ virtual Status systemRunning();
+
+ // Inform statsd that statsCompanion is ready.
+ virtual Status statsCompanionReady();
+
+ virtual Status informAnomalyAlarmFired();
+
+ virtual Status informPollAlarmFired();
+
+ virtual status_t setProcessor(const sp<StatsLogProcessor>& main_processor);
+
+ // TODO: public for testing since statsd doesn't run when system starts. Change to private later.
+ /** Inform statsCompanion that statsd is ready. */
+ virtual void sayHiToStatsCompanion();
+
+private:
+ sp<StatsLogProcessor> m_processor; // Reference to the processor for updating configs.
+
+ const sp<AnomalyMonitor> mAnomalyMonitor; // TODO: Move this to a more logical file/class
+
+ status_t doPrintStatsLog(FILE* out, const Vector<String8>& args);
+
+ void printCmdHelp(FILE* out);
+
+ status_t doLoadConfig(FILE* in);
+
+ /** Fetches the StatsCompanionService. */
+ sp<IStatsCompanionService> getStatsCompanionService();
+};
+
+// --- StatsdDeathRecipient ---
+class StatsdDeathRecipient : public IBinder::DeathRecipient {
+public:
+ StatsdDeathRecipient(sp<AnomalyMonitor> anomalyMonitor)
+ : mAnmlyMntr(anomalyMonitor) {
+ }
+
+ virtual void binderDied(const wp<IBinder>& who);
+
+private:
+ const sp<AnomalyMonitor> mAnmlyMntr;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // STATS_SERVICE_H
diff --git a/cmds/statsd/src/indexed_priority_queue.h b/cmds/statsd/src/indexed_priority_queue.h
new file mode 100644
index 000000000000..76409c07523a
--- /dev/null
+++ b/cmds/statsd/src/indexed_priority_queue.h
@@ -0,0 +1,199 @@
+/*
+ * 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 STATSD_INDEXED_PRIORITY_QUEUE_H
+#define STATSD_INDEXED_PRIORITY_QUEUE_H
+
+// ALOGE can be called from this file. If header loaded by another class, use their LOG_TAG instead.
+#ifndef LOG_TAG
+#define LOG_TAG "statsd(indexed_priority_queue)"
+#endif //LOG_TAG
+
+#include <cutils/log.h>
+#include <unordered_map>
+#include <utils/RefBase.h>
+#include <vector>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/** Defines a hash function for sp<AA>, returning the hash of the underlying pointer. */
+template <class AA>
+struct SpHash {
+ size_t operator()(const sp<const AA>& k) const {
+ return std::hash<const AA*>()(k.get());
+ }
+};
+
+/**
+ * Min priority queue for generic type AA.
+ * Unlike a regular priority queue, this class is also capable of removing interior elements.
+ * @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
+ * whether a should be closer to the top of the queue than b.
+ */
+template <class AA, class Comparator>
+class indexed_priority_queue {
+ public:
+ indexed_priority_queue();
+ /** Adds a into the priority queue. If already present or a==nullptr, does nothing. */
+ void push(sp<const AA> a);
+ /** Removes a from the priority queue. If not present or a==nullptr, does nothing. */
+ void remove(sp<const AA> a);
+ /** Removes all elements. */
+ void clear();
+ /** Returns whether priority queue contains a (not just a copy of a, but a itself). */
+ bool contains(sp<const AA> a) const;
+ /** Returns min element. Returns nullptr iff empty(). */
+ sp<const AA> top() const;
+ /** Returns number of elements in priority queue. */
+ size_t size() const { return pq.size() - 1; } // pq is 1-indexed
+ /** Returns true iff priority queue is empty. */
+ bool empty() const { return size() < 1; }
+
+ private:
+ /** Vector representing a min-heap (1-indexed, with nullptr at 0). */
+ std::vector<sp<const AA>> pq;
+ /** Mapping of each element in pq to its index in pq (i.e. the inverse of a=pq[i]). */
+ std::unordered_map<sp<const AA>, size_t, SpHash<AA>> indices;
+
+ void init();
+ void sift_up(size_t idx);
+ void sift_down(size_t idx);
+ /** Returns whether pq[idx1] is considered higher than pq[idx2], according to Comparator. */
+ bool higher(size_t idx1, size_t idx2) const;
+ void swap_indices(size_t i, size_t j);
+};
+
+// Implementation must be done in this file due to use of template.
+
+template <class AA, class Comparator>
+indexed_priority_queue<AA,Comparator>::indexed_priority_queue() {
+ init();
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA,Comparator>::push(sp<const AA> a) {
+ if (a == nullptr) return;
+ if (contains(a)) return;
+ pq.push_back(a);
+ size_t idx = size(); // index of last element since 1-indexed
+ indices.insert({a, idx});
+ sift_up(idx); // get the pq back in order
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA,Comparator>::remove(sp<const AA> a) {
+ if (a == nullptr) return;
+ if (!contains(a)) return;
+ size_t idx = indices[a];
+ if (idx >= pq.size()) {
+ ALOGE("indexed_priority_queue: Invalid index in map of indices.");
+ return;
+ }
+ if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
+ pq.pop_back();
+ indices.erase(a);
+ return;
+ }
+ // move last element (guaranteed not to be at idx) to idx, then delete a
+ sp<const AA> last_a = pq.back();
+ pq[idx] = last_a;
+ pq.pop_back();
+ indices[last_a] = idx;
+ indices.erase(a);
+
+ // get the heap back in order (since the element at idx is not in order)
+ sift_up(idx);
+ sift_down(idx);
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA,Comparator>::clear() {
+ pq.clear();
+ indices.clear();
+ init();
+}
+
+template <class AA, class Comparator>
+sp<const AA> indexed_priority_queue<AA,Comparator>::top() const {
+ if (empty()) return nullptr;
+ return pq[1];
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA,Comparator>::init() {
+ pq.push_back(nullptr); // so that pq is 1-indexed.
+ indices.insert({nullptr, 0}); // just to be consistent with pq.
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA,Comparator>::sift_up(size_t idx) {
+ while (idx > 1) {
+ size_t parent = idx/2;
+ if (higher(idx, parent)) swap_indices(idx, parent);
+ else break;
+ idx = parent;
+ }
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA,Comparator>::sift_down(size_t idx) {
+ while (2*idx <= size()) {
+ size_t child = 2 * idx;
+ if (child < size() && higher(child+1, child)) child++;
+ if (higher(child, idx)) swap_indices(child, idx);
+ else break;
+ idx = child;
+ }
+}
+
+template <class AA, class Comparator>
+bool indexed_priority_queue<AA,Comparator>::higher(size_t idx1, size_t idx2) const {
+ if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
+ ALOGE("indexed_priority_queue: Attempting to access invalid index");
+ return false; // got to do something.
+ }
+ return Comparator()(pq[idx1], pq[idx2]);
+}
+
+template <class AA, class Comparator>
+bool indexed_priority_queue<AA,Comparator>::contains(sp<const AA> a) const {
+ if (a == nullptr) return false; // publicly, we pretend that nullptr is not actually in pq.
+ return indices.count(a) > 0;
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA,Comparator>::swap_indices(size_t i, size_t j) {
+ if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
+ ALOGE("indexed_priority_queue: Attempting to swap invalid index");
+ return;
+ }
+ sp<const AA> val_i = pq[i];
+ sp<const AA> val_j = pq[j];
+ pq[i] = val_j;
+ pq[j] = val_i;
+ indices[val_i] = j;
+ indices[val_j] = i;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif //STATSD_INDEXED_PRIORITY_QUEUE_H
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
new file mode 100644
index 000000000000..c1dad4f1ed3f
--- /dev/null
+++ b/cmds/statsd/src/main.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "statsd"
+
+#include "LogEntryPrinter.h"
+#include "LogReader.h"
+#include "StatsService.h"
+#include "StatsLogProcessor.h"
+
+#include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <binder/Status.h>
+#include <cutils/log.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+using namespace android;
+using namespace android::os::statsd;
+
+// ================================================================================
+/**
+ * Thread function data.
+ */
+struct log_reader_thread_data {
+ sp<StatsService> service;
+};
+
+/**
+ * Thread func for where the log reader runs.
+ */
+static void*
+log_reader_thread_func(void* cookie)
+{
+ log_reader_thread_data* data = static_cast<log_reader_thread_data*>(cookie);
+
+ sp<LogReader> reader = new LogReader();
+
+ // Put the printer one first, so it will print before the real ones.
+ reader->AddListener(new LogEntryPrinter(STDOUT_FILENO));
+ sp<StatsLogProcessor> main_processor = new StatsLogProcessor();
+ data->service->setProcessor(main_processor);
+ reader->AddListener(main_processor);
+
+ // TODO: Construct and add real LogListners here.
+
+ reader->Run();
+
+ ALOGW("statsd LogReader.Run() is not supposed to return.");
+
+ delete data;
+ return NULL;
+}
+
+/**
+ * Creates and starts the thread to own the LogReader.
+ */
+static status_t
+start_log_reader_thread(const sp<StatsService>& service)
+{
+ status_t err;
+ pthread_attr_t attr;
+ pthread_t thread;
+
+ // Thread data.
+ log_reader_thread_data* data = new log_reader_thread_data();
+ data->service = service;
+
+ // Create the thread
+ err = pthread_attr_init(&attr);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ // TODO: Do we need to tweak thread priority?
+ err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (err != NO_ERROR) {
+ pthread_attr_destroy(&attr);
+ return err;
+ }
+ err = pthread_create(&thread, &attr, log_reader_thread_func, static_cast<void*>(data));
+ if (err != NO_ERROR) {
+ pthread_attr_destroy(&attr);
+ return err;
+ }
+ pthread_attr_destroy(&attr);
+
+ return NO_ERROR;
+}
+
+// ================================================================================
+int
+main(int /*argc*/, char** /*argv*/)
+{
+ status_t err;
+
+ // Set up the looper
+ sp<Looper> looper(Looper::prepare(0 /* opts */));
+
+ // Set up the binder
+ sp<ProcessState> ps(ProcessState::self());
+ ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram
+ ps->startThreadPool();
+ ps->giveThreadPoolName();
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+
+ // Create the service
+ sp<StatsService> service = new StatsService(looper);
+ if (defaultServiceManager()->addService(String16("stats"), service) != 0) {
+ ALOGE("Failed to add service");
+ return -1;
+ }
+
+ // TODO: This line is temporary, since statsd doesn't start up automatically (and therefore
+ // the call in StatsService::SystemRunning() won't ever be called right now).
+ service->sayHiToStatsCompanion();
+
+ // Start the log reader thread
+ err = start_log_reader_thread(service);
+ if (err != NO_ERROR) {
+ return 1;
+ }
+
+ // Loop forever -- the reports run on this thread in a handler, and the
+ // binder calls remain responsive in their pool of one thread.
+ while (true) {
+ looper->pollAll(-1 /* timeoutMillis */);
+ }
+ ALOGW("statsd escaped from its loop.");
+
+ return 1;
+}
diff --git a/cmds/statsd/src/parse_util.cpp b/cmds/statsd/src/parse_util.cpp
new file mode 100644
index 000000000000..ffce88424d93
--- /dev/null
+++ b/cmds/statsd/src/parse_util.cpp
@@ -0,0 +1,121 @@
+/*
+ * 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 <parse_util.h>
+#include <log/log_event_list.h>
+
+using android::os::statsd::EVENT_TIMESTAMP;
+using android::os::statsd::EventMetricData;
+using android::os::statsd::KeyId;
+using android::os::statsd::KeyId_IsValid;
+using android::os::statsd::KeyValuePair;
+using android::os::statsd::TagId;
+using android::os::statsd::TagId_IsValid;
+
+EventMetricData parse(log_msg msg)
+{
+ // dump all statsd logs to dropbox for now.
+ // TODO: Add filtering, aggregation, etc.
+ EventMetricData eventMetricData;
+
+ // set timestamp of the event.
+ KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
+ keyValuePair->set_key(EVENT_TIMESTAMP);
+ keyValuePair->set_value_int(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec);
+
+ // start iterating k,v pairs.
+ android_log_context context = create_android_log_parser(const_cast<log_msg*>(&msg)->msg()
+ + sizeof(uint32_t),
+ const_cast<log_msg*>(&msg)->len()
+ - sizeof(uint32_t));
+ android_log_list_element elem;
+
+ if (context) {
+ memset(&elem, 0, sizeof(elem));
+ size_t index = 0;
+ int32_t key = -1;
+ int32_t tag = -1;
+
+ do {
+ elem = android_log_read_next(context);
+ switch ((int)elem.type) {
+ case EVENT_TYPE_INT:
+ if (index == 0) {
+ tag = elem.data.int32;
+ if (TagId_IsValid(tag)) {
+ eventMetricData.set_tag(static_cast<TagId>(tag));
+ } else {
+ break;
+ }
+ } else if (index % 2 == 1) {
+ key = elem.data.int32;
+ } else if (KeyId_IsValid(key)) {
+ int32_t val = elem.data.int32;
+ KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
+ keyValuePair->set_key(static_cast<KeyId>(key));
+ keyValuePair->set_value_int(val);
+ } else {
+ }
+ index++;
+ break;
+ case EVENT_TYPE_FLOAT:
+ if (index % 2 == 0 && KeyId_IsValid(key)) {
+ float val = elem.data.float32;
+ KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
+ keyValuePair->set_key(static_cast<KeyId>(key));
+ keyValuePair->set_value_float(val);
+ }
+ index++;
+ break;
+ case EVENT_TYPE_STRING:
+ if (index % 2 == 0 && KeyId_IsValid(key)) {
+ char* val = elem.data.string;
+ KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
+ keyValuePair->set_key(static_cast<KeyId>(key));
+ keyValuePair->set_value_str(val);
+ }
+ index++;
+ break;
+ case EVENT_TYPE_LONG:
+ if (index % 2 == 0 && KeyId_IsValid(key)) {
+ int64_t val = elem.data.int64;
+ KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
+ keyValuePair->set_key(static_cast<KeyId>(key));
+ keyValuePair->set_value_int(val);
+ }
+ index++;
+ break;
+ case EVENT_TYPE_LIST:
+ break;
+ case EVENT_TYPE_LIST_STOP:
+ break;
+ case EVENT_TYPE_UNKNOWN:
+ break;
+ default:
+ elem.complete = true;
+ break;
+ }
+
+ if (elem.complete) {
+ break;
+ }
+ } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
+
+ android_log_destroy(&context);
+ }
+
+ return eventMetricData;
+}
diff --git a/cmds/statsd/src/parse_util.h b/cmds/statsd/src/parse_util.h
new file mode 100644
index 000000000000..8750f822102d
--- /dev/null
+++ b/cmds/statsd/src/parse_util.h
@@ -0,0 +1,28 @@
+/*
+ * 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 PARSE_UTIL_H
+#define PARSE_UTIL_H
+
+#include "LogReader.h"
+#include "DropboxWriter.h"
+
+#include <log/logprint.h>
+
+using android::os::statsd::EventMetricData;
+
+EventMetricData parse(const log_msg msg);
+
+#endif // PARSE_UTIL_H
diff --git a/cmds/statsd/src/stats_constants.proto b/cmds/statsd/src/stats_constants.proto
new file mode 100644
index 000000000000..9758a2e0cb89
--- /dev/null
+++ b/cmds/statsd/src/stats_constants.proto
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+package android.os.statsd;
+
+option optimize_for = LITE_RUNTIME;
+
+option java_package = "com.android.internal.logging";
+option java_outer_classname = "StatsConstantsProto";
+
+enum TagId {
+ WAKELOCK = 1;
+ SCREEN = 2;
+ PROCESS = 1112; // TODO: Temporary usage only for testing.
+}
+
+enum KeyId {
+ STATE = 1;
+ ANOTHER_STATE = 2;
+ EVENT_TIMESTAMP = 1001;
+ PACKAGE_NAME = 1002;
+ PACKAGE_VERSION = 1003;
+ PACKAGE_VERSION_STRING = 1004;
+ ATTRIBUTION_CHAIN = 1005;
+}
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
new file mode 100644
index 000000000000..2c66ded0c7ed
--- /dev/null
+++ b/cmds/statsd/src/stats_log.proto
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+package android.os.statsd;
+
+option optimize_for = LITE_RUNTIME;
+
+option java_package = "com.android.os";
+option java_outer_classname = "StatsLog";
+
+import "frameworks/base/cmds/statsd/src/statsd_config.proto";
+import "frameworks/base/cmds/statsd/src/stats_constants.proto";
+
+message KeyValuePair {
+ optional KeyId key = 1;
+
+ oneof value {
+ string value_str = 2;
+ int64 value_int = 3;
+ bool value_bool = 4;
+ float value_float = 5;
+ }
+}
+
+message EventMetricData {
+ optional TagId tag = 1;
+
+ repeated KeyValuePair key_value_pair = 2;
+}
+
+message CountBucketInfo {
+ optional int64 start_bucket_millis = 1;
+
+ optional int64 end_bucket_millis = 2;
+
+ optional int64 count = 3;
+}
+
+message CountMetricData {
+ repeated KeyValuePair dimension = 1;
+
+ repeated CountBucketInfo bucket_info = 2;
+}
+
+message StatsLogReport {
+ optional int32 metric_id = 1;
+
+ optional int64 start_report_millis = 2;
+
+ optional int64 end_report_millis = 3;
+
+ message EventMetricDataWrapper {
+ repeated EventMetricData data = 1;
+ }
+ message CountMetricDataWrapper {
+ repeated CountMetricData data = 1;
+ }
+
+ oneof data {
+ EventMetricDataWrapper event_metrics = 4;
+ CountMetricDataWrapper count_metrics = 5;
+ }
+}
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
new file mode 100644
index 000000000000..c6119df691a3
--- /dev/null
+++ b/cmds/statsd/src/statsd_config.proto
@@ -0,0 +1,119 @@
+syntax = "proto2";
+package android.os.statsd;
+
+option optimize_for = LITE_RUNTIME;
+
+option java_package = "com.android.internal.os";
+option java_outer_classname = "StatsdConfigProto";
+
+import "frameworks/base/cmds/statsd/src/stats_constants.proto";
+
+message KeyMatcher {
+ optional KeyId key = 1;
+ optional bool as_package_name = 2 [ default = false ];
+}
+
+message KeyValueMatcher {
+ optional KeyMatcher key_matcher = 1;
+
+ oneof value_matcher {
+ bool eq_bool = 2;
+ string eq_string = 3;
+ int32 eq_int32 = 4;
+ int64 eq_int64 = 5;
+ int32 lt_int32 = 6;
+ int32 gt_int32 = 7;
+ int64 lt_int64 = 8;
+ int64 gt_int64 = 9;
+ float lt_float = 10;
+ float gt_float = 11;
+ }
+}
+
+enum LogicalOperation {
+ AND = 1;
+ OR = 2;
+ NOT = 3;
+ NAND = 4;
+ NOR = 5;
+}
+
+message SimpleLogEntryMatcher {
+ repeated TagId tag = 1;
+
+ repeated KeyValueMatcher key_value_matcher = 2;
+}
+
+message LogEntryMatcher {
+ optional string name = 1;
+
+ message Combination {
+ optional LogicalOperation operation = 1;
+ repeated LogEntryMatcher matcher = 2;
+ }
+ oneof contents {
+ SimpleLogEntryMatcher simple_log_entry_matcher = 2;
+ Combination combination = 3;
+ }
+}
+
+message SimpleCondition {
+ optional string start = 1;
+
+ optional string stop = 2;
+
+ optional bool count_nesting = 3 [default = true];
+
+ optional string stop_all = 4;
+}
+
+message Condition {
+ optional string name = 1;
+
+ message Combination {
+ optional LogicalOperation operation = 1;
+
+ repeated string condition = 2;
+ }
+
+ oneof contents {
+ SimpleCondition simple_condition = 2;
+ Combination combination = 3;
+ }
+}
+
+message Bucket {
+ optional int64 bucket_size_millis = 1;
+}
+
+message EventMetric {
+ optional int64 metric_id = 1;
+
+ optional string what = 2;
+
+ optional string condition = 3;
+}
+
+message CountMetric {
+ optional int64 metric_id = 1;
+
+ optional string what = 2;
+
+ optional string condition = 3;
+
+ repeated KeyMatcher dimension = 4;
+
+ optional Bucket bucket = 5;
+}
+
+message StatsdConfig {
+ optional int64 config_id = 1;
+
+ repeated EventMetric event_metric = 2;
+
+ repeated CountMetric count_metric = 3;
+
+ repeated LogEntryMatcher log_entry_matcher = 4;
+
+ repeated Condition condition = 5;
+}
diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc
new file mode 100644
index 000000000000..faccd610e223
--- /dev/null
+++ b/cmds/statsd/statsd.rc
@@ -0,0 +1,16 @@
+# 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.
+
+service statsd /system/bin/statsd
+ class main
diff --git a/cmds/statsd/tests/LogReader_test.cpp b/cmds/statsd/tests/LogReader_test.cpp
new file mode 100644
index 000000000000..ca538b082f89
--- /dev/null
+++ b/cmds/statsd/tests/LogReader_test.cpp
@@ -0,0 +1,24 @@
+// 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 LOG_TAG "statsd_test"
+
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+
+TEST(LogReaderTest, TestNothingAtAll) {
+ printf("yay!");
+}
+
diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp
new file mode 100644
index 000000000000..1aad08918ad9
--- /dev/null
+++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp
@@ -0,0 +1,188 @@
+/*
+ * 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 "../src/indexed_priority_queue.h"
+
+#include <gtest/gtest.h>
+
+using namespace android::os::statsd;
+
+/** struct for template in indexed_priority_queue */
+struct AATest : public RefBase {
+ AATest(uint32_t val) : val(val) {
+ }
+
+ const int val;
+
+ struct Smaller {
+ bool operator()(const sp<const AATest> a, const sp<const AATest> b) const {
+ return (a->val < b->val);
+ }
+ };
+};
+
+#ifdef __ANDROID__
+TEST(indexed_priority_queue, empty_and_size) {
+ indexed_priority_queue<AATest, AATest::Smaller> ipq;
+ sp<const AATest> aa4 = new AATest{4};
+ sp<const AATest> aa8 = new AATest{8};
+
+ EXPECT_EQ(0u, ipq.size());
+ EXPECT_TRUE(ipq.empty());
+
+ ipq.push(aa4);
+ EXPECT_EQ(1u, ipq.size());
+ EXPECT_FALSE(ipq.empty());
+
+ ipq.push(aa8);
+ EXPECT_EQ(2u, ipq.size());
+ EXPECT_FALSE(ipq.empty());
+
+ ipq.remove(aa4);
+ EXPECT_EQ(1u, ipq.size());
+ EXPECT_FALSE(ipq.empty());
+
+ ipq.remove(aa8);
+ EXPECT_EQ(0u, ipq.size());
+ EXPECT_TRUE(ipq.empty());
+}
+
+TEST(indexed_priority_queue, top) {
+ indexed_priority_queue<AATest, AATest::Smaller> ipq;
+ sp<const AATest> aa2 = new AATest{2};
+ sp<const AATest> aa4 = new AATest{4};
+ sp<const AATest> aa8 = new AATest{8};
+ sp<const AATest> aa12 = new AATest{12};
+ sp<const AATest> aa16 = new AATest{16};
+ sp<const AATest> aa20 = new AATest{20};
+
+ EXPECT_EQ(ipq.top(), nullptr);
+
+ // add 8, 4, 12
+ ipq.push(aa8);
+ EXPECT_EQ(ipq.top(), aa8);
+
+ ipq.push(aa12);
+ EXPECT_EQ(ipq.top(), aa8);
+
+ ipq.push(aa4);
+ EXPECT_EQ(ipq.top(), aa4);
+
+ // remove 12, 4
+ ipq.remove(aa12);
+ EXPECT_EQ(ipq.top(), aa4);
+
+ ipq.remove(aa4);
+ EXPECT_EQ(ipq.top(), aa8);
+
+ // add 16, 2, 20
+ ipq.push(aa16);
+ EXPECT_EQ(ipq.top(), aa8);
+
+ ipq.push(aa2);
+ EXPECT_EQ(ipq.top(), aa2);
+
+ ipq.push(aa20);
+ EXPECT_EQ(ipq.top(), aa2);
+
+ // remove 2, 20, 16, 8
+ ipq.remove(aa2);
+ EXPECT_EQ(ipq.top(), aa8);
+
+ ipq.remove(aa20);
+ EXPECT_EQ(ipq.top(), aa8);
+
+ ipq.remove(aa16);
+ EXPECT_EQ(ipq.top(), aa8);
+
+ ipq.remove(aa8);
+ EXPECT_EQ(ipq.top(), nullptr);
+}
+
+TEST(indexed_priority_queue, push_same_aa) {
+ indexed_priority_queue<AATest, AATest::Smaller> ipq;
+ sp<const AATest> aa4_a = new AATest{4};
+ sp<const AATest> aa4_b = new AATest{4};
+
+ ipq.push(aa4_a);
+ EXPECT_EQ(1u, ipq.size());
+ EXPECT_TRUE(ipq.contains(aa4_a));
+ EXPECT_FALSE(ipq.contains(aa4_b));
+
+ ipq.push(aa4_a);
+ EXPECT_EQ(1u, ipq.size());
+ EXPECT_TRUE(ipq.contains(aa4_a));
+ EXPECT_FALSE(ipq.contains(aa4_b));
+
+ ipq.push(aa4_b);
+ EXPECT_EQ(2u, ipq.size());
+ EXPECT_TRUE(ipq.contains(aa4_a));
+ EXPECT_TRUE(ipq.contains(aa4_b));
+}
+
+
+TEST(indexed_priority_queue, remove_nonexistant) {
+ indexed_priority_queue<AATest, AATest::Smaller> ipq;
+ sp<const AATest> aa4 = new AATest{4};
+ sp<const AATest> aa5 = new AATest{5};
+
+ ipq.push(aa4);
+ ipq.remove(aa5);
+ EXPECT_EQ(1u, ipq.size());
+ EXPECT_TRUE(ipq.contains(aa4));
+ EXPECT_FALSE(ipq.contains(aa5));
+}
+
+TEST(indexed_priority_queue, remove_same_aa) {
+ indexed_priority_queue<AATest, AATest::Smaller> ipq;
+ sp<const AATest> aa4_a = new AATest{4};
+ sp<const AATest> aa4_b = new AATest{4};
+
+ ipq.push(aa4_a);
+ ipq.push(aa4_b);
+ EXPECT_EQ(2u, ipq.size());
+ EXPECT_TRUE(ipq.contains(aa4_a));
+ EXPECT_TRUE(ipq.contains(aa4_b));
+
+ ipq.remove(aa4_b);
+ EXPECT_EQ(1u, ipq.size());
+ EXPECT_TRUE(ipq.contains(aa4_a));
+ EXPECT_FALSE(ipq.contains(aa4_b));
+
+ ipq.remove(aa4_a);
+ EXPECT_EQ(0u, ipq.size());
+ EXPECT_FALSE(ipq.contains(aa4_a));
+ EXPECT_FALSE(ipq.contains(aa4_b));
+}
+
+TEST(indexed_priority_queue, nulls) {
+ indexed_priority_queue<AATest, AATest::Smaller> ipq;
+
+ EXPECT_TRUE(ipq.empty());
+ EXPECT_FALSE(ipq.contains(nullptr));
+
+ ipq.push(nullptr);
+ EXPECT_TRUE(ipq.empty());
+ EXPECT_FALSE(ipq.contains(nullptr));
+
+ ipq.remove(nullptr);
+ EXPECT_TRUE(ipq.empty());
+ EXPECT_FALSE(ipq.contains(nullptr));
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif