Merge "nativebrige: log code_cache access errors to stderr as well"
diff --git a/adb/Android.mk b/adb/Android.mk
index 9c8ab6d..d629223 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -349,8 +349,6 @@
libcutils \
libbase \
libcrypto_static \
- libminijail \
- libminijail_generated \
- libcap
+ libminijail
include $(BUILD_EXECUTABLE)
diff --git a/adb/adb.cpp b/adb/adb.cpp
index c03d7db..484e561 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -857,8 +857,7 @@
#if ADB_HOST
SendOkay(reply_fd);
#endif
- SendProtocolString(reply_fd, listeners);
- return 1;
+ return SendProtocolString(reply_fd, listeners);
}
if (!strcmp(service, "killforward-all")) {
diff --git a/adb/adb.h b/adb/adb.h
index 9020fc3..59644d4 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -142,10 +142,10 @@
void print_packet(const char *label, apacket *p);
-
-
-void fatal(const char *fmt, ...) __attribute__((noreturn));
-void fatal_errno(const char *fmt, ...) __attribute__((noreturn));
+// These use the system (v)fprintf, not the adb prefixed ones defined in sysdeps.h, so they
+// shouldn't be tagged with ADB_FORMAT_ARCHETYPE.
+void fatal(const char* fmt, ...) __attribute__((noreturn, format(__printf__, 1, 2)));
+void fatal_errno(const char* fmt, ...) __attribute__((noreturn, format(__printf__, 1, 2)));
void handle_packet(apacket *p, atransport *t);
diff --git a/adb/adb_client.cpp b/adb/adb_client.cpp
index cb5e488..bbc4dc7 100644
--- a/adb/adb_client.cpp
+++ b/adb/adb_client.cpp
@@ -124,7 +124,7 @@
int _adb_connect(const std::string& service, std::string* error) {
D("_adb_connect: %s", service.c_str());
- if (service.empty() || service.size() > 1024) {
+ if (service.empty() || service.size() > MAX_PAYLOAD_V1) {
*error = android::base::StringPrintf("bad service name length (%zd)",
service.size());
return -1;
diff --git a/adb/adb_io.cpp b/adb/adb_io.cpp
index 176b7bd..ae16834 100644
--- a/adb/adb_io.cpp
+++ b/adb/adb_io.cpp
@@ -22,14 +22,16 @@
#include <android-base/stringprintf.h>
+#include "adb.h"
#include "adb_trace.h"
#include "adb_utils.h"
#include "sysdeps.h"
bool SendProtocolString(int fd, const std::string& s) {
- int length = s.size();
- if (length > 0xffff) {
- length = 0xffff;
+ unsigned int length = s.size();
+ if (length > MAX_PAYLOAD_V1 - 4) {
+ errno = EMSGSIZE;
+ return false;
}
// The cost of sending two strings outweighs the cost of formatting.
diff --git a/adb/line_printer.cpp b/adb/line_printer.cpp
index e8fe6c9..4ec8979 100644
--- a/adb/line_printer.cpp
+++ b/adb/line_printer.cpp
@@ -67,7 +67,7 @@
void LinePrinter::Print(string to_print, LineType type) {
if (!smart_terminal_) {
- Out(to_print);
+ Out(to_print + "\n");
return;
}
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index eb0ce85..d8e4e93 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -698,17 +698,17 @@
p = s->pkt_first;
}
- /* don't bother if we can't decode the length */
+ /* don't bother if we can't decode the length */
if(p->len < 4) return 0;
len = unhex(p->data, 4);
- if((len < 1) || (len > 1024)) {
+ if ((len < 1) || (len > MAX_PAYLOAD_V1)) {
D("SS(%d): bad size (%d)", s->id, len);
goto fail;
}
D("SS(%d): len is %d", s->id, len );
- /* can't do anything until we have the full header */
+ /* can't do anything until we have the full header */
if((len + 4) > p->len) {
D("SS(%d): waiting for %d more bytes", s->id, len+4 - p->len);
return 0;
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index 0a2a8f6..c3889b6 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -88,7 +88,10 @@
_fh_socket_hook
};
-#define assert(cond) do { if (!(cond)) fatal( "assertion failed '%s' on %s:%ld\n", #cond, __FILE__, __LINE__ ); } while (0)
+#define assert(cond) \
+ do { \
+ if (!(cond)) fatal("assertion failed '%s' on %s:%d\n", #cond, __FILE__, __LINE__); \
+ } while (0)
std::string SystemErrorCodeToString(const DWORD error_code) {
const int kErrorMessageBufferSize = 256;
@@ -1589,7 +1592,7 @@
/**************************************************************************/
/**************************************************************************/
-#define FATAL(x...) fatal(__FUNCTION__, x)
+#define FATAL(fmt, ...) fatal("%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
#if DEBUG
static void dump_fde(fdevent *fde, const char *info)
diff --git a/adb/sysdeps_win32_test.cpp b/adb/sysdeps_win32_test.cpp
index 81923cb..1d40281 100755
--- a/adb/sysdeps_win32_test.cpp
+++ b/adb/sysdeps_win32_test.cpp
@@ -66,7 +66,7 @@
const char* path_val = adb_getenv("PATH");
EXPECT_NE(nullptr, path_val);
if (path_val != nullptr) {
- EXPECT_GT(strlen(path_val), 0);
+ EXPECT_GT(strlen(path_val), 0U);
}
}
diff --git a/base/utf8_test.cpp b/base/utf8_test.cpp
index dde7490..ae8fc8c 100755
--- a/base/utf8_test.cpp
+++ b/base/utf8_test.cpp
@@ -44,7 +44,7 @@
// specific replacement character that UTF8ToWide() may replace the invalid
// UTF-8 characters with because we want to allow that to change if the
// implementation changes.
- EXPECT_EQ(0, wide.find(L"before"));
+ EXPECT_EQ(0U, wide.find(L"before"));
const wchar_t after_wide[] = L"after";
EXPECT_EQ(wide.length() - (arraysize(after_wide) - 1), wide.find(after_wide));
}
diff --git a/bootstat/Android.mk b/bootstat/Android.mk
new file mode 100644
index 0000000..348db88
--- /dev/null
+++ b/bootstat/Android.mk
@@ -0,0 +1,137 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+bootstat_c_includes := external/gtest/include
+
+bootstat_lib_src_files := \
+ boot_event_record_store.cpp \
+ event_log_list_builder.cpp
+
+bootstat_src_files := \
+ bootstat.cpp
+
+bootstat_test_src_files := \
+ boot_event_record_store_test.cpp \
+ event_log_list_builder_test.cpp \
+ testrunner.cpp
+
+bootstat_shared_libs := \
+ libbase \
+ liblog
+
+bootstat_cflags := \
+ -Wall \
+ -Wextra \
+ -Werror
+
+bootstat_cppflags := \
+ -Wno-non-virtual-dtor
+
+bootstat_debug_cflags := \
+ $(bootstat_cflags) \
+ -UNDEBUG
+
+# 524291 corresponds to sysui_histogram, from
+# frameworks/base/core/java/com/android/internal/logging/EventLogTags.logtags
+bootstat_cflags += -DHISTOGRAM_LOG_TAG=524291
+
+
+# bootstat static library
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libbootstat
+LOCAL_CFLAGS := $(bootstat_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_SRC_FILES := $(bootstat_lib_src_files)
+
+include $(BUILD_STATIC_LIBRARY)
+
+# bootstat static library, debug
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libbootstat_debug
+LOCAL_CFLAGS := $(bootstat_cflags)
+LOCAL_CPPFLAGS := $(bootstat_debug_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_SRC_FILES := $(bootstat_lib_src_files)
+
+include $(BUILD_STATIC_LIBRARY)
+
+# bootstat host static library, debug
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libbootstat_host_debug
+LOCAL_CFLAGS := $(bootstat_debug_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_SRC_FILES := $(bootstat_lib_src_files)
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# bootstat binary
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootstat
+LOCAL_CFLAGS := $(bootstat_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_STATIC_LIBRARIES := libbootstat
+LOCAL_SRC_FILES := $(bootstat_src_files)
+
+include $(BUILD_EXECUTABLE)
+
+# Native tests
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootstat_tests
+LOCAL_CFLAGS := $(bootstat_tests_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_STATIC_LIBRARIES := libbootstat_debug libgmock
+LOCAL_SRC_FILES := $(bootstat_test_src_files)
+
+include $(BUILD_NATIVE_TEST)
+
+# Host native tests
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootstat_tests
+LOCAL_CFLAGS := $(bootstat_tests_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_STATIC_LIBRARIES := libbootstat_host_debug libgmock_host
+LOCAL_SRC_FILES := $(bootstat_test_src_files)
+
+include $(BUILD_HOST_NATIVE_TEST)
diff --git a/bootstat/README.md b/bootstat/README.md
new file mode 100644
index 0000000..b494951
--- /dev/null
+++ b/bootstat/README.md
@@ -0,0 +1,47 @@
+# bootstat #
+
+The bootstat command records boot events (e.g., `firmware_loaded`,
+`boot_complete`) and the relative time at which these events occurred. The
+command also aggregates boot event metrics locally and logs the metrics for
+analysis.
+
+ Usage: bootstat [options]
+ options include:
+ -d Dump the boot event records to the console.
+ -h Show this help.
+ -l Log all metrics to logstorage.
+ -r Record the relative time of a named boot event.
+
+## Relative time ##
+
+The timestamp recorded by bootstat is the uptime of the system, i.e., the
+number of seconds since the system booted.
+
+## Recording boot events ##
+
+To record the relative time of an event during the boot phase, call `bootstat`
+with the `-r` option and the name of the boot event.
+
+ $ bootstat -r boot_complete
+
+The relative time at which the command runs is recorded along with the name of
+the boot event to be persisted.
+
+## Logging boot events ##
+
+To log the persisted boot events, call `bootstat` with the `-l` option.
+
+ $ bootstat -l
+
+bootstat logs all boot events recorded using the `-r` option to the EventLog
+using the Tron histogram. On GMS devices these logs are uploaded via Clearcut
+for aggregation and analysis.
+
+## Printing boot events ##
+
+To print the set of persisted boot events, call `bootstat` with the `-p` option.
+
+ $ bootstat -p
+ Boot events:
+ ------------
+ boot_complete 71
\ No newline at end of file
diff --git a/bootstat/boot_event_record_store.cpp b/bootstat/boot_event_record_store.cpp
new file mode 100644
index 0000000..4dab92e
--- /dev/null
+++ b/bootstat/boot_event_record_store.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "boot_event_record_store.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <utime.h>
+#include <cstdlib>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+namespace {
+
+const char BOOTSTAT_DATA_DIR[] = "/data/misc/bootstat/";
+
+// Given a boot even record file at |path|, extracts the event's relative time
+// from the record into |uptime|.
+bool ParseRecordEventTime(const std::string& path, int32_t* uptime) {
+ DCHECK_NE(static_cast<int32_t*>(nullptr), uptime);
+
+ struct stat file_stat;
+ if (stat(path.c_str(), &file_stat) == -1) {
+ PLOG(ERROR) << "Failed to read " << path;
+ return false;
+ }
+
+ *uptime = file_stat.st_mtime;
+ return true;
+}
+
+} // namespace
+
+BootEventRecordStore::BootEventRecordStore() {
+ SetStorePath(BOOTSTAT_DATA_DIR);
+}
+
+void BootEventRecordStore::AddBootEvent(const std::string& name) {
+ std::string uptime_str;
+ if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) {
+ LOG(ERROR) << "Failed to read /proc/uptime";
+ }
+
+ std::string record_path = GetBootEventPath(name);
+ if (creat(record_path.c_str(), S_IRUSR | S_IWUSR) == -1) {
+ PLOG(ERROR) << "Failed to create " << record_path;
+ }
+
+ struct stat file_stat;
+ if (stat(record_path.c_str(), &file_stat) == -1) {
+ PLOG(ERROR) << "Failed to read " << record_path;
+ }
+
+ // Cast intentionally rounds down.
+ time_t uptime = static_cast<time_t>(strtod(uptime_str.c_str(), NULL));
+ struct utimbuf times = {file_stat.st_atime, uptime};
+ if (utime(record_path.c_str(), ×) == -1) {
+ PLOG(ERROR) << "Failed to set mtime for " << record_path;
+ }
+}
+
+std::vector<BootEventRecordStore::BootEventRecord> BootEventRecordStore::
+ GetAllBootEvents() const {
+ std::vector<BootEventRecord> events;
+
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(store_path_.c_str()), closedir);
+
+ // This case could happen due to external manipulation of the filesystem,
+ // so crash out if the record store doesn't exist.
+ CHECK_NE(static_cast<DIR*>(nullptr), dir.get());
+
+ struct dirent* entry;
+ while ((entry = readdir(dir.get())) != NULL) {
+ // Only parse regular files.
+ if (entry->d_type != DT_REG) {
+ continue;
+ }
+
+ const std::string event = entry->d_name;
+ const std::string record_path = GetBootEventPath(event);
+ int32_t uptime;
+ if (!ParseRecordEventTime(record_path, &uptime)) {
+ LOG(ERROR) << "Failed to parse boot time record: " << record_path;
+ continue;
+ }
+
+ events.push_back(std::make_pair(event, uptime));
+ }
+
+ return events;
+}
+
+void BootEventRecordStore::SetStorePath(const std::string& path) {
+ DCHECK_EQ('/', path.back());
+ store_path_ = path;
+}
+
+std::string BootEventRecordStore::GetBootEventPath(
+ const std::string& event) const {
+ DCHECK_EQ('/', store_path_.back());
+ return store_path_ + event;
+}
diff --git a/bootstat/boot_event_record_store.h b/bootstat/boot_event_record_store.h
new file mode 100644
index 0000000..77978ef
--- /dev/null
+++ b/bootstat/boot_event_record_store.h
@@ -0,0 +1,61 @@
+/*
+ * 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 BOOT_EVENT_RECORD_STORE_H_
+#define BOOT_EVENT_RECORD_STORE_H_
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+#include <android-base/macros.h>
+#include <gtest/gtest_prod.h>
+
+// BootEventRecordStore manages the persistence of boot events to the record
+// store and the retrieval of all boot event records from the store.
+class BootEventRecordStore {
+ public:
+ // A BootEventRecord consists of the event name and the timestamp the event
+ // occurred.
+ typedef std::pair<std::string, int32_t> BootEventRecord;
+
+ BootEventRecordStore();
+
+ // Persists the boot event named |name| in the record store.
+ void AddBootEvent(const std::string& name);
+
+ // Returns a list of all of the boot events persisted in the record store.
+ std::vector<BootEventRecord> GetAllBootEvents() const;
+
+ private:
+ // The tests call SetStorePath to override the default store location with a
+ // more test-friendly path.
+ FRIEND_TEST(BootEventRecordStoreTest, AddSingleBootEvent);
+ FRIEND_TEST(BootEventRecordStoreTest, AddMultipleBootEvents);
+
+ // Sets the filesystem path of the record store.
+ void SetStorePath(const std::string& path);
+
+ // Constructs the full path of the given boot |event|.
+ std::string GetBootEventPath(const std::string& event) const;
+
+ // The filesystem path of the record store.
+ std::string store_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootEventRecordStore);
+};
+
+#endif // BOOT_EVENT_RECORD_STORE_H_
\ No newline at end of file
diff --git a/bootstat/boot_event_record_store_test.cpp b/bootstat/boot_event_record_store_test.cpp
new file mode 100644
index 0000000..90874f5
--- /dev/null
+++ b/bootstat/boot_event_record_store_test.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "boot_event_record_store.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <cstdint>
+#include <cstdlib>
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+using testing::UnorderedElementsAreArray;
+
+namespace {
+
+// Returns true if the time difference between |a| and |b| is no larger
+// than 10 seconds. This allow for a relatively large fuzz when comparing
+// two timestamps taken back-to-back.
+bool FuzzUptimeEquals(int32_t a, int32_t b) {
+ const int32_t FUZZ_SECONDS = 10;
+ return (abs(a - b) <= FUZZ_SECONDS);
+}
+
+// Returns the uptime as read from /proc/uptime, rounded down to an integer.
+int32_t ReadUptime() {
+ std::string uptime_str;
+ if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) {
+ return -1;
+ }
+
+ // Cast to int to round down.
+ return static_cast<int32_t>(strtod(uptime_str.c_str(), NULL));
+}
+
+// Recursively deletes the directory at |path|.
+void DeleteDirectory(const std::string& path) {
+ typedef std::unique_ptr<DIR, decltype(&closedir)> ScopedDIR;
+ ScopedDIR dir(opendir(path.c_str()), closedir);
+ ASSERT_NE(nullptr, dir.get());
+
+ struct dirent* entry;
+ while ((entry = readdir(dir.get())) != NULL) {
+ const std::string entry_name(entry->d_name);
+ if (entry_name == "." || entry_name == "..") {
+ continue;
+ }
+
+ const std::string entry_path = path + "/" + entry_name;
+ if (entry->d_type == DT_DIR) {
+ DeleteDirectory(entry_path);
+ } else {
+ unlink(entry_path.c_str());
+ }
+ }
+
+ rmdir(path.c_str());
+}
+
+class BootEventRecordStoreTest : public ::testing::Test {
+ public:
+ BootEventRecordStoreTest() {
+ store_path_ = std::string(store_dir_.path) + "/";
+ }
+
+ const std::string& GetStorePathForTesting() const {
+ return store_path_;
+ }
+
+ private:
+ void TearDown() {
+ // This removes the record store temporary directory even though
+ // TemporaryDir should already take care of it, but this method cleans up
+ // the test files added to the directory which prevent TemporaryDir from
+ // being able to remove the directory.
+ DeleteDirectory(store_path_);
+ }
+
+ // A scoped temporary directory. Using this abstraction provides creation of
+ // the directory and the path to the directory, which is stored in
+ // |store_path_|.
+ TemporaryDir store_dir_;
+
+ // The path to the temporary directory used by the BootEventRecordStore to
+ // persist records. The directory is created and destroyed for each test.
+ std::string store_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest);
+};
+
+} // namespace
+
+TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) {
+ BootEventRecordStore store;
+ store.SetStorePath(GetStorePathForTesting());
+
+ int32_t uptime = ReadUptime();
+ ASSERT_NE(-1, uptime);
+
+ store.AddBootEvent("cenozoic");
+
+ auto events = store.GetAllBootEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ("cenozoic", events[0].first);
+ EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second));
+}
+
+TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) {
+ BootEventRecordStore store;
+ store.SetStorePath(GetStorePathForTesting());
+
+ int32_t uptime = ReadUptime();
+ ASSERT_NE(-1, uptime);
+
+ store.AddBootEvent("cretaceous");
+ store.AddBootEvent("jurassic");
+ store.AddBootEvent("triassic");
+
+ const std::string EXPECTED_NAMES[] = {
+ "cretaceous",
+ "jurassic",
+ "triassic",
+ };
+
+ auto events = store.GetAllBootEvents();
+ ASSERT_EQ(3U, events.size());
+
+ std::vector<std::string> names;
+ std::vector<int32_t> timestamps;
+ for (auto i = events.begin(); i != events.end(); ++i) {
+ names.push_back(i->first);
+ timestamps.push_back(i->second);
+ }
+
+ EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES));
+
+ for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) {
+ EXPECT_TRUE(FuzzUptimeEquals(uptime, *i));
+ }
+}
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
new file mode 100644
index 0000000..a83f8ad
--- /dev/null
+++ b/bootstat/bootstat.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+// The bootstat command provides options to persist boot events with the current
+// timestamp, dump the persisted events, and log all events to EventLog to be
+// uploaded to Android log storage via Tron.
+
+//#define LOG_TAG "bootstat"
+
+#include <unistd.h>
+#include <cstddef>
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <android-base/logging.h>
+#include <log/log.h>
+#include "boot_event_record_store.h"
+#include "event_log_list_builder.h"
+
+namespace {
+
+// Builds an EventLog buffer named |event| containing |data| and writes
+// the log into the Tron histogram logs.
+void LogBootEvent(const std::string& event, int32_t data) {
+ LOG(INFO) << "Logging boot time: " << event << " " << data;
+
+ EventLogListBuilder log_builder;
+ log_builder.Append(event);
+ log_builder.Append(data);
+
+ std::unique_ptr<uint8_t[]> log;
+ size_t size;
+ log_builder.Release(&log, &size);
+
+ android_bWriteLog(HISTOGRAM_LOG_TAG, log.get(), size);
+}
+
+// Scans the boot event record store for record files and logs each boot event
+// via EventLog.
+void LogBootEvents() {
+ BootEventRecordStore boot_event_store;
+
+ auto events = boot_event_store.GetAllBootEvents();
+ for (auto i = events.cbegin(); i != events.cend(); ++i) {
+ LogBootEvent(i->first, i->second);
+ }
+}
+
+void PrintBootEvents() {
+ printf("Boot events:\n");
+ printf("------------\n");
+
+ BootEventRecordStore boot_event_store;
+ auto events = boot_event_store.GetAllBootEvents();
+ for (auto i = events.cbegin(); i != events.cend(); ++i) {
+ printf("%s\t%d\n", i->first.c_str(), i->second);
+ }
+}
+
+void ShowHelp(const char *cmd) {
+ fprintf(stderr, "Usage: %s [options]\n", cmd);
+ fprintf(stderr,
+ "options include:\n"
+ " -d Dump the boot event records to the console.\n"
+ " -h Show this help.\n"
+ " -l Log all metrics to logstorage.\n"
+ " -r Record the timestamp of a named boot event.\n");
+}
+
+// Constructs a readable, printable string from the givencommand line
+// arguments.
+std::string GetCommandLine(int argc, char **argv) {
+ std::string cmd;
+ for (int i = 0; i < argc; ++i) {
+ cmd += argv[i];
+ cmd += " ";
+ }
+
+ return cmd;
+}
+
+} // namespace
+
+int main(int argc, char **argv) {
+ android::base::InitLogging(argv);
+
+ const std::string cmd_line = GetCommandLine(argc, argv);
+ LOG(INFO) << "Service started: " << cmd_line;
+
+ int opt = 0;
+ while ((opt = getopt(argc, argv, "hlpr:")) != -1) {
+ switch (opt) {
+ case 'h': {
+ ShowHelp(argv[0]);
+ break;
+ }
+
+ case 'l': {
+ LogBootEvents();
+ break;
+ }
+
+ case 'p': {
+ PrintBootEvents();
+ break;
+ }
+
+ case 'r': {
+ // |optarg| is an external variable set by getopt representing
+ // the option argument.
+ const char* event = optarg;
+
+ BootEventRecordStore boot_event_store;
+ boot_event_store.AddBootEvent(event);
+ break;
+ }
+
+ default: {
+ DCHECK_EQ(opt, '?');
+
+ // |optopt| is an external variable set by getopt representing
+ // the value of the invalid option.
+ LOG(ERROR) << "Invalid option: " << optopt;
+ ShowHelp(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/bootstat/event_log_list_builder.cpp b/bootstat/event_log_list_builder.cpp
new file mode 100644
index 0000000..7eb355a
--- /dev/null
+++ b/bootstat/event_log_list_builder.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "event_log_list_builder.h"
+
+#include <cinttypes>
+#include <string>
+#include <android-base/logging.h>
+#include <log/log.h>
+
+namespace {
+
+const size_t MAX_EVENT_PAYLOAD_SIZE = 512 - 1; // Leave room for final '\n'.
+const size_t EVENT_TYPE_SIZE = 1; // Size in bytes of the event type marker.
+
+} // namespace
+
+EventLogListBuilder::EventLogListBuilder()
+ : payload_count_(0),
+ payload_size_(0),
+ payload_(std::make_unique<uint8_t[]>(MAX_EVENT_PAYLOAD_SIZE)) {
+ memset(payload_.get(), 0, MAX_EVENT_PAYLOAD_SIZE);
+
+ // Set up the top-level EventLog data type.
+ AppendByte(EVENT_TYPE_LIST);
+
+ // Skip over the byte prepresenting the number of items in the list. This
+ // value is set in Release().
+ payload_size_++;
+}
+
+bool EventLogListBuilder::Append(int value) {
+ DCHECK_NE(static_cast<uint8_t*>(nullptr), payload_.get());
+
+ if (!IsSpaceAvailable(sizeof(value) + EVENT_TYPE_SIZE)) {
+ return false;
+ }
+
+ AppendByte(EVENT_TYPE_INT);
+ AppendData(&value, sizeof(value));
+
+ payload_count_++;
+ return true;
+}
+
+bool EventLogListBuilder::Append(const std::string& value) {
+ DCHECK_NE(static_cast<uint8_t*>(nullptr), payload_.get());
+
+ int len = value.length();
+ if (!IsSpaceAvailable(sizeof(len) + len)) {
+ return false;
+ }
+
+ AppendByte(EVENT_TYPE_STRING);
+ AppendData(&len, sizeof(len));
+ AppendData(value.c_str(), len);
+
+ payload_count_++;
+ return true;
+}
+
+void EventLogListBuilder::Release(std::unique_ptr<uint8_t[]>* log,
+ size_t* size) {
+ // Finalize the log payload.
+ payload_[1] = payload_count_;
+
+ // Return the log payload.
+ *size = payload_size_;
+ *log = std::move(payload_);
+}
+
+void EventLogListBuilder::AppendData(const void* data, size_t size) {
+ DCHECK_LT(payload_size_ + size, MAX_EVENT_PAYLOAD_SIZE);
+ memcpy(&payload_[payload_size_], data, size);
+ payload_size_ += size;
+}
+
+void EventLogListBuilder::AppendByte(uint8_t byte) {
+ DCHECK_LT(payload_size_ + sizeof(byte), MAX_EVENT_PAYLOAD_SIZE);
+ payload_[payload_size_++] = byte;
+}
+
+bool EventLogListBuilder::IsSpaceAvailable(size_t value_size) {
+ size_t space_needed = value_size + EVENT_TYPE_SIZE;
+ if (payload_size_ + space_needed > MAX_EVENT_PAYLOAD_SIZE) {
+ size_t remaining = MAX_EVENT_PAYLOAD_SIZE - payload_size_;
+ LOG(WARNING) << "Not enough space for value. remain=" <<
+ remaining << "; needed=" << space_needed;
+ return false;
+ }
+
+ return true;
+}
diff --git a/bootstat/event_log_list_builder.h b/bootstat/event_log_list_builder.h
new file mode 100644
index 0000000..4e29b01
--- /dev/null
+++ b/bootstat/event_log_list_builder.h
@@ -0,0 +1,70 @@
+/*
+ * 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 EVENT_LOG_LIST_BUILDER_H_
+#define EVENT_LOG_LIST_BUILDER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include <android-base/macros.h>
+
+// EventLogListBuilder provides a mechanism to build an EventLog list
+// consisting of int and string EventLog values.
+//
+// NOTE: This class does not provide the ability to append an embedded list,
+// i.e., a list containing a list.
+class EventLogListBuilder {
+ public:
+ EventLogListBuilder();
+
+ // Append a single value of a specified type.
+ bool Append(int value);
+ bool Append(const std::string& value);
+
+ // Finalizes construction of the EventLog list and releases the data
+ // to the caller. Caller takes ownership of the payload. No further calls
+ // to append* may be made once the payload is acquired by the caller.
+ void Release(std::unique_ptr<uint8_t[]>* log, size_t* size);
+
+ private:
+ // Appends |data| of the given |size| to the payload.
+ void AppendData(const void* data, size_t size);
+
+ // Appends a single byte to the payload.
+ void AppendByte(uint8_t byte);
+
+ // Returns true iff the remaining capacity in |payload_| is large enough to
+ // accommodate |value_size| bytes. The space required to log the event type
+ // is included in the internal calculation so must not be passed in to
+ // |value_size|.
+ bool IsSpaceAvailable(size_t value_size);
+
+ // The number of items in the EventLog list.
+ size_t payload_count_;
+
+ // The size of the data stored in |payload_|. Used to track where to insert
+ // new data.
+ size_t payload_size_;
+
+ // The payload constructed by calls to log*. The payload may only contain
+ // MAX_EVENT_PAYLOAD (512) bytes.
+ std::unique_ptr<uint8_t[]> payload_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventLogListBuilder);
+};
+
+ #endif // EVENT_LOG_LIST_BUILDER_H_
diff --git a/bootstat/event_log_list_builder_test.cpp b/bootstat/event_log_list_builder_test.cpp
new file mode 100644
index 0000000..affb4bf
--- /dev/null
+++ b/bootstat/event_log_list_builder_test.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "event_log_list_builder.h"
+
+#include <inttypes.h>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <log/log.h>
+
+using testing::ElementsAreArray;
+
+TEST(EventLogListBuilder, Empty) {
+ EventLogListBuilder builder;
+
+ const uint8_t EXPECTED_LOG[] = {
+ EVENT_TYPE_LIST,
+ 0, // Number of items in the list.
+ };
+
+ std::unique_ptr<uint8_t[]> log;
+ size_t size;
+ builder.Release(&log, &size);
+ EXPECT_EQ(2U, size);
+
+ uint8_t* log_data = log.get();
+ EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+ ElementsAreArray(EXPECTED_LOG));
+}
+
+TEST(EventLogListBuilder, SingleInt) {
+ EventLogListBuilder builder;
+
+ const uint8_t EXPECTED_LOG[] = {
+ EVENT_TYPE_LIST,
+ 1, // Number of items in the list.
+ EVENT_TYPE_INT,
+ 42, 0, 0, 0, // 4 byte integer value.
+ };
+
+ builder.Append(42);
+
+ std::unique_ptr<uint8_t[]> log;
+ size_t size;
+ builder.Release(&log, &size);
+ EXPECT_EQ(7U, size);
+
+ uint8_t* log_data = log.get();
+ EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+ ElementsAreArray(EXPECTED_LOG));
+}
+
+TEST(EventLogListBuilder, SingleString) {
+ EventLogListBuilder builder;
+
+ const uint8_t EXPECTED_LOG[] = {
+ EVENT_TYPE_LIST,
+ 1, // Number of items in the list.
+ EVENT_TYPE_STRING,
+ 5, 0, 0, 0, // 4 byte length of the string.
+ 'D', 'r', 'o', 'i', 'd',
+ };
+
+ builder.Append("Droid");
+
+ std::unique_ptr<uint8_t[]> log;
+ size_t size;
+ builder.Release(&log, &size);
+ EXPECT_EQ(12U, size);
+
+ uint8_t* log_data = log.get();
+ EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+ ElementsAreArray(EXPECTED_LOG));
+}
+
+TEST(EventLogListBuilder, IntThenString) {
+ EventLogListBuilder builder;
+
+ const uint8_t EXPECTED_LOG[] = {
+ EVENT_TYPE_LIST,
+ 2, // Number of items in the list.
+ EVENT_TYPE_INT,
+ 42, 0, 0, 0, // 4 byte integer value.
+ EVENT_TYPE_STRING,
+ 5, 0, 0, 0, // 4 byte length of the string.
+ 'D', 'r', 'o', 'i', 'd',
+ };
+
+ builder.Append(42);
+ builder.Append("Droid");
+
+ std::unique_ptr<uint8_t[]> log;
+ size_t size;
+ builder.Release(&log, &size);
+ EXPECT_EQ(17U, size);
+
+ uint8_t* log_data = log.get();
+ EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+ ElementsAreArray(EXPECTED_LOG));
+}
diff --git a/bootstat/testrunner.cpp b/bootstat/testrunner.cpp
new file mode 100644
index 0000000..79b61d1
--- /dev/null
+++ b/bootstat/testrunner.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ android::base::InitLogging(argv, android::base::StderrLogger);
+ return RUN_ALL_TESTS();
+}
diff --git a/crash_reporter/Android.mk b/crash_reporter/Android.mk
index bc023b0..4feb72a 100644
--- a/crash_reporter/Android.mk
+++ b/crash_reporter/Android.mk
@@ -19,7 +19,6 @@
crash_reporter_src := crash_collector.cc \
kernel_collector.cc \
kernel_warning_collector.cc \
- udev_collector.cc \
unclean_shutdown_collector.cc \
user_collector.cc
@@ -29,7 +28,6 @@
crash_reporter_logs_test.cc \
kernel_collector_test.cc \
testrunner.cc \
- udev_collector_test.cc \
unclean_shutdown_collector_test.cc \
user_collector_test.cc
diff --git a/crash_reporter/README.md b/crash_reporter/README.md
new file mode 100644
index 0000000..9ac0a86
--- /dev/null
+++ b/crash_reporter/README.md
@@ -0,0 +1,61 @@
+# crash_reporter
+
+`crash_reporter` is a deamon running on the device that saves the call stack of
+crashing programs. It makes use of the
+[Breakpad](https://chromium.googlesource.com/breakpad/breakpad/) library.
+
+During a build, Breakpad symbol files are generated for all binaries. They are
+packaged into a zip file when running `m dist`, so that a developer can upload
+them to the crash server.
+
+On a device, if the user has opted in to metrics and crash reporting, a
+Breakpad minidump is generated when an executable crashes, which is then
+uploaded to the crash server.
+
+On the crash server, it compares the minidump's signature to the symbol files
+that the developer has uploaded, and extracts and symbolizes the stack trace
+from the minidump.
+
+## SELinux policies
+
+In order to correctly generate a minidump, `crash_reporter` needs to be given
+the proper SELinux permissions for accessing the domain of the crashing
+executable. By default, `crash_reporter` has only been given access to a select
+number of system domains, such as `metricsd`, `weave`, and `update_engine`. If
+a developer wants their executable's crashes to be caught by `crash_reporter`,
+they will have to set their SELinux policies in their .te file to allow
+`crash_reporter` access to their domain. This can be done through a simple
+[macro](https://android.googlesource.com/device/generic/brillo/+/master/sepolicy/te_macros):
+
+ allow_crash_reporter(domain_name)
+
+Replace *domain_name* with whatever domain is assigned to the executable in
+the `file_contexts` file.
+
+## Configuration
+
+`crash_reporter` has a few different configuration options that have to be set.
+
+- Crashes are only handled and uploaded if analytics reporting is enabled,
+ either via the weave call to set `_metrics.enableAnalyticsReporting` or by
+ manually creating the file `/data/misc/metrics/enabled` (for testing only).
+- The `BRILLO_CRASH_SERVER` make variable should be set in the `product.mk`
+ file to the URL of the crash server. For Brillo builds, it is set
+ automatically through the product configuration. Setting this variable will
+ populate the `/etc/os-release.d/crash_server` file on the device, which is
+ read by `crash_sender`.
+- The `BRILLO_PRODUCT_ID` make variable should be set in the `product.mk` file
+ to the product's ID. For Brillo builds, it is set automatically through the
+ product configuration. Setting this variable will populate the
+ `/etc/os-release.d/product_id`, which is read by `crash_sender`.
+
+## Uploading crash reports in *eng* builds
+
+By default, crash reports are only uploaded to the server for production
+*user* and *userdebug* images. In *eng* builds, with crash reporting enabled
+the device will generate minidumps for any crashing executables but will not
+send them to the crash server. If a developer does want to force an upload,
+they can do so by issuing the command `SECONDS_SEND_SPREAD=5 FORCE_OFFICIAL=1
+crash_sender` from an ADB shell. This will send the report to the server, with
+the *image_type* field set to *force-official* so that these reports can be
+differentiated from normal reports.
diff --git a/crash_reporter/crash_reporter.cc b/crash_reporter/crash_reporter.cc
index 26ffa38..b69492a 100644
--- a/crash_reporter/crash_reporter.cc
+++ b/crash_reporter/crash_reporter.cc
@@ -35,10 +35,13 @@
#include "kernel_collector.h"
#include "kernel_warning_collector.h"
-#include "udev_collector.h"
#include "unclean_shutdown_collector.h"
#include "user_collector.h"
+#if !defined(__ANDROID__)
+#include "udev_collector.h"
+#endif
+
static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
static const char kKernelCrashDetected[] = "/var/run/kernel-crash-detected";
static const char kUncleanShutdownDetected[] =
@@ -176,6 +179,7 @@
return 0;
}
+#if !defined(__ANDROID__)
static int HandleUdevCrash(UdevCollector *udev_collector,
const std::string& udev_event) {
// Handle a crash indicated by a udev event.
@@ -189,6 +193,7 @@
return 1;
return 0;
}
+#endif
static int HandleKernelWarning(KernelWarningCollector
*kernel_warning_collector) {
@@ -249,7 +254,11 @@
DEFINE_bool(crash_test, false, "Crash test");
DEFINE_string(user, "", "User crash info (pid:signal:exec_name)");
DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
+
+#if !defined(__ANDROID__)
DEFINE_string(udev, "", "Udev event description (type:device:subsystem)");
+#endif
+
DEFINE_bool(kernel_warning, false, "Report collected kernel warning");
DEFINE_string(pid, "", "PID of crashing process");
DEFINE_string(uid, "", "UID of crashing process");
@@ -279,8 +288,11 @@
UncleanShutdownCollector unclean_shutdown_collector;
unclean_shutdown_collector.Initialize(CountUncleanShutdown,
IsFeedbackAllowed);
+
+#if !defined(__ANDROID__)
UdevCollector udev_collector;
udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
+#endif
KernelWarningCollector kernel_warning_collector;
kernel_warning_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
@@ -304,9 +316,11 @@
FLAGS_generate_kernel_signature);
}
+#if !defined(__ANDROID__)
if (!FLAGS_udev.empty()) {
return HandleUdevCrash(&udev_collector, FLAGS_udev);
}
+#endif
if (FLAGS_kernel_warning) {
return HandleKernelWarning(&kernel_warning_collector);
diff --git a/debuggerd/backtrace.cpp b/debuggerd/backtrace.cpp
index b46f8f4..b6916e5 100644
--- a/debuggerd/backtrace.cpp
+++ b/debuggerd/backtrace.cpp
@@ -67,8 +67,7 @@
_LOG(log, logtype::BACKTRACE, "\n----- end %d -----\n", pid);
}
-static void dump_thread(
- log_t* log, pid_t tid, bool attached, bool* detach_failed, int* total_sleep_time_usec) {
+static void dump_thread(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid) {
char path[PATH_MAX];
char threadnamebuf[1024];
char* threadname = NULL;
@@ -88,56 +87,25 @@
_LOG(log, logtype::BACKTRACE, "\n\"%s\" sysTid=%d\n", threadname ? threadname : "<unknown>", tid);
- if (!attached && ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) {
- _LOG(log, logtype::BACKTRACE, "Could not attach to thread: %s\n", strerror(errno));
- return;
- }
-
- if (!attached && wait_for_sigstop(tid, total_sleep_time_usec, detach_failed) == -1) {
- return;
- }
-
- std::unique_ptr<Backtrace> backtrace(Backtrace::Create(tid, BACKTRACE_CURRENT_THREAD));
+ std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
if (backtrace->Unwind(0)) {
dump_backtrace_to_log(backtrace.get(), log, " ");
} else {
ALOGE("Unwind failed: tid = %d", tid);
}
-
- if (!attached && ptrace(PTRACE_DETACH, tid, 0, 0) != 0) {
- ALOGE("ptrace detach from %d failed: %s\n", tid, strerror(errno));
- *detach_failed = true;
- }
}
-void dump_backtrace(int fd, int amfd, pid_t pid, pid_t tid, bool* detach_failed,
- int* total_sleep_time_usec) {
+void dump_backtrace(int fd, int amfd, BacktraceMap* map, pid_t pid, pid_t tid,
+ const std::set<pid_t>& siblings) {
log_t log;
log.tfd = fd;
log.amfd = amfd;
dump_process_header(&log, pid);
- dump_thread(&log, tid, true, detach_failed, total_sleep_time_usec);
+ dump_thread(&log, map, pid, tid);
- char task_path[64];
- snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
- DIR* d = opendir(task_path);
- if (d != NULL) {
- struct dirent* de = NULL;
- while ((de = readdir(d)) != NULL) {
- if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
- continue;
- }
-
- char* end;
- pid_t new_tid = strtoul(de->d_name, &end, 10);
- if (*end || new_tid == tid) {
- continue;
- }
-
- dump_thread(&log, new_tid, false, detach_failed, total_sleep_time_usec);
- }
- closedir(d);
+ for (pid_t sibling : siblings) {
+ dump_thread(&log, map, pid, sibling);
}
dump_process_footer(&log, pid);
diff --git a/debuggerd/backtrace.h b/debuggerd/backtrace.h
index da14cd4..98c433b 100644
--- a/debuggerd/backtrace.h
+++ b/debuggerd/backtrace.h
@@ -19,14 +19,17 @@
#include <sys/types.h>
+#include <set>
+
#include "utility.h"
class Backtrace;
+class BacktraceMap;
// Dumps a backtrace using a format similar to what Dalvik uses so that the result
// can be intermixed in a bug report.
-void dump_backtrace(int fd, int amfd, pid_t pid, pid_t tid, bool* detach_failed,
- int* total_sleep_time_usec);
+void dump_backtrace(int fd, int amfd, BacktraceMap* map, pid_t pid, pid_t tid,
+ const std::set<pid_t>& siblings);
/* Dumps the backtrace in the backtrace data structure to the log. */
void dump_backtrace_to_log(Backtrace* backtrace, log_t* log, const char* prefix);
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index 58b629b..8efbacc 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-#include <stdio.h>
-#include <errno.h>
-#include <signal.h>
-#include <pthread.h>
-#include <stdarg.h>
-#include <fcntl.h>
-#include <sys/types.h>
#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
#include <time.h>
#include <elf.h>
@@ -31,6 +31,8 @@
#include <sys/stat.h>
#include <sys/wait.h>
+#include <set>
+
#include <selinux/android.h>
#include <log/logger.h>
@@ -71,7 +73,7 @@
"* Process %d has been suspended while crashing.\n"
"* To attach gdbserver and start gdb, run this on the host:\n"
"*\n"
- "* gdbclient %d\n"
+ "* gdbclient.py -p %d\n"
"*\n"
"* Wait for gdb to start, then press the VOLUME DOWN key\n"
"* to let the process continue crashing.\n"
@@ -79,16 +81,13 @@
request.pid, request.tid);
// Wait for VOLUME DOWN.
- if (init_getevent() == 0) {
- while (true) {
- input_event e;
- if (get_event(&e, -1) == 0) {
- if (e.type == EV_KEY && e.code == KEY_VOLUMEDOWN && e.value == 0) {
- break;
- }
+ while (true) {
+ input_event e;
+ if (get_event(&e, -1) == 0) {
+ if (e.type == EV_KEY && e.code == KEY_VOLUMEDOWN && e.value == 0) {
+ break;
}
}
- uninit_getevent();
}
ALOGI("debuggerd resuming process %d", request.pid);
@@ -335,6 +334,180 @@
}
#endif
+static void ptrace_siblings(pid_t pid, pid_t main_tid, std::set<pid_t>& tids) {
+ char task_path[64];
+
+ snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
+
+ std::unique_ptr<DIR, int (*)(DIR*)> d(opendir(task_path), closedir);
+
+ // Bail early if the task directory cannot be opened.
+ if (!d) {
+ ALOGE("debuggerd: failed to open /proc/%d/task: %s", pid, strerror(errno));
+ return;
+ }
+
+ struct dirent* de;
+ while ((de = readdir(d.get())) != NULL) {
+ // Ignore "." and "..".
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
+ continue;
+ }
+
+ char* end;
+ pid_t tid = strtoul(de->d_name, &end, 10);
+ if (*end) {
+ continue;
+ }
+
+ if (tid == main_tid) {
+ continue;
+ }
+
+ if (ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) {
+ ALOGE("debuggerd: ptrace attach to %d failed: %s", tid, strerror(errno));
+ continue;
+ }
+
+ tids.insert(tid);
+ }
+}
+
+static bool perform_dump(const debugger_request_t& request, int fd, int tombstone_fd,
+ BacktraceMap* backtrace_map, const std::set<pid_t>& siblings) {
+ if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) {
+ ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno));
+ return false;
+ }
+
+ int total_sleep_time_usec = 0;
+ while (true) {
+ int signal = wait_for_signal(request.tid, &total_sleep_time_usec);
+ switch (signal) {
+ case -1:
+ ALOGE("debuggerd: timed out waiting for signal");
+ return false;
+
+ case SIGSTOP:
+ if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
+ ALOGV("debuggerd: stopped -- dumping to tombstone");
+ engrave_tombstone(tombstone_fd, backtrace_map, request.pid, request.tid, siblings, signal,
+ request.original_si_code, request.abort_msg_address);
+ } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) {
+ ALOGV("debuggerd: stopped -- dumping to fd");
+ dump_backtrace(fd, -1, backtrace_map, request.pid, request.tid, siblings);
+ } else {
+ ALOGV("debuggerd: stopped -- continuing");
+ if (ptrace(PTRACE_CONT, request.tid, 0, 0) != 0) {
+ ALOGE("debuggerd: ptrace continue failed: %s", strerror(errno));
+ return false;
+ }
+ continue; // loop again
+ }
+ break;
+
+ case SIGABRT:
+ case SIGBUS:
+ case SIGFPE:
+ case SIGILL:
+ case SIGSEGV:
+#ifdef SIGSTKFLT
+ case SIGSTKFLT:
+#endif
+ case SIGTRAP:
+ ALOGV("stopped -- fatal signal\n");
+ // Send a SIGSTOP to the process to make all of
+ // the non-signaled threads stop moving. Without
+ // this we get a lot of "ptrace detach failed:
+ // No such process".
+ kill(request.pid, SIGSTOP);
+ engrave_tombstone(tombstone_fd, backtrace_map, request.pid, request.tid, siblings, signal,
+ request.original_si_code, request.abort_msg_address);
+ break;
+
+ default:
+ ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal);
+ break;
+ }
+ break;
+ }
+
+ return true;
+}
+
+static bool drop_privileges() {
+ if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) {
+ ALOGE("debuggerd: failed to setresgid");
+ return false;
+ }
+
+ if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) {
+ ALOGE("debuggerd: failed to setresuid");
+ return false;
+ }
+
+ return true;
+}
+
+static bool fork_signal_sender(int* in_fd, int* out_fd, pid_t* sender_pid, pid_t target_pid) {
+ int input_pipe[2];
+ int output_pipe[2];
+ if (pipe(input_pipe) != 0) {
+ ALOGE("debuggerd: failed to create input pipe for signal sender: %s", strerror(errno));
+ return false;
+ }
+
+ if (pipe(output_pipe) != 0) {
+ close(input_pipe[0]);
+ close(input_pipe[1]);
+ ALOGE("debuggerd: failed to create output pipe for signal sender: %s", strerror(errno));
+ return false;
+ }
+
+ pid_t fork_pid = fork();
+ if (fork_pid == -1) {
+ ALOGE("debuggerd: failed to initialize signal sender: fork failed: %s", strerror(errno));
+ return false;
+ } else if (fork_pid == 0) {
+ close(input_pipe[1]);
+ close(output_pipe[0]);
+ auto wait = [=]() {
+ char buf[1];
+ if (TEMP_FAILURE_RETRY(read(input_pipe[0], buf, 1)) != 1) {
+ ALOGE("debuggerd: signal sender failed to read from pipe");
+ exit(1);
+ }
+ };
+ auto notify_done = [=]() {
+ if (TEMP_FAILURE_RETRY(write(output_pipe[1], "", 1)) != 1) {
+ ALOGE("debuggerd: signal sender failed to write to pipe");
+ exit(1);
+ }
+ };
+
+ wait();
+ if (kill(target_pid, SIGSTOP) != 0) {
+ ALOGE("debuggerd: failed to stop target '%d': %s", target_pid, strerror(errno));
+ }
+ notify_done();
+
+ wait();
+ if (kill(target_pid, SIGCONT) != 0) {
+ ALOGE("debuggerd: failed to resume target '%d': %s", target_pid, strerror(errno));
+ }
+ notify_done();
+
+ exit(0);
+ } else {
+ close(input_pipe[0]);
+ close(output_pipe[1]);
+ *in_fd = input_pipe[1];
+ *out_fd = output_pipe[0];
+ *sender_pid = fork_pid;
+ return true;
+ }
+}
+
static void handle_request(int fd) {
ALOGV("handle_request(%d)\n", fd);
@@ -405,117 +578,88 @@
// ensure that it can run as soon as we call PTRACE_CONT below.
// See details in bionic/libc/linker/debugger.c, in function
// debugger_signal_handler().
- if (ptrace(PTRACE_ATTACH, request.tid, 0, 0)) {
- ALOGE("debuggerd: ptrace attach failed: %s\n", strerror(errno));
+
+ // Attach to the target process.
+ if (ptrace(PTRACE_ATTACH, request.tid, 0, 0) != 0) {
+ ALOGE("debuggerd: ptrace attach failed: %s", strerror(errno));
exit(1);
}
+ // Don't attach to the sibling threads if we want to attach gdb.
+ // Supposedly, it makes the process less reliable.
+ bool attach_gdb = should_attach_gdb(&request);
+ int signal_in_fd = -1;
+ int signal_out_fd = -1;
+ pid_t signal_pid = 0;
+ if (attach_gdb) {
+ // Open all of the input devices we need to listen for VOLUMEDOWN before dropping privileges.
+ if (init_getevent() != 0) {
+ ALOGE("debuggerd: failed to initialize input device, not waiting for gdb");
+ attach_gdb = false;
+ }
+
+ // Fork a process that stays root, and listens on a pipe to pause and resume the target.
+ if (!fork_signal_sender(&signal_in_fd, &signal_out_fd, &signal_pid, request.pid)) {
+ attach_gdb = false;
+ }
+ }
+
+ auto notify_signal_sender = [=]() {
+ char buf[1];
+ if (TEMP_FAILURE_RETRY(write(signal_in_fd, "", 1)) != 1) {
+ ALOGE("debuggerd: failed to notify signal process: %s", strerror(errno));
+ } else if (TEMP_FAILURE_RETRY(read(signal_out_fd, buf, 1)) != 1) {
+ ALOGE("debuggerd: failed to read response from signal process: %s", strerror(errno));
+ }
+ };
+
+ std::set<pid_t> siblings;
+ if (!attach_gdb) {
+ ptrace_siblings(request.pid, request.tid, siblings);
+ }
+
// Generate the backtrace map before dropping privileges.
std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(request.pid));
+ bool succeeded = false;
+
// Now that we've done everything that requires privileges, we can drop them.
- if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) {
- ALOGE("debuggerd: failed to setresgid");
- exit(1);
- }
-
- if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) {
- ALOGE("debuggerd: failed to setresuid");
- exit(1);
- }
-
- bool detach_failed = false;
- bool tid_unresponsive = false;
- bool attach_gdb = should_attach_gdb(&request);
- if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) {
- ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno));
- exit(1);
- }
-
- int total_sleep_time_usec = 0;
- while (true) {
- int signal = wait_for_sigstop(request.tid, &total_sleep_time_usec, &detach_failed);
- if (signal == -1) {
- tid_unresponsive = true;
- break;
- }
-
- switch (signal) {
- case SIGSTOP:
- if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
- ALOGV("stopped -- dumping to tombstone\n");
- engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal,
- request.original_si_code, request.abort_msg_address, true,
- &detach_failed, &total_sleep_time_usec);
- } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) {
- ALOGV("stopped -- dumping to fd\n");
- dump_backtrace(fd, -1, request.pid, request.tid, &detach_failed, &total_sleep_time_usec);
- } else {
- ALOGV("stopped -- continuing\n");
- status = ptrace(PTRACE_CONT, request.tid, 0, 0);
- if (status) {
- ALOGE("debuggerd: ptrace continue failed: %s\n", strerror(errno));
- }
- continue; // loop again
+ if (drop_privileges()) {
+ succeeded = perform_dump(request, fd, tombstone_fd, backtrace_map.get(), siblings);
+ if (succeeded) {
+ if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
+ if (!tombstone_path.empty()) {
+ write(fd, tombstone_path.c_str(), tombstone_path.length());
}
- break;
-
- case SIGABRT:
- case SIGBUS:
- case SIGFPE:
- case SIGILL:
- case SIGSEGV:
-#ifdef SIGSTKFLT
- case SIGSTKFLT:
-#endif
- case SIGTRAP:
- ALOGV("stopped -- fatal signal\n");
- // Send a SIGSTOP to the process to make all of
- // the non-signaled threads stop moving. Without
- // this we get a lot of "ptrace detach failed:
- // No such process".
- kill(request.pid, SIGSTOP);
- // don't dump sibling threads when attaching to GDB because it
- // makes the process less reliable, apparently...
- engrave_tombstone(tombstone_fd, backtrace_map.get(), request.pid, request.tid, signal,
- request.original_si_code, request.abort_msg_address, !attach_gdb,
- &detach_failed, &total_sleep_time_usec);
- break;
-
- default:
- ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal);
- break;
+ }
}
- break;
- }
- if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
- if (!tombstone_path.empty()) {
- write(fd, tombstone_path.c_str(), tombstone_path.length());
- }
- }
-
- if (!tid_unresponsive) {
- ALOGV("detaching");
if (attach_gdb) {
- // stop the process so we can debug
- kill(request.pid, SIGSTOP);
- }
- if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) {
- ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno));
- detach_failed = true;
- } else if (attach_gdb) {
- // if debug.db.uid is set, its value indicates if we should wait
- // for user action for the crashing process.
- // in this case, we log a message and turn the debug LED on
- // waiting for a gdb connection (for instance)
- wait_for_user_action(request);
+ // Tell the signal process to send SIGSTOP to the target.
+ notify_signal_sender();
}
}
- // Resume the stopped process so it can crash in peace, and exit.
- kill(request.pid, SIGCONT);
- exit(0);
+ if (ptrace(PTRACE_DETACH, request.tid, 0, 0) != 0) {
+ ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno));
+ }
+
+ for (pid_t sibling : siblings) {
+ ptrace(PTRACE_DETACH, sibling, 0, 0);
+ }
+
+ // Wait for gdb, if requested.
+ if (attach_gdb && succeeded) {
+ wait_for_user_action(request);
+
+ // Tell the signal process to send SIGCONT to the target.
+ notify_signal_sender();
+
+ uninit_getevent();
+ waitpid(signal_pid, nullptr, 0);
+ }
+
+ exit(!succeeded);
}
static int do_server() {
diff --git a/debuggerd/tombstone.cpp b/debuggerd/tombstone.cpp
index b2f203d..dda6677 100644
--- a/debuggerd/tombstone.cpp
+++ b/debuggerd/tombstone.cpp
@@ -328,6 +328,33 @@
return addr_str;
}
+static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) {
+ if (address == 0) {
+ return;
+ }
+
+ address += sizeof(size_t); // Skip the buffer length.
+
+ char msg[512];
+ memset(msg, 0, sizeof(msg));
+ char* p = &msg[0];
+ while (p < &msg[sizeof(msg)]) {
+ word_t data;
+ size_t len = sizeof(word_t);
+ if (!backtrace->ReadWord(address, &data)) {
+ break;
+ }
+ address += sizeof(word_t);
+
+ while (len > 0 && (*p++ = (data >> (sizeof(word_t) - len) * 8) & 0xff) != 0) {
+ len--;
+ }
+ }
+ msg[sizeof(msg) - 1] = '\0';
+
+ _LOG(log, logtype::HEADER, "Abort message: '%s'\n", msg);
+}
+
static void dump_all_maps(Backtrace* backtrace, BacktraceMap* map, log_t* log, pid_t tid) {
bool print_fault_address_marker = false;
uintptr_t addr = 0;
@@ -416,67 +443,37 @@
}
}
-// Return true if some thread is not detached cleanly
-static bool dump_sibling_thread_report(
- log_t* log, pid_t pid, pid_t tid, int* total_sleep_time_usec, BacktraceMap* map) {
- char task_path[64];
-
- snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
-
- DIR* d = opendir(task_path);
- // Bail early if the task directory cannot be opened
- if (d == NULL) {
- ALOGE("Cannot open /proc/%d/task\n", pid);
- return false;
- }
-
- bool detach_failed = false;
- struct dirent* de;
- while ((de = readdir(d)) != NULL) {
- // Ignore "." and ".."
- if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
- continue;
- }
-
- // The main thread at fault has been handled individually
- char* end;
- pid_t new_tid = strtoul(de->d_name, &end, 10);
- if (*end || new_tid == tid) {
- continue;
- }
-
- // Skip this thread if cannot ptrace it
- if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) {
- ALOGE("ptrace attach to %d failed: %s\n", new_tid, strerror(errno));
- continue;
- }
-
- if (wait_for_sigstop(new_tid, total_sleep_time_usec, &detach_failed) == -1) {
- continue;
- }
-
- log->current_tid = new_tid;
+static void dump_thread(log_t* log, pid_t pid, pid_t tid, BacktraceMap* map, int signal,
+ int si_code, uintptr_t abort_msg_address, bool primary_thread) {
+ log->current_tid = tid;
+ if (!primary_thread) {
_LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
- dump_thread_info(log, pid, new_tid);
+ }
+ dump_thread_info(log, pid, tid);
- dump_registers(log, new_tid);
- std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, new_tid, map));
- if (backtrace->Unwind(0)) {
- dump_backtrace_and_stack(backtrace.get(), log);
- } else {
- ALOGE("Unwind of sibling failed: pid = %d, tid = %d", pid, new_tid);
- }
+ if (signal) {
+ dump_signal_info(log, tid, signal, si_code);
+ }
- log->current_tid = log->crashed_tid;
+ std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
+ if (primary_thread) {
+ dump_abort_message(backtrace.get(), log, abort_msg_address);
+ }
+ dump_registers(log, tid);
+ if (backtrace->Unwind(0)) {
+ dump_backtrace_and_stack(backtrace.get(), log);
+ } else {
+ ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid);
+ }
- if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) {
- ALOGE("ptrace detach from %d failed: %s\n", new_tid, strerror(errno));
- detach_failed = true;
+ if (primary_thread) {
+ dump_memory_and_code(log, backtrace.get());
+ if (map) {
+ dump_all_maps(backtrace.get(), map, log, tid);
}
}
- closedir(d);
- return detach_failed;
+ log->current_tid = log->crashed_tid;
}
// Reads the contents of the specified log device, filters out the entries
@@ -605,36 +602,10 @@
dump_log_file(log, pid, "main", tail);
}
-static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) {
- if (address == 0) {
- return;
- }
-
- address += sizeof(size_t); // Skip the buffer length.
-
- char msg[512];
- memset(msg, 0, sizeof(msg));
- char* p = &msg[0];
- while (p < &msg[sizeof(msg)]) {
- word_t data;
- size_t len = sizeof(word_t);
- if (!backtrace->ReadWord(address, &data)) {
- break;
- }
- address += sizeof(word_t);
-
- while (len > 0 && (*p++ = (data >> (sizeof(word_t) - len) * 8) & 0xff) != 0)
- len--;
- }
- msg[sizeof(msg) - 1] = '\0';
-
- _LOG(log, logtype::HEADER, "Abort message: '%s'\n", msg);
-}
-
// Dumps all information about the specified pid to the tombstone.
-static bool dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid, int signal, int si_code,
- uintptr_t abort_msg_address, bool dump_sibling_threads,
- int* total_sleep_time_usec) {
+static void dump_crash(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid,
+ const std::set<pid_t>& siblings, int signal, int si_code,
+ uintptr_t abort_msg_address) {
// don't copy log messages to tombstone unless this is a dev device
char value[PROPERTY_VALUE_MAX];
property_get("ro.debuggable", value, "0");
@@ -653,32 +624,15 @@
_LOG(log, logtype::HEADER,
"*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
dump_header_info(log);
- dump_thread_info(log, pid, tid);
-
- if (signal) {
- dump_signal_info(log, tid, signal, si_code);
- }
-
- std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
- dump_abort_message(backtrace.get(), log, abort_msg_address);
- dump_registers(log, tid);
- if (backtrace->Unwind(0)) {
- dump_backtrace_and_stack(backtrace.get(), log);
- } else {
- ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid);
- }
- dump_memory_and_code(log, backtrace.get());
- if (map) {
- dump_all_maps(backtrace.get(), map, log, tid);
- }
-
+ dump_thread(log, pid, tid, map, signal, si_code, abort_msg_address, true);
if (want_logs) {
dump_logs(log, pid, 5);
}
- bool detach_failed = false;
- if (dump_sibling_threads) {
- detach_failed = dump_sibling_thread_report(log, pid, tid, total_sleep_time_usec, map);
+ if (!siblings.empty()) {
+ for (pid_t sibling : siblings) {
+ dump_thread(log, pid, sibling, map, 0, 0, 0, false);
+ }
}
if (want_logs) {
@@ -694,7 +648,7 @@
TEMP_FAILURE_RETRY( read(log->amfd, &eodMarker, 1) );
}
- return detach_failed;
+ return;
}
// open_tombstone - find an available tombstone slot, if any, of the
@@ -780,16 +734,15 @@
return amfd;
}
-void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal,
- int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads,
- bool* detach_failed, int* total_sleep_time_usec) {
+void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid,
+ const std::set<pid_t>& siblings, int signal, int original_si_code,
+ uintptr_t abort_msg_address) {
log_t log;
log.current_tid = tid;
log.crashed_tid = tid;
if (tombstone_fd < 0) {
ALOGE("debuggerd: skipping tombstone write, nothing to do.\n");
- *detach_failed = false;
return;
}
@@ -798,8 +751,7 @@
// being closed.
int amfd = activity_manager_connect();
log.amfd = amfd;
- *detach_failed = dump_crash(&log, map, pid, tid, signal, original_si_code, abort_msg_address,
- dump_sibling_threads, total_sleep_time_usec);
+ dump_crash(&log, map, pid, tid, siblings, signal, original_si_code, abort_msg_address);
// This file descriptor can be -1, any error is ignored.
close(amfd);
diff --git a/debuggerd/tombstone.h b/debuggerd/tombstone.h
index 5f2d239..2b8b8be 100644
--- a/debuggerd/tombstone.h
+++ b/debuggerd/tombstone.h
@@ -20,6 +20,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <sys/types.h>
+#include <set>
#include <string>
class BacktraceMap;
@@ -30,10 +31,9 @@
*/
int open_tombstone(std::string* path);
-/* Creates a tombstone file and writes the crash dump to it.
- * Returns the path of the tombstone, which must be freed using free(). */
-void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid, int signal,
- int original_si_code, uintptr_t abort_msg_address, bool dump_sibling_threads,
- bool* detach_failed, int* total_sleep_time_usec);
+/* Creates a tombstone file and writes the crash dump to it. */
+void engrave_tombstone(int tombstone_fd, BacktraceMap* map, pid_t pid, pid_t tid,
+ const std::set<pid_t>& siblings, int signal, int original_si_code,
+ uintptr_t abort_msg_address);
#endif // _DEBUGGERD_TOMBSTONE_H
diff --git a/debuggerd/utility.cpp b/debuggerd/utility.cpp
index ce214f9..cd252ce 100644
--- a/debuggerd/utility.cpp
+++ b/debuggerd/utility.cpp
@@ -30,8 +30,8 @@
#include <backtrace/Backtrace.h>
#include <log/log.h>
-const int SLEEP_TIME_USEC = 50000; // 0.05 seconds
-const int MAX_TOTAL_SLEEP_USEC = 10000000; // 10 seconds
+constexpr int SLEEP_TIME_USEC = 50000; // 0.05 seconds
+constexpr int MAX_TOTAL_SLEEP_USEC = 10000000; // 10 seconds
// Whitelist output desired in the logcat output.
bool is_allowed_in_logcat(enum logtype ltype) {
@@ -78,14 +78,13 @@
}
}
-int wait_for_sigstop(pid_t tid, int* total_sleep_time_usec, bool* detach_failed) {
- bool allow_dead_tid = false;
- for (;;) {
+int wait_for_signal(pid_t tid, int* total_sleep_time_usec) {
+ while (true) {
int status;
pid_t n = TEMP_FAILURE_RETRY(waitpid(tid, &status, __WALL | WNOHANG));
if (n == -1) {
ALOGE("waitpid failed: tid %d, %s", tid, strerror(errno));
- break;
+ return -1;
} else if (n == tid) {
if (WIFSTOPPED(status)) {
return WSTOPSIG(status);
@@ -93,29 +92,18 @@
ALOGE("unexpected waitpid response: n=%d, status=%08x\n", n, status);
// This is the only circumstance under which we can allow a detach
// to fail with ESRCH, which indicates the tid has exited.
- allow_dead_tid = true;
- break;
+ return -1;
}
}
if (*total_sleep_time_usec > MAX_TOTAL_SLEEP_USEC) {
ALOGE("timed out waiting for stop signal: tid=%d", tid);
- break;
+ return -1;
}
usleep(SLEEP_TIME_USEC);
*total_sleep_time_usec += SLEEP_TIME_USEC;
}
-
- if (ptrace(PTRACE_DETACH, tid, 0, 0) != 0) {
- if (allow_dead_tid && errno == ESRCH) {
- ALOGE("tid exited before attach completed: tid %d", tid);
- } else {
- *detach_failed = true;
- ALOGE("detach failed: tid %d, %s", tid, strerror(errno));
- }
- }
- return -1;
}
#define MEMORY_BYTES_TO_DUMP 256
diff --git a/debuggerd/utility.h b/debuggerd/utility.h
index 8bef192..ed08ddc 100644
--- a/debuggerd/utility.h
+++ b/debuggerd/utility.h
@@ -74,7 +74,7 @@
void _LOG(log_t* log, logtype ltype, const char *fmt, ...)
__attribute__ ((format(printf, 3, 4)));
-int wait_for_sigstop(pid_t, int*, bool*);
+int wait_for_signal(pid_t tid, int* total_sleep_time_usec);
void dump_memory(log_t* log, Backtrace* backtrace, uintptr_t addr, const char* fmt, ...);
diff --git a/fastboot/.clang-format b/fastboot/.clang-format
new file mode 100644
index 0000000..6737535
--- /dev/null
+++ b/fastboot/.clang-format
@@ -0,0 +1,12 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+UseTab: Never
+PenaltyExcessCharacter: 32
diff --git a/fastboot/engine.cpp b/fastboot/engine.cpp
index ac5a17a..47567c0 100644
--- a/fastboot/engine.cpp
+++ b/fastboot/engine.cpp
@@ -157,17 +157,17 @@
a->msg = mkmsg("writing '%s'", ptn);
}
-void fb_queue_flash_sparse(const char *ptn, struct sparse_file *s, unsigned sz)
-{
+void fb_queue_flash_sparse(const char* ptn, struct sparse_file* s, unsigned sz, size_t current,
+ size_t total) {
Action *a;
a = queue_action(OP_DOWNLOAD_SPARSE, "");
a->data = s;
a->size = 0;
- a->msg = mkmsg("sending sparse '%s' (%d KB)", ptn, sz / 1024);
+ a->msg = mkmsg("sending sparse '%s' %zu/%zu (%d KB)", ptn, current, total, sz / 1024);
a = queue_action(OP_COMMAND, "flash:%s", ptn);
- a->msg = mkmsg("writing '%s'", ptn);
+ a->msg = mkmsg("writing '%s' %zu/%zu", ptn, current, total);
}
static int match(const char* str, const char** value, unsigned count) {
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index bd17485..4573da0 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -43,6 +43,8 @@
#include <sys/types.h>
#include <unistd.h>
#include <functional>
+#include <utility>
+#include <vector>
#include <android-base/parseint.h>
#include <android-base/strings.h>
@@ -705,13 +707,22 @@
sparse_file** s;
switch (buf->type) {
- case FB_BUFFER_SPARSE:
+ case FB_BUFFER_SPARSE: {
+ std::vector<std::pair<sparse_file*, int64_t>> sparse_files;
s = reinterpret_cast<sparse_file**>(buf->data);
while (*s) {
int64_t sz = sparse_file_len(*s, true, false);
- fb_queue_flash_sparse(pname, *s++, sz);
+ sparse_files.emplace_back(*s, sz);
+ ++s;
+ }
+
+ for (size_t i = 0; i < sparse_files.size(); ++i) {
+ const auto& pair = sparse_files[i];
+ fb_queue_flash_sparse(pname, pair.first, pair.second, i + 1, sparse_files.size());
}
break;
+ }
+
case FB_BUFFER:
fb_queue_flash(pname, buf->data, buf->sz);
break;
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index acfbc13..1932bab 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -51,7 +51,8 @@
/* engine.c - high level command queue engine */
bool fb_getvar(Transport* transport, const std::string& key, std::string* value);
void fb_queue_flash(const char *ptn, void *data, uint32_t sz);
-void fb_queue_flash_sparse(const char *ptn, struct sparse_file *s, uint32_t sz);
+void fb_queue_flash_sparse(const char* ptn, struct sparse_file* s, uint32_t sz, size_t current,
+ size_t total);
void fb_queue_erase(const char *ptn);
void fb_queue_format(const char *ptn, int skip_if_not_supported, int32_t max_chunk_sz);
void fb_queue_require(const char *prod, const char *var, bool invert,
diff --git a/include/log/log.h b/include/log/log.h
index 3d9240d..1bd9165 100644
--- a/include/log/log.h
+++ b/include/log/log.h
@@ -585,14 +585,6 @@
(__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE) != 0)
#endif
-// TODO: remove these prototypes and their users
-#define android_writevLog(vec,num) do{}while(0)
-#define android_write1Log(str,len) do{}while (0)
-#define android_setMinPriority(tag, prio) do{}while(0)
-//#define android_logToCallback(func) do{}while(0)
-#define android_logToFile(tag, file) (0)
-#define android_logToFd(tag, fd) (0)
-
typedef enum log_id {
LOG_ID_MIN = 0,
diff --git a/include/log/logd.h b/include/log/logd.h
index b7aedaf..b271602 100644
--- a/include/log/logd.h
+++ b/include/log/logd.h
@@ -45,6 +45,7 @@
int __android_log_bswrite(int32_t tag, const char *payload);
int __android_log_security_bwrite(int32_t tag, const void *payload, size_t len);
+int __android_log_security_bswrite(int32_t tag, const char *payload);
#ifdef __cplusplus
}
diff --git a/include/ziparchive/zip_archive_stream_entry.h b/include/ziparchive/zip_archive_stream_entry.h
new file mode 100644
index 0000000..a40b799
--- /dev/null
+++ b/include/ziparchive/zip_archive_stream_entry.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+// Read-only stream access to Zip archives entries.
+#ifndef LIBZIPARCHIVE_ZIPARCHIVESTREAMENTRY_H_
+#define LIBZIPARCHIVE_ZIPARCHIVESTREAMENTRY_H_
+
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+class ZipArchiveStreamEntry {
+ public:
+ virtual ~ZipArchiveStreamEntry() {}
+
+ virtual const std::vector<uint8_t>* Read() = 0;
+
+ virtual bool Verify() = 0;
+
+ static ZipArchiveStreamEntry* Create(ZipArchiveHandle handle, const ZipEntry& entry);
+ static ZipArchiveStreamEntry* CreateRaw(ZipArchiveHandle handle, const ZipEntry& entry);
+
+ protected:
+ ZipArchiveStreamEntry(ZipArchiveHandle handle) : handle_(handle) {}
+
+ virtual bool Init(const ZipEntry& entry);
+
+ ZipArchiveHandle handle_;
+
+ uint32_t crc32_;
+};
+
+#endif // LIBZIPARCHIVE_ZIPARCHIVESTREAMENTRY_H_
diff --git a/include/ziparchive/zip_writer.h b/include/ziparchive/zip_writer.h
index 0efade8..0b6ede4 100644
--- a/include/ziparchive/zip_writer.h
+++ b/include/ziparchive/zip_writer.h
@@ -87,11 +87,27 @@
int32_t StartEntry(const char* path, size_t flags);
/**
+ * Starts a new zip entry with the given path and flags, where the
+ * entry will be aligned to the given alignment.
+ * Flags can only be ZipWriter::kCompress. Using the flag ZipWriter::kAlign32
+ * will result in an error.
+ * Subsequent calls to WriteBytes(const void*, size_t) will add data to this entry.
+ * Returns 0 on success, and an error value < 0 on failure.
+ */
+ int32_t StartAlignedEntry(const char* path, size_t flags, uint32_t alignment);
+
+ /**
* Same as StartEntry(const char*, size_t), but sets a last modified time for the entry.
*/
int32_t StartEntryWithTime(const char* path, size_t flags, time_t time);
/**
+ * Same as StartAlignedEntry(const char*, size_t), but sets a last modified time for the entry.
+ */
+ int32_t StartAlignedEntryWithTime(const char* path, size_t flags, time_t time,
+ uint32_t alignment);
+
+ /**
* Writes bytes to the zip file for the previously started zip entry.
* Returns 0 on success, and an error value < 0 on failure.
*/
diff --git a/init/log.cpp b/init/log.cpp
index a72906b..ace9fd7 100644
--- a/init/log.cpp
+++ b/init/log.cpp
@@ -27,6 +27,8 @@
static void init_klog_vwrite(int level, const char* fmt, va_list ap) {
static const char* tag = basename(getprogname());
+ if (level > klog_get_level()) return;
+
// The kernel's printk buffer is only 1024 bytes.
// TODO: should we automatically break up long lines into multiple lines?
// Or we could log but with something like "..." at the end?
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 806608e..5c1ae79 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -113,18 +113,6 @@
return check_mac_perms(ctl_name, sctx, cr);
}
-/*
- * Checks permissions for setting system properties.
- * Returns 1 if uid allowed, 0 otherwise.
- */
-static int check_perms(const char *name, char *sctx, struct ucred *cr)
-{
- if(!strncmp(name, "ro.", 3))
- name +=3;
-
- return check_mac_perms(name, sctx, cr);
-}
-
std::string property_get(const char* name) {
char value[PROP_VALUE_MAX] = {0};
__system_property_get(name, value);
@@ -312,7 +300,7 @@
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
- if (check_perms(msg.name, source_ctx, &cr)) {
+ if (check_mac_perms(msg.name, source_ctx, &cr)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
diff --git a/libbacktrace/backtrace_test.cpp b/libbacktrace/backtrace_test.cpp
index 23636db..7d829fe 100644
--- a/libbacktrace/backtrace_test.cpp
+++ b/libbacktrace/backtrace_test.cpp
@@ -1156,7 +1156,7 @@
int fd = open(tmp_so_name, O_RDONLY);
ASSERT_TRUE(fd != -1);
- void* map = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ void* map = mmap(NULL, map_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
ASSERT_TRUE(map != MAP_FAILED);
close(fd);
ASSERT_TRUE(unlink(tmp_so_name) != -1);
@@ -1206,7 +1206,7 @@
exit(0);
}
- void* map = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ void* map = mmap(NULL, map_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
fprintf(stderr, "Failed to map in memory: %s\n", strerror(errno));
unlink(tmp_so_name);
diff --git a/libcutils/klog.c b/libcutils/klog.c
index 710dc66..7402903 100644
--- a/libcutils/klog.c
+++ b/libcutils/klog.c
@@ -62,6 +62,7 @@
}
void klog_write(int level, const char* fmt, ...) {
+ if (level > klog_level) return;
char buf[LOG_BUF_MAX];
va_list ap;
va_start(ap, fmt);
diff --git a/liblog/logd_write.c b/liblog/logd_write.c
index 5406c50..55b965b 100644
--- a/liblog/logd_write.c
+++ b/liblog/logd_write.c
@@ -668,3 +668,25 @@
return write_to_log(LOG_ID_EVENTS, vec, 4);
}
+
+/*
+ * Like __android_log_security_bwrite, but used for writing strings to the
+ * security log.
+ */
+int __android_log_security_bswrite(int32_t tag, const char *payload)
+{
+ struct iovec vec[4];
+ char type = EVENT_TYPE_STRING;
+ uint32_t len = strlen(payload);
+
+ vec[0].iov_base = &tag;
+ vec[0].iov_len = sizeof(tag);
+ vec[1].iov_base = &type;
+ vec[1].iov_len = sizeof(type);
+ vec[2].iov_base = &len;
+ vec[2].iov_len = sizeof(len);
+ vec[3].iov_base = (void*)payload;
+ vec[3].iov_len = len;
+
+ return write_to_log(LOG_ID_SECURITY, vec, 4);
+}
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index 403a4f4..6e6b0b9 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -48,7 +48,9 @@
"libm.so:"
"libOpenMAXAL.so:"
"libOpenSLES.so:"
+ "libRS.so:"
"libstdc++.so:"
+ "libwebviewchromium_plat_support.so:"
"libz.so";
class LibraryNamespaces {
diff --git a/libnetutils/ifc_utils.c b/libnetutils/ifc_utils.c
index 956ed30..e0a9f7f 100644
--- a/libnetutils/ifc_utils.c
+++ b/libnetutils/ifc_utils.c
@@ -253,6 +253,7 @@
int prefixlen) {
int ifindex, s, len, ret;
struct sockaddr_storage ss;
+ int saved_errno;
void *addr;
size_t addrlen;
struct {
@@ -317,15 +318,21 @@
memcpy(RTA_DATA(rta), addr, addrlen);
s = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
- if (send(s, &req, req.n.nlmsg_len, 0) < 0) {
- close(s);
+ if (s < 0) {
return -errno;
}
+ if (send(s, &req, req.n.nlmsg_len, 0) < 0) {
+ saved_errno = errno;
+ close(s);
+ return -saved_errno;
+ }
+
len = recv(s, buf, sizeof(buf), 0);
+ saved_errno = errno;
close(s);
if (len < 0) {
- return -errno;
+ return -saved_errno;
}
// Parse the acknowledgement to find the return code.
diff --git a/libziparchive/Android.mk b/libziparchive/Android.mk
index 8a4921f..056b3e1 100644
--- a/libziparchive/Android.mk
+++ b/libziparchive/Android.mk
@@ -15,34 +15,46 @@
LOCAL_PATH := $(call my-dir)
-source_files := zip_archive.cc zip_writer.cc
-test_files := zip_archive_test.cc zip_writer_test.cc entry_name_utils_test.cc
+libziparchive_source_files := \
+ zip_archive.cc \
+ zip_archive_stream_entry.cc \
+ zip_writer.cc \
+
+libziparchive_test_files := \
+ entry_name_utils_test.cc \
+ zip_archive_test.cc \
+ zip_writer_test.cc \
# ZLIB_CONST turns on const for input buffers, which is pretty standard.
-common_c_flags := -Werror -Wall -DZLIB_CONST
+libziparchive_common_c_flags := \
+ -DZLIB_CONST \
+ -Werror \
+ -Wall \
# Incorrectly warns when C++11 empty brace {} initializer is used.
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61489
-common_cpp_flags := -Wold-style-cast -Wno-missing-field-initializers
+libziparchive_common_cpp_flags := \
+ -Wold-style-cast \
+ -Wno-missing-field-initializers \
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
-LOCAL_SRC_FILES := ${source_files}
+LOCAL_SRC_FILES := $(libziparchive_source_files)
LOCAL_STATIC_LIBRARIES := libz
LOCAL_SHARED_LIBRARIES := libutils libbase
LOCAL_MODULE:= libziparchive
-LOCAL_CFLAGS := $(common_c_flags)
-LOCAL_CPPFLAGS := $(common_cpp_flags)
+LOCAL_CFLAGS := $(libziparchive_common_c_flags)
+LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags)
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
-LOCAL_SRC_FILES := ${source_files}
+LOCAL_SRC_FILES := $(libziparchive_source_files)
LOCAL_STATIC_LIBRARIES := libz libutils libbase
LOCAL_MODULE:= libziparchive-host
-LOCAL_CFLAGS := $(common_c_flags)
+LOCAL_CFLAGS := $(libziparchive_common_c_flags)
LOCAL_CFLAGS_windows := -mno-ms-bitfields
-LOCAL_CPPFLAGS := $(common_cpp_flags)
+LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags)
LOCAL_MULTILIB := both
LOCAL_MODULE_HOST_OS := darwin linux windows
@@ -50,12 +62,12 @@
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
-LOCAL_SRC_FILES := ${source_files}
+LOCAL_SRC_FILES := $(libziparchive_source_files)
LOCAL_STATIC_LIBRARIES := libutils
LOCAL_SHARED_LIBRARIES := libz-host liblog libbase
LOCAL_MODULE:= libziparchive-host
-LOCAL_CFLAGS := $(common_c_flags)
-LOCAL_CPPFLAGS := $(common_cpp_flags)
+LOCAL_CFLAGS := $(libziparchive_common_c_flags)
+LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags)
LOCAL_MULTILIB := both
include $(BUILD_HOST_SHARED_LIBRARY)
@@ -63,21 +75,33 @@
include $(CLEAR_VARS)
LOCAL_MODULE := ziparchive-tests
LOCAL_CPP_EXTENSION := .cc
-LOCAL_CFLAGS := $(common_c_flags)
-LOCAL_CPPFLAGS := $(common_cpp_flags)
-LOCAL_SRC_FILES := $(test_files)
-LOCAL_SHARED_LIBRARIES := liblog libbase
-LOCAL_STATIC_LIBRARIES := libziparchive libz libutils
+LOCAL_CFLAGS := $(libziparchive_common_c_flags)
+LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags)
+LOCAL_SRC_FILES := $(libziparchive_test_files)
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ liblog \
+
+LOCAL_STATIC_LIBRARIES := \
+ libziparchive \
+ libz \
+ libutils \
+
include $(BUILD_NATIVE_TEST)
include $(CLEAR_VARS)
LOCAL_MODULE := ziparchive-tests-host
LOCAL_CPP_EXTENSION := .cc
-LOCAL_CFLAGS := $(common_c_flags)
-LOCAL_CPPFLAGS := -Wno-unnamed-type-template-args $(common_cpp_flags)
-LOCAL_SRC_FILES := $(test_files)
-LOCAL_SHARED_LIBRARIES := libziparchive-host liblog libbase
+LOCAL_CFLAGS := $(libziparchive_common_c_flags)
+LOCAL_CPPFLAGS := -Wno-unnamed-type-template-args $(libziparchive_common_cpp_flags)
+LOCAL_SRC_FILES := $(libziparchive_test_files)
+LOCAL_SHARED_LIBRARIES := \
+ libziparchive-host \
+ liblog \
+ libbase \
+
LOCAL_STATIC_LIBRARIES := \
+ libutils \
libz \
- libutils
+
include $(BUILD_HOST_NATIVE_TEST)
diff --git a/libziparchive/testdata/bad_crc.zip b/libziparchive/testdata/bad_crc.zip
new file mode 100644
index 0000000..e12ba07
--- /dev/null
+++ b/libziparchive/testdata/bad_crc.zip
Binary files differ
diff --git a/libziparchive/testdata/large.zip b/libziparchive/testdata/large.zip
new file mode 100644
index 0000000..49659c8
--- /dev/null
+++ b/libziparchive/testdata/large.zip
Binary files differ
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 07ef6cd..3b1e972 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -36,11 +36,12 @@
#include "log/log.h"
#include "utils/Compat.h"
#include "utils/FileMap.h"
+#include "ziparchive/zip_archive.h"
#include "zlib.h"
#include "entry_name_utils-inl.h"
#include "zip_archive_common.h"
-#include "ziparchive/zip_archive.h"
+#include "zip_archive_private.h"
using android::base::get_unaligned;
@@ -134,43 +135,6 @@
* every page that the Central Directory touches. Easier to tuck a copy
* of the string length into the hash table entry.
*/
-struct ZipArchive {
- /* open Zip archive */
- const int fd;
- const bool close_file;
-
- /* mapped central directory area */
- off64_t directory_offset;
- android::FileMap directory_map;
-
- /* number of entries in the Zip archive */
- uint16_t num_entries;
-
- /*
- * We know how many entries are in the Zip archive, so we can have a
- * fixed-size hash table. We define a load factor of 0.75 and overallocat
- * so the maximum number entries can never be higher than
- * ((4 * UINT16_MAX) / 3 + 1) which can safely fit into a uint32_t.
- */
- uint32_t hash_table_size;
- ZipString* hash_table;
-
- ZipArchive(const int fd, bool assume_ownership) :
- fd(fd),
- close_file(assume_ownership),
- directory_offset(0),
- num_entries(0),
- hash_table_size(0),
- hash_table(NULL) {}
-
- ~ZipArchive() {
- if (close_file && fd >= 0) {
- close(fd);
- }
-
- free(hash_table);
- }
-};
/*
* Round up to the next highest power of 2.
diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h
new file mode 100644
index 0000000..ab52368
--- /dev/null
+++ b/libziparchive/zip_archive_private.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_
+#define LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <utils/FileMap.h>
+#include <ziparchive/zip_archive.h>
+
+struct ZipArchive {
+ // open Zip archive
+ const int fd;
+ const bool close_file;
+
+ // mapped central directory area
+ off64_t directory_offset;
+ android::FileMap directory_map;
+
+ // number of entries in the Zip archive
+ uint16_t num_entries;
+
+ // We know how many entries are in the Zip archive, so we can have a
+ // fixed-size hash table. We define a load factor of 0.75 and over
+ // allocate so the maximum number entries can never be higher than
+ // ((4 * UINT16_MAX) / 3 + 1) which can safely fit into a uint32_t.
+ uint32_t hash_table_size;
+ ZipString* hash_table;
+
+ ZipArchive(const int fd, bool assume_ownership) :
+ fd(fd),
+ close_file(assume_ownership),
+ directory_offset(0),
+ num_entries(0),
+ hash_table_size(0),
+ hash_table(NULL) {}
+
+ ~ZipArchive() {
+ if (close_file && fd >= 0) {
+ close(fd);
+ }
+
+ free(hash_table);
+ }
+};
+
+#endif // LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_
diff --git a/libziparchive/zip_archive_stream_entry.cc b/libziparchive/zip_archive_stream_entry.cc
new file mode 100644
index 0000000..f618835
--- /dev/null
+++ b/libziparchive/zip_archive_stream_entry.cc
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+// Read-only stream access to Zip Archive entries.
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <vector>
+
+#define LOG_TAG "ZIPARCHIVE"
+#include <android-base/file.h>
+#include <log/log.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_archive_stream_entry.h>
+#include <zlib.h>
+
+#include "zip_archive_private.h"
+
+static constexpr size_t kBufSize = 65535;
+
+bool ZipArchiveStreamEntry::Init(const ZipEntry& entry) {
+ ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_);
+ off64_t data_offset = entry.offset;
+ if (lseek64(archive->fd, data_offset, SEEK_SET) != data_offset) {
+ ALOGW("lseek to data at %" PRId64 " failed: %s", data_offset, strerror(errno));
+ return false;
+ }
+ crc32_ = entry.crc32;
+ return true;
+}
+
+class ZipArchiveStreamEntryUncompressed : public ZipArchiveStreamEntry {
+ public:
+ ZipArchiveStreamEntryUncompressed(ZipArchiveHandle handle) : ZipArchiveStreamEntry(handle) {}
+ virtual ~ZipArchiveStreamEntryUncompressed() {}
+
+ const std::vector<uint8_t>* Read() override;
+
+ bool Verify() override;
+
+ protected:
+ bool Init(const ZipEntry& entry) override;
+
+ uint32_t length_;
+
+ private:
+ std::vector<uint8_t> data_;
+ uint32_t computed_crc32_;
+};
+
+bool ZipArchiveStreamEntryUncompressed::Init(const ZipEntry& entry) {
+ if (!ZipArchiveStreamEntry::Init(entry)) {
+ return false;
+ }
+
+ length_ = entry.uncompressed_length;
+
+ data_.resize(kBufSize);
+ computed_crc32_ = 0;
+
+ return true;
+}
+
+const std::vector<uint8_t>* ZipArchiveStreamEntryUncompressed::Read() {
+ if (length_ == 0) {
+ return nullptr;
+ }
+
+ size_t bytes = (length_ > data_.size()) ? data_.size() : length_;
+ ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_);
+ errno = 0;
+ if (!android::base::ReadFully(archive->fd, data_.data(), bytes)) {
+ if (errno != 0) {
+ ALOGE("Error reading from archive fd: %s", strerror(errno));
+ } else {
+ ALOGE("Short read of zip file, possibly corrupted zip?");
+ }
+ length_ = 0;
+ return nullptr;
+ }
+
+ if (bytes < data_.size()) {
+ data_.resize(bytes);
+ }
+ computed_crc32_ = crc32(computed_crc32_, data_.data(), data_.size());
+ length_ -= bytes;
+ return &data_;
+}
+
+bool ZipArchiveStreamEntryUncompressed::Verify() {
+ return length_ == 0 && crc32_ == computed_crc32_;
+}
+
+class ZipArchiveStreamEntryCompressed : public ZipArchiveStreamEntry {
+ public:
+ ZipArchiveStreamEntryCompressed(ZipArchiveHandle handle) : ZipArchiveStreamEntry(handle) {}
+ virtual ~ZipArchiveStreamEntryCompressed();
+
+ const std::vector<uint8_t>* Read() override;
+
+ bool Verify() override;
+
+ protected:
+ bool Init(const ZipEntry& entry) override;
+
+ private:
+ bool z_stream_init_ = false;
+ z_stream z_stream_;
+ std::vector<uint8_t> in_;
+ std::vector<uint8_t> out_;
+ uint32_t uncompressed_length_;
+ uint32_t compressed_length_;
+ uint32_t computed_crc32_;
+};
+
+// This method is using libz macros with old-style-casts
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
+static inline int zlib_inflateInit2(z_stream* stream, int window_bits) {
+ return inflateInit2(stream, window_bits);
+}
+#pragma GCC diagnostic pop
+
+bool ZipArchiveStreamEntryCompressed::Init(const ZipEntry& entry) {
+ if (!ZipArchiveStreamEntry::Init(entry)) {
+ return false;
+ }
+
+ // Initialize the zlib stream struct.
+ memset(&z_stream_, 0, sizeof(z_stream_));
+ z_stream_.zalloc = Z_NULL;
+ z_stream_.zfree = Z_NULL;
+ z_stream_.opaque = Z_NULL;
+ z_stream_.next_in = nullptr;
+ z_stream_.avail_in = 0;
+ z_stream_.avail_out = 0;
+ z_stream_.data_type = Z_UNKNOWN;
+
+ // Use the undocumented "negative window bits" feature to tell zlib
+ // that there's no zlib header waiting for it.
+ int zerr = zlib_inflateInit2(&z_stream_, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)",
+ ZLIB_VERSION);
+ } else {
+ ALOGE("Call to inflateInit2 failed (zerr=%d)", zerr);
+ }
+
+ return false;
+ }
+
+ z_stream_init_ = true;
+
+ uncompressed_length_ = entry.uncompressed_length;
+ compressed_length_ = entry.compressed_length;
+
+ out_.resize(kBufSize);
+ in_.resize(kBufSize);
+
+ computed_crc32_ = 0;
+
+ return true;
+}
+
+ZipArchiveStreamEntryCompressed::~ZipArchiveStreamEntryCompressed() {
+ if (z_stream_init_) {
+ inflateEnd(&z_stream_);
+ z_stream_init_ = false;
+ }
+}
+
+bool ZipArchiveStreamEntryCompressed::Verify() {
+ return z_stream_init_ && uncompressed_length_ == 0 && compressed_length_ == 0 &&
+ crc32_ == computed_crc32_;
+}
+
+const std::vector<uint8_t>* ZipArchiveStreamEntryCompressed::Read() {
+ if (z_stream_.avail_out == 0) {
+ z_stream_.next_out = out_.data();
+ z_stream_.avail_out = out_.size();;
+ }
+
+ while (true) {
+ if (z_stream_.avail_in == 0) {
+ if (compressed_length_ == 0) {
+ return nullptr;
+ }
+ size_t bytes = (compressed_length_ > in_.size()) ? in_.size() : compressed_length_;
+ ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_);
+ errno = 0;
+ if (!android::base::ReadFully(archive->fd, in_.data(), bytes)) {
+ if (errno != 0) {
+ ALOGE("Error reading from archive fd: %s", strerror(errno));
+ } else {
+ ALOGE("Short read of zip file, possibly corrupted zip?");
+ }
+ return nullptr;
+ }
+
+ compressed_length_ -= bytes;
+ z_stream_.next_in = in_.data();
+ z_stream_.avail_in = bytes;
+ }
+
+ int zerr = inflate(&z_stream_, Z_NO_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGE("inflate zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)",
+ zerr, z_stream_.next_in, z_stream_.avail_in,
+ z_stream_.next_out, z_stream_.avail_out);
+ return nullptr;
+ }
+
+ if (z_stream_.avail_out == 0) {
+ uncompressed_length_ -= out_.size();
+ computed_crc32_ = crc32(computed_crc32_, out_.data(), out_.size());
+ return &out_;
+ }
+ if (zerr == Z_STREAM_END) {
+ if (z_stream_.avail_out != 0) {
+ // Resize the vector down to the actual size of the data.
+ out_.resize(out_.size() - z_stream_.avail_out);
+ computed_crc32_ = crc32(computed_crc32_, out_.data(), out_.size());
+ uncompressed_length_ -= out_.size();
+ return &out_;
+ }
+ return nullptr;
+ }
+ }
+ return nullptr;
+}
+
+class ZipArchiveStreamEntryRawCompressed : public ZipArchiveStreamEntryUncompressed {
+ public:
+ ZipArchiveStreamEntryRawCompressed(ZipArchiveHandle handle)
+ : ZipArchiveStreamEntryUncompressed(handle) {}
+ virtual ~ZipArchiveStreamEntryRawCompressed() {}
+
+ bool Verify() override;
+
+ protected:
+ bool Init(const ZipEntry& entry) override;
+};
+
+bool ZipArchiveStreamEntryRawCompressed::Init(const ZipEntry& entry) {
+ if (!ZipArchiveStreamEntryUncompressed::Init(entry)) {
+ return false;
+ }
+ length_ = entry.compressed_length;
+
+ return true;
+}
+
+bool ZipArchiveStreamEntryRawCompressed::Verify() {
+ return length_ == 0;
+}
+
+ZipArchiveStreamEntry* ZipArchiveStreamEntry::Create(
+ ZipArchiveHandle handle, const ZipEntry& entry) {
+ ZipArchiveStreamEntry* stream = nullptr;
+ if (entry.method != kCompressStored) {
+ stream = new ZipArchiveStreamEntryCompressed(handle);
+ } else {
+ stream = new ZipArchiveStreamEntryUncompressed(handle);
+ }
+ if (stream && !stream->Init(entry)) {
+ delete stream;
+ stream = nullptr;
+ }
+
+ return stream;
+}
+
+ZipArchiveStreamEntry* ZipArchiveStreamEntry::CreateRaw(
+ ZipArchiveHandle handle, const ZipEntry& entry) {
+ ZipArchiveStreamEntry* stream = nullptr;
+ if (entry.method == kCompressStored) {
+ // Not compressed, don't need to do anything special.
+ stream = new ZipArchiveStreamEntryUncompressed(handle);
+ } else {
+ stream = new ZipArchiveStreamEntryRawCompressed(handle);
+ }
+ if (stream && !stream->Init(entry)) {
+ delete stream;
+ stream = nullptr;
+ }
+ return stream;
+}
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index cb0f410..d426dc4 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -14,54 +14,49 @@
* limitations under the License.
*/
-#include "ziparchive/zip_archive.h"
-
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
+#include <string.h>
#include <unistd.h>
+
#include <vector>
#include <android-base/file.h>
#include <gtest/gtest.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_archive_stream_entry.h>
static std::string test_data_dir;
static const std::string kMissingZip = "missing.zip";
static const std::string kValidZip = "valid.zip";
+static const std::string kLargeZip = "large.zip";
+static const std::string kBadCrcZip = "bad_crc.zip";
-static const uint8_t kATxtContents[] = {
+static const std::vector<uint8_t> kATxtContents {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'\n'
};
-static const uint8_t kBTxtContents[] = {
+static const std::vector<uint8_t> kATxtContentsCompressed {
+ 'K', 'L', 'J', 'N', 'I', 'M', 'K', 207, 'H',
+ 132, 210, '\\', '\0'
+};
+
+static const std::vector<uint8_t> kBTxtContents {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'\n'
};
-static const uint16_t kATxtNameLength = 5;
-static const uint16_t kBTxtNameLength = 5;
-static const uint16_t kNonexistentTxtNameLength = 15;
-static const uint16_t kEmptyTxtNameLength = 9;
-
-static const uint8_t kATxtName[kATxtNameLength] = {
- 'a', '.', 't', 'x', 't'
-};
-
-static const uint8_t kBTxtName[kBTxtNameLength] = {
- 'b', '.', 't', 'x', 't'
-};
-
-static const uint8_t kNonexistentTxtName[kNonexistentTxtNameLength] = {
- 'n', 'o', 'n', 'e', 'x', 'i', 's', 't', 'e', 'n', 't', '.', 't', 'x' ,'t'
-};
-
-static const uint8_t kEmptyTxtName[kEmptyTxtNameLength] = {
- 'e', 'm', 'p', 't', 'y', '.', 't', 'x', 't'
-};
+static const std::string kATxtName("a.txt");
+static const std::string kBTxtName("b.txt");
+static const std::string kNonexistentTxtName("nonexistent.txt");
+static const std::string kEmptyTxtName("empty.txt");
+static const std::string kLargeCompressTxtName("compress.txt");
+static const std::string kLargeUncompressTxtName("uncompress.txt");
static int32_t OpenArchiveWrapper(const std::string& name,
ZipArchiveHandle* handle) {
@@ -75,6 +70,11 @@
ASSERT_EQ(0, memcmp(name_str.c_str(), name.name, name.name_length));
}
+static void SetZipString(ZipString* zip_str, const std::string& str) {
+ zip_str->name = reinterpret_cast<const uint8_t*>(str.c_str());
+ zip_str->name_length = str.size();
+}
+
TEST(ziparchive, Open) {
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
@@ -115,7 +115,7 @@
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
void* iteration_cookie;
- ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, NULL));
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, nullptr, nullptr));
ZipEntry data;
ZipString name;
@@ -152,7 +152,7 @@
void* iteration_cookie;
ZipString prefix("b/");
- ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, &prefix, NULL));
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, &prefix, nullptr));
ZipEntry data;
ZipString name;
@@ -181,7 +181,7 @@
void* iteration_cookie;
ZipString suffix(".txt");
- ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, &suffix));
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, nullptr, &suffix));
ZipEntry data;
ZipString name;
@@ -262,8 +262,7 @@
ZipEntry data;
ZipString name;
- name.name = kATxtName;
- name.name_length = kATxtNameLength;
+ SetZipString(&name, kATxtName);
ASSERT_EQ(0, FindEntry(handle, name, &data));
// Known facts about a.txt, from zipinfo -v.
@@ -276,8 +275,7 @@
// An entry that doesn't exist. Should be a negative return code.
ZipString absent_name;
- absent_name.name = kNonexistentTxtName;
- absent_name.name_length = kNonexistentTxtNameLength;
+ SetZipString(&absent_name, kNonexistentTxtName);
ASSERT_LT(FindEntry(handle, absent_name, &data), 0);
CloseArchive(handle);
@@ -288,7 +286,7 @@
ASSERT_EQ(0, OpenArchiveWrapper("declaredlength.zip", &handle));
void* iteration_cookie;
- ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, NULL));
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, nullptr, nullptr));
ZipString name;
ZipEntry data;
@@ -306,26 +304,24 @@
// An entry that's deflated.
ZipEntry data;
ZipString a_name;
- a_name.name = kATxtName;
- a_name.name_length = kATxtNameLength;
+ SetZipString(&a_name, kATxtName);
ASSERT_EQ(0, FindEntry(handle, a_name, &data));
const uint32_t a_size = data.uncompressed_length;
- ASSERT_EQ(a_size, sizeof(kATxtContents));
+ ASSERT_EQ(a_size, kATxtContents.size());
uint8_t* buffer = new uint8_t[a_size];
ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, a_size));
- ASSERT_EQ(0, memcmp(buffer, kATxtContents, a_size));
+ ASSERT_EQ(0, memcmp(buffer, kATxtContents.data(), a_size));
delete[] buffer;
// An entry that's stored.
ZipString b_name;
- b_name.name = kBTxtName;
- b_name.name_length = kBTxtNameLength;
+ SetZipString(&b_name, kBTxtName);
ASSERT_EQ(0, FindEntry(handle, b_name, &data));
const uint32_t b_size = data.uncompressed_length;
- ASSERT_EQ(b_size, sizeof(kBTxtContents));
+ ASSERT_EQ(b_size, kBTxtContents.size());
buffer = new uint8_t[b_size];
ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, b_size));
- ASSERT_EQ(0, memcmp(buffer, kBTxtContents, b_size));
+ ASSERT_EQ(0, memcmp(buffer, kBTxtContents.data(), b_size));
delete[] buffer;
CloseArchive(handle);
@@ -374,8 +370,7 @@
0x0100, 0x4c00, 0x0000, 0x5b00, 0x0001, 0x0000, 0x0000
};
-static const uint8_t kAbTxtName[] = { 'a', 'b', '.', 't', 'x', 't' };
-static const uint16_t kAbTxtNameLength = sizeof(kAbTxtName);
+static const std::string kAbTxtName("ab.txt");
static const size_t kAbUncompressedSize = 270216;
static int make_temporary_file(const char* file_name_pattern) {
@@ -405,8 +400,7 @@
ZipEntry entry;
ZipString empty_name;
- empty_name.name = kEmptyTxtName;
- empty_name.name_length = kEmptyTxtNameLength;
+ SetZipString(&empty_name, kEmptyTxtName);
ASSERT_EQ(0, FindEntry(handle, empty_name, &entry));
ASSERT_EQ(static_cast<uint32_t>(0), entry.uncompressed_length);
uint8_t buffer[1];
@@ -436,8 +430,7 @@
ZipEntry entry;
ZipString ab_name;
- ab_name.name = kAbTxtName;
- ab_name.name_length = kAbTxtNameLength;
+ SetZipString(&ab_name, kAbTxtName);
ASSERT_EQ(0, FindEntry(handle, ab_name, &entry));
ASSERT_EQ(kAbUncompressedSize, entry.uncompressed_length);
@@ -504,8 +497,7 @@
ZipEntry entry;
ZipString name;
- name.name = kATxtName;
- name.name_length = kATxtNameLength;
+ SetZipString(&name, kATxtName);
ASSERT_EQ(0, FindEntry(handle, name, &entry));
ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, fd));
@@ -521,22 +513,131 @@
ASSERT_EQ(static_cast<ssize_t>(entry.uncompressed_length),
TEMP_FAILURE_RETRY(
read(fd, &uncompressed_data[0], entry.uncompressed_length)));
- ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents,
- sizeof(kATxtContents)));
+ ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents.data(),
+ kATxtContents.size()));
// Assert that the total length of the file is sane
- ASSERT_EQ(data_size + static_cast<ssize_t>(sizeof(kATxtContents)),
+ ASSERT_EQ(data_size + static_cast<ssize_t>(kATxtContents.size()),
lseek64(fd, 0, SEEK_END));
close(fd);
}
+static void ZipArchiveStreamTest(
+ ZipArchiveHandle& handle, const std::string& entry_name, bool raw,
+ bool verified, ZipEntry* entry, std::vector<uint8_t>* read_data) {
+ ZipString name;
+ SetZipString(&name, entry_name);
+ ASSERT_EQ(0, FindEntry(handle, name, entry));
+ std::unique_ptr<ZipArchiveStreamEntry> stream;
+ if (raw) {
+ stream.reset(ZipArchiveStreamEntry::CreateRaw(handle, *entry));
+ if (entry->method == kCompressStored) {
+ read_data->resize(entry->uncompressed_length);
+ } else {
+ read_data->resize(entry->compressed_length);
+ }
+ } else {
+ stream.reset(ZipArchiveStreamEntry::Create(handle, *entry));
+ read_data->resize(entry->uncompressed_length);
+ }
+ uint8_t* read_data_ptr = read_data->data();
+ ASSERT_TRUE(stream.get() != nullptr);
+ const std::vector<uint8_t>* data;
+ uint64_t total_size = 0;
+ while ((data = stream->Read()) != nullptr) {
+ total_size += data->size();
+ memcpy(read_data_ptr, data->data(), data->size());
+ read_data_ptr += data->size();
+ }
+ ASSERT_EQ(verified, stream->Verify());
+ ASSERT_EQ(total_size, read_data->size());
+}
+
+static void ZipArchiveStreamTestUsingContents(
+ const std::string& zip_file, const std::string& entry_name,
+ const std::vector<uint8_t>& contents, bool raw) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(zip_file, &handle));
+
+ ZipEntry entry;
+ std::vector<uint8_t> read_data;
+ ZipArchiveStreamTest(handle, entry_name, raw, true, &entry, &read_data);
+
+ ASSERT_EQ(contents.size(), read_data.size());
+ ASSERT_TRUE(memcmp(read_data.data(), contents.data(), read_data.size()) == 0);
+
+ CloseArchive(handle);
+}
+
+static void ZipArchiveStreamTestUsingMemory(const std::string& zip_file, const std::string& entry_name) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(zip_file, &handle));
+
+ ZipEntry entry;
+ std::vector<uint8_t> read_data;
+ ZipArchiveStreamTest(handle, entry_name, false, true, &entry, &read_data);
+
+ std::vector<uint8_t> cmp_data(entry.uncompressed_length);
+ ASSERT_EQ(entry.uncompressed_length, read_data.size());
+ ASSERT_EQ(0, ExtractToMemory(handle, &entry, cmp_data.data(), cmp_data.size()));
+ ASSERT_TRUE(memcmp(read_data.data(), cmp_data.data(), read_data.size()) == 0);
+
+ CloseArchive(handle);
+}
+
+TEST(ziparchive, StreamCompressed) {
+ ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContents, false);
+}
+
+TEST(ziparchive, StreamUncompressed) {
+ ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, false);
+}
+
+TEST(ziparchive, StreamRawCompressed) {
+ ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContentsCompressed, true);
+}
+
+TEST(ziparchive, StreamRawUncompressed) {
+ ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, true);
+}
+
+TEST(ziparchive, StreamLargeCompressed) {
+ ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeCompressTxtName);
+}
+
+TEST(ziparchive, StreamLargeUncompressed) {
+ ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeUncompressTxtName);
+}
+
+TEST(ziparchive, StreamCompressedBadCrc) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(kBadCrcZip, &handle));
+
+ ZipEntry entry;
+ std::vector<uint8_t> read_data;
+ ZipArchiveStreamTest(handle, kATxtName, false, false, &entry, &read_data);
+
+ CloseArchive(handle);
+}
+
+TEST(ziparchive, StreamUncompressedBadCrc) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(kBadCrcZip, &handle));
+
+ ZipEntry entry;
+ std::vector<uint8_t> read_data;
+ ZipArchiveStreamTest(handle, kBTxtName, false, false, &entry, &read_data);
+
+ CloseArchive(handle);
+}
+
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
static struct option options[] = {
- { "test_data_dir", required_argument, NULL, 't' },
- { NULL, 0, NULL, 0 }
+ { "test_data_dir", required_argument, nullptr, 't' },
+ { nullptr, 0, nullptr, 0 }
};
while (true) {
@@ -557,9 +658,15 @@
}
if (test_data_dir[0] != '/') {
- printf("Test data must be an absolute path, was %s\n\n",
- test_data_dir.c_str());
- return -2;
+ std::vector<char> cwd_buffer(1024);
+ const char* cwd = getcwd(cwd_buffer.data(), cwd_buffer.size() - 1);
+ if (cwd == nullptr) {
+ printf("Cannot get current working directory, use an absolute path instead, was %s\n\n",
+ test_data_dir.c_str());
+ return -2;
+ }
+ test_data_dir = '/' + test_data_dir;
+ test_data_dir = cwd + test_data_dir;
}
return RUN_ALL_TESTS();
diff --git a/libziparchive/zip_writer.cc b/libziparchive/zip_writer.cc
index f117cc5..1ebed30 100644
--- a/libziparchive/zip_writer.cc
+++ b/libziparchive/zip_writer.cc
@@ -20,12 +20,19 @@
#include <utils/Log.h>
+#include <sys/param.h>
+
#include <cassert>
#include <cstdio>
#include <memory>
+#include <vector>
#include <zlib.h>
#define DEF_MEM_LEVEL 8 // normally in zutil.h?
+#if !defined(powerof2)
+#define powerof2(x) ((((x)-1)&(x))==0)
+#endif
+
/* Zip compression methods we support */
enum {
kCompressStored = 0, // no compression
@@ -50,6 +57,12 @@
// An error occurred in zlib.
static const int32_t kZlibError = -4;
+// The start aligned function was called with the aligned flag.
+static const int32_t kInvalidAlign32Flag = -5;
+
+// The alignment parameter is not a power of 2.
+static const int32_t kInvalidAlignment = -6;
+
static const char* sErrorCodes[] = {
"Invalid state",
"IO error",
@@ -102,7 +115,25 @@
}
int32_t ZipWriter::StartEntry(const char* path, size_t flags) {
- return StartEntryWithTime(path, flags, time_t());
+ uint32_t alignment = 0;
+ if (flags & kAlign32) {
+ flags &= ~kAlign32;
+ alignment = 4;
+ }
+ return StartAlignedEntryWithTime(path, flags, time_t(), alignment);
+}
+
+int32_t ZipWriter::StartAlignedEntry(const char* path, size_t flags, uint32_t alignment) {
+ return StartAlignedEntryWithTime(path, flags, time_t(), alignment);
+}
+
+int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t time) {
+ uint32_t alignment = 0;
+ if (flags & kAlign32) {
+ flags &= ~kAlign32;
+ alignment = 4;
+ }
+ return StartAlignedEntryWithTime(path, flags, time, alignment);
}
static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_date) {
@@ -126,11 +157,20 @@
*out_time = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
}
-int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t time) {
+int32_t ZipWriter::StartAlignedEntryWithTime(const char* path, size_t flags,
+ time_t time, uint32_t alignment) {
if (state_ != State::kWritingZip) {
return kInvalidState;
}
+ if (flags & kAlign32) {
+ return kInvalidAlign32Flag;
+ }
+
+ if (powerof2(alignment) == 0) {
+ return kInvalidAlignment;
+ }
+
FileInfo fileInfo = {};
fileInfo.path = std::string(path);
fileInfo.local_file_header_offset = current_offset_;
@@ -166,11 +206,14 @@
header.file_name_length = fileInfo.path.size();
off64_t offset = current_offset_ + sizeof(header) + fileInfo.path.size();
- if ((flags & ZipWriter::kAlign32) && (offset & 0x03)) {
+ std::vector<char> zero_padding;
+ if (alignment != 0 && (offset & (alignment - 1))) {
// Pad the extra field so the data will be aligned.
- uint16_t padding = 4 - (offset % 4);
+ uint16_t padding = alignment - (offset % alignment);
header.extra_field_length = padding;
offset += padding;
+ zero_padding.resize(padding);
+ memset(zero_padding.data(), 0, zero_padding.size());
}
if (fwrite(&header, sizeof(header), 1, file_) != 1) {
@@ -181,7 +224,9 @@
return HandleError(kIoError);
}
- if (fwrite("\0\0\0", 1, header.extra_field_length, file_) != header.extra_field_length) {
+ if (header.extra_field_length != 0 &&
+ fwrite(zero_padding.data(), 1, header.extra_field_length, file_)
+ != header.extra_field_length) {
return HandleError(kIoError);
}
diff --git a/libziparchive/zip_writer_test.cc b/libziparchive/zip_writer_test.cc
index b7d1458..fe0846d 100644
--- a/libziparchive/zip_writer_test.cc
+++ b/libziparchive/zip_writer_test.cc
@@ -19,6 +19,7 @@
#include <android-base/test_utils.h>
#include <gtest/gtest.h>
+#include <time.h>
#include <memory>
#include <vector>
@@ -122,7 +123,7 @@
CloseArchive(handle);
}
-TEST_F(zipwriter, WriteUncompressedZipWithAlignedFile) {
+TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlag) {
ZipWriter writer(file_);
ASSERT_EQ(0, writer.StartEntry("align.txt", ZipWriter::kAlign32));
@@ -142,6 +143,103 @@
CloseArchive(handle);
}
+void ConvertZipTimeToTm(uint32_t& zip_time, struct tm* tm) {
+ memset(tm, 0, sizeof(struct tm));
+ tm->tm_hour = (zip_time >> 11) & 0x1f;
+ tm->tm_min = (zip_time >> 5) & 0x3f;
+ tm->tm_sec = (zip_time & 0x1f) << 1;
+
+ tm->tm_year = ((zip_time >> 25) & 0x7f) + 80;
+ tm->tm_mon = ((zip_time >> 21) & 0xf) - 1;
+ tm->tm_mday = (zip_time >> 16) & 0x1f;
+}
+
+TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlagAndTime) {
+ ZipWriter writer(file_);
+
+ struct tm tm;
+ memset(&tm, 0, sizeof(struct tm));
+ ASSERT_TRUE(strptime("18:30:20 1/12/2001", "%H:%M:%S %d/%m/%Y", &tm) != nullptr);
+ time_t time = mktime(&tm);
+ ASSERT_EQ(0, writer.StartEntryWithTime("align.txt", ZipWriter::kAlign32, time));
+ ASSERT_EQ(0, writer.WriteBytes("he", 2));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+
+ ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
+
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
+
+ ZipEntry data;
+ ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data));
+ EXPECT_EQ(0, data.offset & 0x03);
+
+ struct tm mod;
+ ConvertZipTimeToTm(data.mod_time, &mod);
+ EXPECT_EQ(tm.tm_sec, mod.tm_sec);
+ EXPECT_EQ(tm.tm_min, mod.tm_min);
+ EXPECT_EQ(tm.tm_hour, mod.tm_hour);
+ EXPECT_EQ(tm.tm_mday, mod.tm_mday);
+ EXPECT_EQ(tm.tm_mon, mod.tm_mon);
+ EXPECT_EQ(tm.tm_year, mod.tm_year);
+
+ CloseArchive(handle);
+}
+
+TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValue) {
+ ZipWriter writer(file_);
+
+ ASSERT_EQ(0, writer.StartAlignedEntry("align.txt", 0, 4096));
+ ASSERT_EQ(0, writer.WriteBytes("he", 2));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+
+ ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
+
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
+
+ ZipEntry data;
+ ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data));
+ EXPECT_EQ(0, data.offset & 0xfff);
+
+ CloseArchive(handle);
+}
+
+TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValueAndTime) {
+ ZipWriter writer(file_);
+
+ struct tm tm;
+ memset(&tm, 0, sizeof(struct tm));
+ ASSERT_TRUE(strptime("18:30:20 1/12/2001", "%H:%M:%S %d/%m/%Y", &tm) != nullptr);
+ time_t time = mktime(&tm);
+ ASSERT_EQ(0, writer.StartAlignedEntryWithTime("align.txt", 0, time, 4096));
+ ASSERT_EQ(0, writer.WriteBytes("he", 2));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+
+ ASSERT_GE(0, lseek(fd_, 0, SEEK_SET));
+
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false));
+
+ ZipEntry data;
+ ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data));
+ EXPECT_EQ(0, data.offset & 0xfff);
+
+ struct tm mod;
+ ConvertZipTimeToTm(data.mod_time, &mod);
+ EXPECT_EQ(tm.tm_sec, mod.tm_sec);
+ EXPECT_EQ(tm.tm_min, mod.tm_min);
+ EXPECT_EQ(tm.tm_hour, mod.tm_hour);
+ EXPECT_EQ(tm.tm_mday, mod.tm_mday);
+ EXPECT_EQ(tm.tm_mon, mod.tm_mon);
+ EXPECT_EQ(tm.tm_year, mod.tm_year);
+
+ CloseArchive(handle);
+}
+
TEST_F(zipwriter, WriteCompressedZipWithOneFile) {
ZipWriter writer(file_);
@@ -206,3 +304,10 @@
CloseArchive(handle);
}
+
+TEST_F(zipwriter, CheckStartEntryErrors) {
+ ZipWriter writer(file_);
+
+ ASSERT_EQ(-5, writer.StartAlignedEntry("align.txt", ZipWriter::kAlign32, 4096));
+ ASSERT_EQ(-6, writer.StartAlignedEntry("align.txt", 0, 3));
+}
diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp
index cb3d1c2..48036d3 100644
--- a/logd/FlushCommand.cpp
+++ b/logd/FlushCommand.cpp
@@ -36,7 +36,7 @@
mLogMask(logMask),
mPid(pid),
mStart(start),
- mTimeout(timeout) {
+ mTimeout((start > 1) ? timeout : 0) {
}
// runSocketCommand is called once for every open client on the
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index 707527b..9e0d451 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -109,6 +109,9 @@
}
log_id_for_each(i) {
+ mLastSet[i] = false;
+ mLast[i] = mLogElements.begin();
+
char key[PROP_NAME_MAX];
snprintf(key, sizeof(key), "%s.%s",
@@ -329,7 +332,23 @@
}
}
+ bool setLast[LOG_ID_MAX];
+ bool doSetLast = false;
+ log_id_for_each(i) {
+ doSetLast |= setLast[i] = mLastSet[i] && (it == mLast[i]);
+ }
it = mLogElements.erase(it);
+ if (doSetLast) {
+ log_id_for_each(i) {
+ if (setLast[i]) {
+ if (it == mLogElements.end()) { // unlikely
+ mLastSet[i] = false;
+ } else {
+ mLast[i] = it;
+ }
+ }
+ }
+ }
if (coalesce) {
stats.erase(element);
} else {
@@ -490,7 +509,8 @@
if (caller_uid != AID_ROOT) {
// Only here if clearAll condition (pruneRows == ULONG_MAX)
- for(it = mLogElements.begin(); it != mLogElements.end();) {
+ it = mLastSet[id] ? mLast[id] : mLogElements.begin();
+ while (it != mLogElements.end()) {
LogBufferElement *element = *it;
if ((element->getLogId() != id) || (element->getUid() != caller_uid)) {
@@ -498,6 +518,11 @@
continue;
}
+ if (!mLastSet[id] || ((*mLast[id])->getLogId() != id)) {
+ mLast[id] = it;
+ mLastSet[id] = true;
+ }
+
if (oldest && (oldest->mStart <= element->getSequence())) {
busy = true;
if (oldest->mTimeout.tv_sec || oldest->mTimeout.tv_nsec) {
@@ -566,7 +591,7 @@
bool kick = false;
bool leading = true;
- it = mLogElements.begin();
+ it = mLastSet[id] ? mLast[id] : mLogElements.begin();
// Perform at least one mandatory garbage collection cycle in following
// - clear leading chatty tags
// - coalesce chatty tags
@@ -615,6 +640,11 @@
continue;
}
+ if (leading && (!mLastSet[id] || ((*mLast[id])->getLogId() != id))) {
+ mLast[id] = it;
+ mLastSet[id] = true;
+ }
+
unsigned short dropped = element->getDropped();
// remove any leading drops
@@ -725,7 +755,7 @@
bool whitelist = false;
bool hasWhitelist = (id != LOG_ID_SECURITY) && mPrune.nice() && !clearAll;
- it = mLogElements.begin();
+ it = mLastSet[id] ? mLast[id] : mLogElements.begin();
while((pruneRows > 0) && (it != mLogElements.end())) {
LogBufferElement *element = *it;
@@ -734,6 +764,11 @@
continue;
}
+ if (!mLastSet[id] || ((*mLast[id])->getLogId() != id)) {
+ mLast[id] = it;
+ mLastSet[id] = true;
+ }
+
if (oldest && (oldest->mStart <= element->getSequence())) {
busy = true;
if (whitelist) {
@@ -764,7 +799,7 @@
// Do not save the whitelist if we are reader range limited
if (whitelist && (pruneRows > 0)) {
- it = mLogElements.begin();
+ it = mLastSet[id] ? mLast[id] : mLogElements.begin();
while((it != mLogElements.end()) && (pruneRows > 0)) {
LogBufferElement *element = *it;
@@ -773,6 +808,11 @@
continue;
}
+ if (!mLastSet[id] || ((*mLast[id])->getLogId() != id)) {
+ mLast[id] = it;
+ mLastSet[id] = true;
+ }
+
if (oldest && (oldest->mStart <= element->getSequence())) {
busy = true;
if (stats.sizes(id) > (2 * log_buffer_size(id))) {
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index 2667e78..03739c7 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -82,6 +82,9 @@
LogStatistics stats;
PruneList mPrune;
+ // watermark for last per log id
+ LogBufferElementCollection::iterator mLast[LOG_ID_MAX];
+ bool mLastSet[LOG_ID_MAX];
// watermark of any worst/chatty uid processing
typedef std::unordered_map<uid_t,
LogBufferElementCollection::iterator>
diff --git a/logd/LogListener.cpp b/logd/LogListener.cpp
index 5348a2d..846dd7c 100644
--- a/logd/LogListener.cpp
+++ b/logd/LogListener.cpp
@@ -15,6 +15,7 @@
*/
#include <limits.h>
+#include <sys/cdefs.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
@@ -46,7 +47,7 @@
+ LOGGER_ENTRY_MAX_PAYLOAD];
struct iovec iov = { buffer, sizeof(buffer) };
- char control[CMSG_SPACE(sizeof(struct ucred))];
+ char control[CMSG_SPACE(sizeof(struct ucred))] __aligned(4);
struct msghdr hdr = {
NULL,
0,
diff --git a/metricsd/Android.mk b/metricsd/Android.mk
index 7381703..250c657 100644
--- a/metricsd/Android.mk
+++ b/metricsd/Android.mk
@@ -38,6 +38,7 @@
uploader/metrics_hashes.cc \
uploader/metrics_log_base.cc \
uploader/metrics_log.cc \
+ uploader/metricsd_service_runner.cc \
uploader/sender_http.cc \
uploader/system_profile_cache.cc \
uploader/upload_service.cc
@@ -84,6 +85,7 @@
metricsd_shared_libraries := \
libbinder \
libbrillo \
+ libbrillo-binder \
libbrillo-http \
libchrome \
libprotobuf-cpp-lite \
@@ -200,6 +202,9 @@
LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries)
LOCAL_SRC_FILES := $(metricsd_tests_sources) $(metricsd_common)
LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_protos metricsd_binder_proxy
+ifdef BRILLO
+LOCAL_MODULE_TAGS := debug
+endif
include $(BUILD_NATIVE_TEST)
# Unit tests for metrics_collector.
@@ -215,6 +220,9 @@
$(metrics_collector_common)
LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_binder_proxy \
$(metrics_collector_static_libraries)
+ifdef BRILLO
+LOCAL_MODULE_TAGS := debug
+endif
include $(BUILD_NATIVE_TEST)
# Weave schema files
diff --git a/metricsd/constants.h b/metricsd/constants.h
index 4815888..b702737 100644
--- a/metricsd/constants.h
+++ b/metricsd/constants.h
@@ -26,6 +26,7 @@
static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
static const char kConsentFileName[] = "enabled";
static const char kStagedLogName[] = "staged_log";
+static const char kSavedLogName[] = "saved_log";
static const char kFailedUploadCountName[] = "failed_upload_count";
static const char kDefaultVersion[] = "0.0.0.0";
diff --git a/metricsd/metricsd_main.cc b/metricsd/metricsd_main.cc
index f460268..0178342 100644
--- a/metricsd/metricsd_main.cc
+++ b/metricsd/metricsd_main.cc
@@ -14,21 +14,15 @@
* limitations under the License.
*/
-#include <thread>
-
-#include <base/at_exit.h>
#include <base/command_line.h>
#include <base/files/file_path.h>
#include <base/logging.h>
-#include <base/metrics/statistics_recorder.h>
-#include <base/strings/string_util.h>
#include <base/time/time.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include "constants.h"
-#include "uploader/bn_metricsd_impl.h"
-#include "uploader/crash_counters.h"
+#include "uploader/metricsd_service_runner.h"
#include "uploader/upload_service.h"
int main(int argc, char** argv) {
@@ -39,10 +33,13 @@
// Upload Service flags.
DEFINE_int32(upload_interval_secs, 1800,
- "Interval at which metrics_daemon sends the metrics. (needs "
- "-uploader)");
+ "Interval at which metricsd uploads the metrics.");
+ DEFINE_int32(disk_persistence_interval_secs, 300,
+ "Interval at which metricsd saves the aggregated metrics to "
+ "disk to avoid losing them if metricsd stops in between "
+ "two uploads.");
DEFINE_string(server, metrics::kMetricsServer,
- "Server to upload the metrics to. (needs -uploader)");
+ "Server to upload the metrics to.");
DEFINE_string(private_directory, metrics::kMetricsdDirectory,
"Path to the private directory used by metricsd "
"(testing only)");
@@ -76,18 +73,11 @@
return errno;
}
- std::shared_ptr<CrashCounters> counters(new CrashCounters);
-
UploadService upload_service(
FLAGS_server, base::TimeDelta::FromSeconds(FLAGS_upload_interval_secs),
+ base::TimeDelta::FromSeconds(FLAGS_disk_persistence_interval_secs),
base::FilePath(FLAGS_private_directory),
- base::FilePath(FLAGS_shared_directory), counters);
+ base::FilePath(FLAGS_shared_directory));
- base::StatisticsRecorder::Initialize();
-
- // Create and start the binder thread.
- BnMetricsdImpl binder_service(counters);
- std::thread binder_thread(&BnMetricsdImpl::Run, &binder_service);
-
- upload_service.Run();
+ return upload_service.Run();
}
diff --git a/metricsd/uploader/bn_metricsd_impl.cc b/metricsd/uploader/bn_metricsd_impl.cc
index 2cbc2da..219ed60 100644
--- a/metricsd/uploader/bn_metricsd_impl.cc
+++ b/metricsd/uploader/bn_metricsd_impl.cc
@@ -19,8 +19,6 @@
#include <base/metrics/histogram.h>
#include <base/metrics/sparse_histogram.h>
#include <base/metrics/statistics_recorder.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/String8.h>
@@ -37,16 +35,6 @@
CHECK(counters_) << "Invalid counters argument to constructor";
}
-void BnMetricsdImpl::Run() {
- android::status_t status =
- android::defaultServiceManager()->addService(getInterfaceDescriptor(),
- this);
- CHECK(status == android::OK) << "Metricsd service registration failed";
- android::ProcessState::self()->setThreadPoolMaxThreadCount(0);
- android::IPCThreadState::self()->disableBackgroundScheduling(true);
- android::IPCThreadState::self()->joinThreadPool();
-}
-
Status BnMetricsdImpl::recordHistogram(
const String16& name, int sample, int min, int max, int nbuckets) {
base::HistogramBase* histogram = base::Histogram::FactoryGet(
diff --git a/metricsd/uploader/bn_metricsd_impl.h b/metricsd/uploader/bn_metricsd_impl.h
index 016ccb6..bf47e80 100644
--- a/metricsd/uploader/bn_metricsd_impl.h
+++ b/metricsd/uploader/bn_metricsd_impl.h
@@ -25,9 +25,6 @@
explicit BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters);
virtual ~BnMetricsdImpl() = default;
- // Starts the binder main loop.
- void Run();
-
// Records a histogram.
android::binder::Status recordHistogram(const android::String16& name,
int sample,
diff --git a/metricsd/uploader/metrics_log.cc b/metricsd/uploader/metrics_log.cc
index a01b5da..39655e6 100644
--- a/metricsd/uploader/metrics_log.cc
+++ b/metricsd/uploader/metrics_log.cc
@@ -18,6 +18,8 @@
#include <string>
+#include <base/files/file_util.h>
+
#include "uploader/proto/system_profile.pb.h"
#include "uploader/system_profile_setter.h"
@@ -27,6 +29,40 @@
: MetricsLogBase("", 0, metrics::MetricsLogBase::ONGOING_LOG, "") {
}
+bool MetricsLog::LoadFromFile(const base::FilePath& saved_log) {
+ std::string encoded_log;
+ if (!base::ReadFileToString(saved_log, &encoded_log)) {
+ LOG(ERROR) << "Failed to read the metrics log backup from "
+ << saved_log.value();
+ return false;
+ }
+
+ if (!uma_proto()->ParseFromString(encoded_log)) {
+ LOG(ERROR) << "Failed to parse log from " << saved_log.value()
+ << ", deleting the log";
+ base::DeleteFile(saved_log, false);
+ uma_proto()->Clear();
+ return false;
+ }
+
+ VLOG(1) << uma_proto()->histogram_event_size() << " histograms loaded from "
+ << saved_log.value();
+
+ return true;
+}
+
+bool MetricsLog::SaveToFile(const base::FilePath& path) {
+ std::string encoded_log;
+ GetEncodedLog(&encoded_log);
+
+ if (static_cast<int>(encoded_log.size()) !=
+ base::WriteFile(path, encoded_log.data(), encoded_log.size())) {
+ LOG(ERROR) << "Failed to persist the current log to " << path.value();
+ return false;
+ }
+ return true;
+}
+
void MetricsLog::IncrementUserCrashCount(unsigned int count) {
metrics::SystemProfileProto::Stability* stability(
uma_proto()->mutable_system_profile()->mutable_stability());
diff --git a/metricsd/uploader/metrics_log.h b/metricsd/uploader/metrics_log.h
index b76cd72..9e60b97 100644
--- a/metricsd/uploader/metrics_log.h
+++ b/metricsd/uploader/metrics_log.h
@@ -19,6 +19,7 @@
#include <string>
+#include <base/files/file_path.h>
#include <base/macros.h>
#include "uploader/metrics_log_base.h"
@@ -44,8 +45,15 @@
// Populate the system profile with system information using setter.
bool PopulateSystemProfile(SystemProfileSetter* setter);
+ // Load the log from |path|.
+ bool LoadFromFile(const base::FilePath& path);
+
+ // Save this log to |path|.
+ bool SaveToFile(const base::FilePath& path);
+
private:
friend class UploadServiceTest;
+ FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed);
FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts);
FRIEND_TEST(UploadServiceTest, LogKernelCrash);
diff --git a/metricsd/uploader/metricsd_service_runner.cc b/metricsd/uploader/metricsd_service_runner.cc
new file mode 100644
index 0000000..2834977
--- /dev/null
+++ b/metricsd/uploader/metricsd_service_runner.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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 "uploader/metricsd_service_runner.h"
+
+#include <thread>
+
+#include <binder/IServiceManager.h>
+#include <brillo/binder_watcher.h>
+#include <utils/Errors.h>
+
+#include "uploader/bn_metricsd_impl.h"
+
+MetricsdServiceRunner::MetricsdServiceRunner(
+ std::shared_ptr<CrashCounters> counters)
+ : counters_(counters) {}
+
+void MetricsdServiceRunner::Start() {
+ thread_.reset(new std::thread(&MetricsdServiceRunner::Run, this));
+}
+
+void MetricsdServiceRunner::Run() {
+ android::sp<BnMetricsdImpl> metrics_service(new BnMetricsdImpl(counters_));
+
+ android::status_t status = android::defaultServiceManager()->addService(
+ metrics_service->getInterfaceDescriptor(), metrics_service);
+ CHECK(status == android::OK) << "Metricsd service registration failed";
+
+ message_loop_for_io_.reset(new base::MessageLoopForIO);
+
+ brillo::BinderWatcher watcher;
+ CHECK(watcher.Init()) << "failed to initialize the binder file descriptor "
+ << "watcher";
+
+ message_loop_for_io_->Run();
+
+ // Delete the message loop here as it needs to be deconstructed in the thread
+ // it is attached to.
+ message_loop_for_io_.reset();
+}
+
+void MetricsdServiceRunner::Stop() {
+ message_loop_for_io_->PostTask(FROM_HERE,
+ message_loop_for_io_->QuitClosure());
+
+ thread_->join();
+}
diff --git a/metricsd/uploader/metricsd_service_runner.h b/metricsd/uploader/metricsd_service_runner.h
new file mode 100644
index 0000000..1715de0
--- /dev/null
+++ b/metricsd/uploader/metricsd_service_runner.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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 METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
+#define METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
+
+#include <memory>
+#include <thread>
+
+#include <base/message_loop/message_loop.h>
+
+#include "uploader/crash_counters.h"
+
+class MetricsdServiceRunner {
+ public:
+ MetricsdServiceRunner(std::shared_ptr<CrashCounters> counters);
+
+ // Start the Metricsd Binder service in a new thread.
+ void Start();
+
+ // Stop the Metricsd service and wait for its thread to exit.
+ void Stop();
+
+ private:
+ // Creates and run the main loop for metricsd's Binder service.
+ void Run();
+
+ std::unique_ptr<base::MessageLoopForIO> message_loop_for_io_;
+
+ std::unique_ptr<std::thread> thread_;
+ std::shared_ptr<CrashCounters> counters_;
+};
+
+#endif // METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
diff --git a/metricsd/uploader/upload_service.cc b/metricsd/uploader/upload_service.cc
index 2fb30c3..0dc59a4 100644
--- a/metricsd/uploader/upload_service.cc
+++ b/metricsd/uploader/upload_service.cc
@@ -42,49 +42,89 @@
UploadService::UploadService(const std::string& server,
const base::TimeDelta& upload_interval,
+ const base::TimeDelta& disk_persistence_interval,
const base::FilePath& private_metrics_directory,
- const base::FilePath& shared_metrics_directory,
- const std::shared_ptr<CrashCounters> counters)
- : histogram_snapshot_manager_(this),
+ const base::FilePath& shared_metrics_directory)
+ : brillo::Daemon(),
+ histogram_snapshot_manager_(this),
sender_(new HttpSender(server)),
failed_upload_count_(metrics::kFailedUploadCountName,
private_metrics_directory),
- counters_(counters),
- upload_interval_(upload_interval) {
+ counters_(new CrashCounters),
+ upload_interval_(upload_interval),
+ disk_persistence_interval_(disk_persistence_interval),
+ metricsd_service_runner_(counters_) {
staged_log_path_ = private_metrics_directory.Append(metrics::kStagedLogName);
+ saved_log_path_ = private_metrics_directory.Append(metrics::kSavedLogName);
consent_file_ = shared_metrics_directory.Append(metrics::kConsentFileName);
}
+void UploadService::LoadSavedLog() {
+ if (base::PathExists(saved_log_path_)) {
+ GetOrCreateCurrentLog()->LoadFromFile(saved_log_path_);
+ }
+}
+
int UploadService::OnInit() {
+ brillo::Daemon::OnInit();
+
+ base::StatisticsRecorder::Initialize();
+ metricsd_service_runner_.Start();
+
system_profile_setter_.reset(new SystemProfileCache());
- base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
- base::Bind(&UploadService::UploadEventCallback,
- base::Unretained(this),
- upload_interval_),
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)),
upload_interval_);
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)),
+ disk_persistence_interval_);
+
+ LoadSavedLog();
+
return EX_OK;
}
+void UploadService::OnShutdown(int* exit_code) {
+ metricsd_service_runner_.Stop();
+ PersistToDisk();
+}
+
void UploadService::InitForTest(SystemProfileSetter* setter) {
+ LoadSavedLog();
system_profile_setter_.reset(setter);
}
void UploadService::StartNewLog() {
- CHECK(!HasStagedLog()) << "the staged log should be discarded before "
- << "starting a new metrics log";
- MetricsLog* log = new MetricsLog();
- current_log_.reset(log);
+ current_log_.reset(new MetricsLog());
}
-void UploadService::UploadEventCallback(const base::TimeDelta& interval) {
+void UploadService::UploadEventCallback() {
UploadEvent();
- base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
- base::Bind(&UploadService::UploadEventCallback,
- base::Unretained(this),
- interval),
- interval);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)),
+ upload_interval_);
+}
+
+void UploadService::PersistEventCallback() {
+ PersistToDisk();
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)),
+ disk_persistence_interval_);
+}
+
+void UploadService::PersistToDisk() {
+ GatherHistograms();
+ if (current_log_) {
+ current_log_->SaveToFile(saved_log_path_);
+ }
}
void UploadService::UploadEvent() {
@@ -178,14 +218,16 @@
<< "log.";
return;
}
- std::string encoded_log;
- staged_log->GetEncodedLog(&encoded_log);
+
+ if (!base::DeleteFile(saved_log_path_, false)) {
+ // There is a chance that we will upload the same metrics twice but, if we
+ // are lucky, the backup should be overridden before that. In doubt, try not
+ // to lose any metrics.
+ LOG(ERROR) << "failed to delete the last backup of the current log.";
+ }
failed_upload_count_.Set(0);
- if (static_cast<int>(encoded_log.size()) != base::WriteFile(
- staged_log_path_, encoded_log.data(), encoded_log.size())) {
- LOG(ERROR) << "failed to persist to " << staged_log_path_.value();
- }
+ staged_log->SaveToFile(staged_log_path_);
}
MetricsLog* UploadService::GetOrCreateCurrentLog() {
diff --git a/metricsd/uploader/upload_service.h b/metricsd/uploader/upload_service.h
index 420653e..a1d9d3b 100644
--- a/metricsd/uploader/upload_service.h
+++ b/metricsd/uploader/upload_service.h
@@ -28,6 +28,7 @@
#include "persistent_integer.h"
#include "uploader/crash_counters.h"
#include "uploader/metrics_log.h"
+#include "uploader/metricsd_service_runner.h"
#include "uploader/proto/chrome_user_metrics_extension.pb.h"
#include "uploader/sender.h"
#include "uploader/system_profile_cache.h"
@@ -65,19 +66,22 @@
public:
UploadService(const std::string& server,
const base::TimeDelta& upload_interval,
+ const base::TimeDelta& disk_persistence_interval,
const base::FilePath& private_metrics_directory,
- const base::FilePath& shared_metrics_directory,
- const std::shared_ptr<CrashCounters> counters);
+ const base::FilePath& shared_metrics_directory);
// Initializes the upload service.
- int OnInit();
+ int OnInit() override;
+
+ // Cleans up the internal state before exiting.
+ void OnShutdown(int* exit_code) override;
// Starts a new log. The log needs to be regenerated after each successful
// launch as it is destroyed when staging the log.
void StartNewLog();
- // Event callback for handling MessageLoop events.
- void UploadEventCallback(const base::TimeDelta& interval);
+ // Saves the current metrics to a file.
+ void PersistToDisk();
// Triggers an upload event.
void UploadEvent();
@@ -97,6 +101,8 @@
friend class UploadServiceTest;
FRIEND_TEST(UploadServiceTest, CanSendMultipleTimes);
+ FRIEND_TEST(UploadServiceTest, CorruptedSavedLog);
+ FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed);
FRIEND_TEST(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload);
FRIEND_TEST(UploadServiceTest, EmptyLogsAreNotSent);
FRIEND_TEST(UploadServiceTest, FailedSendAreRetried);
@@ -108,6 +114,7 @@
FRIEND_TEST(UploadServiceTest, LogKernelCrash);
FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
FRIEND_TEST(UploadServiceTest, LogUserCrash);
+ FRIEND_TEST(UploadServiceTest, PersistEmptyLog);
FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
@@ -118,12 +125,21 @@
// will be discarded.
static const int kMaxFailedUpload;
+ // Loads the log saved to disk if it exists.
+ void LoadSavedLog();
+
// Resets the internal state.
void Reset();
// Returns true iff metrics reporting is enabled.
bool AreMetricsEnabled();
+ // Event callback for handling Upload events.
+ void UploadEventCallback();
+
+ // Event callback for handling Persist events.
+ void PersistEventCallback();
+
// Aggregates all histogram available in memory and store them in the current
// log.
void GatherHistograms();
@@ -153,9 +169,13 @@
std::shared_ptr<CrashCounters> counters_;
base::TimeDelta upload_interval_;
+ base::TimeDelta disk_persistence_interval_;
+
+ MetricsdServiceRunner metricsd_service_runner_;
base::FilePath consent_file_;
base::FilePath staged_log_path_;
+ base::FilePath saved_log_path_;
bool testing_;
};
diff --git a/metricsd/uploader/upload_service_test.cc b/metricsd/uploader/upload_service_test.cc
index ec507e8..70112f4 100644
--- a/metricsd/uploader/upload_service_test.cc
+++ b/metricsd/uploader/upload_service_test.cc
@@ -45,18 +45,18 @@
ASSERT_FALSE(base::StatisticsRecorder::IsActive());
base::StatisticsRecorder::Initialize();
- base::FilePath private_dir = dir_.path().Append("private");
- base::FilePath shared_dir = dir_.path().Append("shared");
+ private_dir_ = dir_.path().Append("private");
+ shared_dir_ = dir_.path().Append("shared");
- EXPECT_TRUE(base::CreateDirectory(private_dir));
- EXPECT_TRUE(base::CreateDirectory(shared_dir));
+ EXPECT_TRUE(base::CreateDirectory(private_dir_));
+ EXPECT_TRUE(base::CreateDirectory(shared_dir_));
- ASSERT_EQ(0, base::WriteFile(shared_dir.Append(metrics::kConsentFileName),
+ ASSERT_EQ(0, base::WriteFile(shared_dir_.Append(metrics::kConsentFileName),
"", 0));
- counters_.reset(new CrashCounters);
- upload_service_.reset(new UploadService("", base::TimeDelta(), private_dir,
- shared_dir, counters_));
+ upload_service_.reset(new UploadService(
+ "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+ counters_ = upload_service_->counters_;
upload_service_->sender_.reset(new SenderMock);
upload_service_->InitForTest(new MockSystemProfileSetter);
@@ -81,15 +81,16 @@
base::FilePath filepath =
dir_.path().Append("etc/os-release.d").Append(name);
ASSERT_TRUE(base::CreateDirectory(filepath.DirName()));
- ASSERT_EQ(
- value.size(),
- base::WriteFile(filepath, value.data(), value.size()));
+ ASSERT_EQ(value.size(),
+ base::WriteFile(filepath, value.data(), value.size()));
}
const metrics::SystemProfileProto_Stability GetCurrentStability() {
EXPECT_TRUE(upload_service_->current_log_.get());
- return upload_service_->current_log_->uma_proto()->system_profile().stability();
+ return upload_service_->current_log_->uma_proto()
+ ->system_profile()
+ .stability();
}
base::ScopedTempDir dir_;
@@ -97,6 +98,8 @@
std::unique_ptr<base::AtExitManager> exit_manager_;
std::shared_ptr<CrashCounters> counters_;
+ base::FilePath private_dir_;
+ base::FilePath shared_dir_;
};
TEST_F(UploadServiceTest, FailedSendAreRetried) {
@@ -149,12 +152,9 @@
}
TEST_F(UploadServiceTest, LogEmptyByDefault) {
- UploadService upload_service("", base::TimeDelta(), dir_.path(), dir_.path(),
- std::make_shared<CrashCounters>());
-
- // current_log_ should be initialized later as it needs AtExitManager to exit
+ // current_log_ should be initialized later as it needs AtExitManager to exist
// in order to gather system information from SysInfo.
- EXPECT_FALSE(upload_service.current_log_);
+ EXPECT_FALSE(upload_service_->current_log_);
}
TEST_F(UploadServiceTest, CanSendMultipleTimes) {
@@ -222,10 +222,8 @@
}
TEST_F(UploadServiceTest, ExtractChannelFromString) {
- EXPECT_EQ(
- SystemProfileCache::ProtoChannelFromString(
- "developer-build"),
- metrics::SystemProfileProto::CHANNEL_UNKNOWN);
+ EXPECT_EQ(SystemProfileCache::ProtoChannelFromString("developer-build"),
+ metrics::SystemProfileProto::CHANNEL_UNKNOWN);
EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_DEV,
SystemProfileCache::ProtoChannelFromString("dev-channel"));
@@ -300,3 +298,38 @@
SetTestingProperty(metrics::kProductId, "hello");
ASSERT_TRUE(cache.Initialize());
}
+
+TEST_F(UploadServiceTest, CurrentLogSavedAndResumed) {
+ SendHistogram("hello", 10, 0, 100, 10);
+ upload_service_->PersistToDisk();
+ EXPECT_EQ(
+ 1, upload_service_->current_log_->uma_proto()->histogram_event().size());
+ upload_service_.reset(new UploadService(
+ "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+ upload_service_->InitForTest(nullptr);
+
+ SendHistogram("hello", 10, 0, 100, 10);
+ upload_service_->GatherHistograms();
+ EXPECT_EQ(2, upload_service_->GetOrCreateCurrentLog()
+ ->uma_proto()
+ ->histogram_event()
+ .size());
+}
+
+TEST_F(UploadServiceTest, PersistEmptyLog) {
+ upload_service_->PersistToDisk();
+ EXPECT_FALSE(base::PathExists(upload_service_->saved_log_path_));
+}
+
+TEST_F(UploadServiceTest, CorruptedSavedLog) {
+ // Write a bogus saved log.
+ EXPECT_EQ(5, base::WriteFile(upload_service_->saved_log_path_, "hello", 5));
+
+ upload_service_.reset(new UploadService(
+ "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+
+ upload_service_->InitForTest(nullptr);
+ // If the log is unreadable, we drop it and continue execution.
+ ASSERT_NE(nullptr, upload_service_->GetOrCreateCurrentLog());
+ ASSERT_FALSE(base::PathExists(upload_service_->saved_log_path_));
+}
diff --git a/toolbox/ps.c b/toolbox/ps.c
index 3bc540d..ecc1c9f 100644
--- a/toolbox/ps.c
+++ b/toolbox/ps.c
@@ -57,7 +57,7 @@
int prio, nice, rtprio, sched, psr;
struct passwd *pw;
- sprintf(statline, "/proc/%d", pid);
+ sprintf(statline, "/proc/%d", tid ? tid : pid);
stat(statline, &stats);
if(tid) {