diff options
Diffstat (limited to 'cmds')
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 |