Remove redundant action triggers on persist.sys.usb.config
am: 05e04a134e

Change-Id: I7d94aa8729514e7ce1e4fac301c9d52fa7dc66bc
diff --git a/adb/Android.mk b/adb/Android.mk
index 695cbe0..8f56d74 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -62,6 +62,7 @@
     fdevent_test.cpp \
     socket_test.cpp \
     sysdeps_test.cpp \
+    sysdeps/stat_test.cpp \
     transport_test.cpp \
 
 LIBADB_CFLAGS := \
@@ -89,6 +90,7 @@
 
 LIBADB_windows_SRC_FILES := \
     sysdeps_win32.cpp \
+    sysdeps/win32/stat.cpp \
     usb_windows.cpp \
 
 LIBADB_TEST_windows_SRCS := \
diff --git a/adb/adb_utils_test.cpp b/adb/adb_utils_test.cpp
index f1ebaa1..a78f8d3 100644
--- a/adb/adb_utils_test.cpp
+++ b/adb/adb_utils_test.cpp
@@ -52,18 +52,6 @@
 
   ASSERT_TRUE(directory_exists(profiles_dir));
 
-  // On modern (English?) Windows, this is a directory symbolic link to
-  // C:\ProgramData. Symbolic links are rare on Windows and the user requires
-  // a special permission (by default granted to Administrative users) to
-  // create symbolic links.
-  ASSERT_FALSE(directory_exists(subdir(profiles_dir, "All Users")));
-
-  // On modern (English?) Windows, this is a directory junction to
-  // C:\Users\Default. Junctions are used throughout user profile directories
-  // for backwards compatibility and they don't require any special permissions
-  // to create.
-  ASSERT_FALSE(directory_exists(subdir(profiles_dir, "Default User")));
-
   ASSERT_FALSE(directory_exists(subdir(profiles_dir, "does-not-exist")));
 #else
   ASSERT_TRUE(directory_exists("/proc"));
@@ -72,6 +60,28 @@
 #endif
 }
 
+#if defined(_WIN32)
+TEST(adb_utils, directory_exists_win32_symlink_junction) {
+  char profiles_dir[MAX_PATH];
+  DWORD cch = arraysize(profiles_dir);
+
+  // On typical Windows 7, returns C:\Users
+  ASSERT_TRUE(GetProfilesDirectoryA(profiles_dir, &cch));
+
+  // On modern (English?) Windows, this is a directory symbolic link to
+  // C:\ProgramData. Symbolic links are rare on Windows and the user requires
+  // a special permission (by default granted to Administrative users) to
+  // create symbolic links.
+  EXPECT_FALSE(directory_exists(subdir(profiles_dir, "All Users")));
+
+  // On modern (English?) Windows, this is a directory junction to
+  // C:\Users\Default. Junctions are used throughout user profile directories
+  // for backwards compatibility and they don't require any special permissions
+  // to create.
+  EXPECT_FALSE(directory_exists(subdir(profiles_dir, "Default User")));
+}
+#endif
+
 TEST(adb_utils, escape_arg) {
   ASSERT_EQ(R"('')", escape_arg(""));
 
@@ -113,14 +123,20 @@
 
 void test_mkdirs(const std::string basepath) {
   // Test creating a directory hierarchy.
-  EXPECT_TRUE(mkdirs(basepath));
+  ASSERT_TRUE(mkdirs(basepath));
   // Test finding an existing directory hierarchy.
-  EXPECT_TRUE(mkdirs(basepath));
+  ASSERT_TRUE(mkdirs(basepath));
+  // Test mkdirs on an existing hierarchy with a trailing slash.
+  ASSERT_TRUE(mkdirs(basepath + '/'));
+#if defined(_WIN32)
+  ASSERT_TRUE(mkdirs(basepath + '\\'));
+#endif
+
   const std::string filepath = basepath + "/file";
   // Verify that the hierarchy was created by trying to create a file in it.
-  EXPECT_NE(-1, adb_creat(filepath.c_str(), 0600));
+  ASSERT_NE(-1, adb_creat(filepath.c_str(), 0600));
   // If a file exists where we want a directory, the operation should fail.
-  EXPECT_FALSE(mkdirs(filepath));
+  ASSERT_FALSE(mkdirs(filepath));
 }
 
 TEST(adb_utils, mkdirs) {
diff --git a/adb/bugreport.cpp b/adb/bugreport.cpp
index 64f01bd..9ed44a7 100644
--- a/adb/bugreport.cpp
+++ b/adb/bugreport.cpp
@@ -14,28 +14,40 @@
  * limitations under the License.
  */
 
+#define TRACE_TAG ADB
+
+#include "bugreport.h"
+
 #include <string>
+#include <vector>
 
 #include <android-base/strings.h>
 
-#include "bugreport.h"
+#include "sysdeps.h"
+#include "adb_utils.h"
 #include "file_sync_service.h"
 
-static constexpr char BUGZ_OK_PREFIX[] = "OK:";
-static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
+static constexpr char BUGZ_BEGIN_PREFIX[] = "BEGIN:";
 static constexpr char BUGZ_PROGRESS_PREFIX[] = "PROGRESS:";
 static constexpr char BUGZ_PROGRESS_SEPARATOR[] = "/";
+static constexpr char BUGZ_OK_PREFIX[] = "OK:";
+static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
 
 // Custom callback used to handle the output of zipped bugreports.
 class BugreportStandardStreamsCallback : public StandardStreamsCallbackInterface {
   public:
-    BugreportStandardStreamsCallback(const std::string& dest_file, bool show_progress, Bugreport* br)
+    BugreportStandardStreamsCallback(const std::string& dest_dir, const std::string& dest_file,
+                                     bool show_progress, Bugreport* br)
         : br_(br),
+          src_file_(),
+          dest_dir_(dest_dir),
           dest_file_(dest_file),
-          line_message_(android::base::StringPrintf("generating %s", dest_file_.c_str())),
+          line_message_(),
+          invalid_lines_(),
           show_progress_(show_progress),
-          status_(-1),
+          status_(0),
           line_() {
+        SetLineMessage();
     }
 
     void OnStdout(const char* buffer, int length) {
@@ -54,28 +66,72 @@
         OnStream(nullptr, stderr, buffer, length);
     }
     int Done(int unused_) {
-        // Process remaining line, if any...
+        // Process remaining line, if any.
         ProcessLine(line_);
-        // ..then return.
+
+        // Warn about invalid lines, if any.
+        if (!invalid_lines_.empty()) {
+            fprintf(stderr,
+                    "WARNING: bugreportz generated %zu line(s) with unknown commands, "
+                    "device might not support zipped bugreports:\n",
+                    invalid_lines_.size());
+            for (const auto& line : invalid_lines_) {
+                fprintf(stderr, "\t%s\n", line.c_str());
+            }
+            fprintf(stderr,
+                    "If the zipped bugreport was not generated, try 'adb bugreport' instead.\n");
+        }
+
+        // Pull the generated bug report.
+        if (status_ == 0) {
+            if (src_file_.empty()) {
+                fprintf(stderr, "bugreportz did not return a '%s' or '%s' line\n", BUGZ_OK_PREFIX,
+                        BUGZ_FAIL_PREFIX);
+                return -1;
+            }
+            std::string destination;
+            if (dest_dir_.empty()) {
+                destination = dest_file_;
+            } else {
+                destination = android::base::StringPrintf("%s%c%s", dest_dir_.c_str(),
+                                                          OS_PATH_SEPARATOR, dest_file_.c_str());
+            }
+            std::vector<const char*> srcs{src_file_.c_str()};
+            status_ =
+                br_->DoSyncPull(srcs, destination.c_str(), true, line_message_.c_str()) ? 0 : 1;
+            if (status_ != 0) {
+                fprintf(stderr,
+                        "Bug report finished but could not be copied to '%s'.\n"
+                        "Try to run 'adb pull %s <directory>'\n"
+                        "to copy it to a directory that can be written.\n",
+                        destination.c_str(), src_file_.c_str());
+            }
+        }
         return status_;
     }
 
   private:
+    void SetLineMessage() {
+        line_message_ =
+            android::base::StringPrintf("generating %s", adb_basename(dest_file_).c_str());
+    }
+
+    void SetSrcFile(const std::string path) {
+        src_file_ = path;
+        if (!dest_dir_.empty()) {
+            // Only uses device-provided name when user passed a directory.
+            dest_file_ = adb_basename(path);
+            SetLineMessage();
+        }
+    }
+
     void ProcessLine(const std::string& line) {
         if (line.empty()) return;
 
-        if (android::base::StartsWith(line, BUGZ_OK_PREFIX)) {
-            if (show_progress_) {
-                // Make sure pull message doesn't conflict with generation message.
-                br_->UpdateProgress(line_message_, 100, 100);
-            }
-
-            const char* zip_file = &line[strlen(BUGZ_OK_PREFIX)];
-            std::vector<const char*> srcs{zip_file};
-            status_ = br_->DoSyncPull(srcs, dest_file_.c_str(), true, line_message_.c_str()) ? 0 : 1;
-            if (status_ != 0) {
-                fprintf(stderr, "Could not copy file '%s' to '%s'\n", zip_file, dest_file_.c_str());
-            }
+        if (android::base::StartsWith(line, BUGZ_BEGIN_PREFIX)) {
+            SetSrcFile(&line[strlen(BUGZ_BEGIN_PREFIX)]);
+        } else if (android::base::StartsWith(line, BUGZ_OK_PREFIX)) {
+            SetSrcFile(&line[strlen(BUGZ_OK_PREFIX)]);
         } else if (android::base::StartsWith(line, BUGZ_FAIL_PREFIX)) {
             const char* error_message = &line[strlen(BUGZ_FAIL_PREFIX)];
             fprintf(stderr, "Device failed to take a zipped bugreport: %s\n", error_message);
@@ -89,24 +145,38 @@
             size_t idx2 = line.rfind(BUGZ_PROGRESS_SEPARATOR);
             int progress = std::stoi(line.substr(idx1, (idx2 - idx1)));
             int total = std::stoi(line.substr(idx2 + 1));
-            br_->UpdateProgress(dest_file_, progress, total);
+            br_->UpdateProgress(line_message_, progress, total);
         } else {
-            fprintf(stderr,
-                    "WARNING: unexpected line (%s) returned by bugreportz, "
-                    "device probably does not support zipped bugreports.\n"
-                    "Try 'adb bugreport' instead.",
-                    line.c_str());
+            invalid_lines_.push_back(line);
         }
     }
 
     Bugreport* br_;
-    const std::string dest_file_;
-    const std::string line_message_;
+
+    // Path of bugreport on device.
+    std::string src_file_;
+
+    // Bugreport destination on host, depending on argument passed on constructor:
+    // - if argument is a directory, dest_dir_ is set with it and dest_file_ will be the name
+    //   of the bugreport reported by the device.
+    // - if argument is empty, dest_dir is set as the current directory and dest_file_ will be the
+    //   name of the bugreport reported by the device.
+    // - otherwise, dest_dir_ is not set and dest_file_ is set with the value passed on constructor.
+    std::string dest_dir_, dest_file_;
+
+    // Message displayed on LinePrinter, it's updated every time the destination above change.
+    std::string line_message_;
+
+    // Lines sent by bugreportz that contain invalid commands; will be displayed at the end.
+    std::vector<std::string> invalid_lines_;
+
+    // Whether PROGRESS_LINES should be interpreted as progress.
     bool show_progress_;
+
+    // Overall process of the operation, as returned by Done().
     int status_;
 
-    // Temporary buffer containing the characters read since the last newline
-    // (\n).
+    // Temporary buffer containing the characters read since the last newline (\n).
     std::string line_;
 
     DISALLOW_COPY_AND_ASSIGN(BugreportStandardStreamsCallback);
@@ -116,34 +186,62 @@
 int usage();
 
 int Bugreport::DoIt(TransportType transport_type, const char* serial, int argc, const char** argv) {
-    if (argc == 1) return SendShellCommand(transport_type, serial, "bugreport", false);
-    if (argc != 2) return usage();
-
-    // Zipped bugreport option - will call 'bugreportz', which prints the location
-    // of the generated
-    // file, then pull it to the destination file provided by the user.
-    std::string dest_file = argv[1];
-    if (!android::base::EndsWith(argv[1], ".zip")) {
-        // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
-        dest_file += ".zip";
-    }
+    if (argc > 2) return usage();
 
     // Gets bugreportz version.
-    std::string bugz_stderr;
-    DefaultStandardStreamsCallback version_callback(nullptr, &bugz_stderr);
+    std::string bugz_stdout, bugz_stderr;
+    DefaultStandardStreamsCallback version_callback(&bugz_stdout, &bugz_stderr);
     int status = SendShellCommand(transport_type, serial, "bugreportz -v", false, &version_callback);
-
-    if (status != 0) {
-        fprintf(stderr,
-                "Failed to get bugreportz version: 'bugreport -v' returned '%s' "
-                "(code %d)."
-                "\nIf the device does not support it, try running 'adb bugreport' "
-                "to get a "
-                "flat-file bugreport.",
-                bugz_stderr.c_str(), status);
-        return status;
-    }
     std::string bugz_version = android::base::Trim(bugz_stderr);
+    std::string bugz_output = android::base::Trim(bugz_stdout);
+
+    if (status != 0 || bugz_version.empty()) {
+        D("'bugreportz' -v results: status=%d, stdout='%s', stderr='%s'", status,
+          bugz_output.c_str(), bugz_version.c_str());
+        if (argc == 1) {
+            // Device does not support bugreportz: if called as 'adb bugreport', just falls out to
+            // the flat-file version.
+            fprintf(stderr,
+                    "Failed to get bugreportz version, which is only available on devices "
+                    "running Android 7.0 or later.\nTrying a plain-text bug report instead.\n");
+            return SendShellCommand(transport_type, serial, "bugreport", false);
+        }
+
+        // But if user explicitly asked for a zipped bug report, fails instead (otherwise calling
+        // 'bugreport' would generate a lot of output the user might not be prepared to handle).
+        fprintf(stderr,
+                "Failed to get bugreportz version: 'bugreportz -v' returned '%s' (code %d).\n"
+                "If the device does not run Android 7.0 or above, try 'adb bugreport' instead.\n",
+                bugz_output.c_str(), status);
+        return status != 0 ? status : -1;
+    }
+
+    std::string dest_file, dest_dir;
+
+    if (argc == 1) {
+        // No args - use current directory
+        if (!getcwd(&dest_dir)) {
+            perror("adb: getcwd failed");
+            return 1;
+        }
+    } else {
+        // Check whether argument is a directory or file
+        if (directory_exists(argv[1])) {
+            dest_dir = argv[1];
+        } else {
+            dest_file = argv[1];
+        }
+    }
+
+    if (dest_file.empty()) {
+        // Uses a default value until device provides the proper name
+        dest_file = "bugreport.zip";
+    } else {
+        if (!android::base::EndsWith(dest_file, ".zip")) {
+            // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
+            dest_file += ".zip";
+        }
+    }
 
     bool show_progress = true;
     std::string bugz_command = "bugreportz -p";
@@ -153,12 +251,11 @@
         fprintf(stderr,
                 "Bugreport is in progress and it could take minutes to complete.\n"
                 "Please be patient and do not cancel or disconnect your device "
-                "until it completes."
-                "\n");
+                "until it completes.\n");
         show_progress = false;
         bugz_command = "bugreportz";
     }
-    BugreportStandardStreamsCallback bugz_callback(dest_file, show_progress, this);
+    BugreportStandardStreamsCallback bugz_callback(dest_dir, dest_file, show_progress, this);
     return SendShellCommand(transport_type, serial, bugz_command, false, &bugz_callback);
 }
 
diff --git a/adb/bugreport_test.cpp b/adb/bugreport_test.cpp
index a89d8dc..3cd2b6d 100644
--- a/adb/bugreport_test.cpp
+++ b/adb/bugreport_test.cpp
@@ -19,6 +19,12 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+
+#include "sysdeps.h"
+#include "adb_utils.h"
+
 using ::testing::_;
 using ::testing::Action;
 using ::testing::ActionInterface;
@@ -30,7 +36,9 @@
 using ::testing::StrEq;
 using ::testing::WithArg;
 using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
 using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
 
 // Empty function so tests don't need to be linked against file_sync_service.cpp, which requires
 // SELinux and its transitive dependencies...
@@ -122,81 +130,139 @@
 
 class BugreportTest : public ::testing::Test {
   public:
-    void SetBugreportzVersion(const std::string& version) {
+    void SetUp() {
+        if (!getcwd(&cwd_)) {
+            ADD_FAILURE() << "getcwd failed: " << strerror(errno);
+            return;
+        }
+    }
+
+    void ExpectBugreportzVersion(const std::string& version) {
         EXPECT_CALL(br_,
                     SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
             .WillOnce(DoAll(WithArg<4>(WriteOnStderr(version.c_str())),
                             WithArg<4>(ReturnCallbackDone(0))));
     }
 
-    void ExpectProgress(int progress, int total) {
-        EXPECT_CALL(br_, UpdateProgress(HasSubstr("file.zip"), progress, total));
+    void ExpectProgress(int progress, int total, const std::string& file = "file.zip") {
+        EXPECT_CALL(br_, UpdateProgress(StrEq("generating " + file), progress, total));
     }
 
     BugreportMock br_;
+    std::string cwd_;  // TODO: make it static
 };
 
-// Tests when called with invalid number of argumnts
+// Tests when called with invalid number of arguments
 TEST_F(BugreportTest, InvalidNumberArgs) {
-    const char* args[1024] = {"bugreport", "to", "principal"};
+    const char* args[] = {"bugreport", "to", "principal"};
     ASSERT_EQ(-42, br_.DoIt(kTransportLocal, "HannibalLecter", 3, args));
 }
 
-// Tests the legacy 'adb bugreport' option
-TEST_F(BugreportTest, FlatFileFormat) {
+// Tests the 'adb bugreport' option when the device does not support 'bugreportz' - it falls back
+// to the flat-file format ('bugreport' binary on device)
+TEST_F(BugreportTest, NoArgumentsPreNDevice) {
+    // clang-format off
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStderr("")),
+                        // Write some bogus output on stdout to make sure it's ignored
+                        WithArg<4>(WriteOnStdout("Dude, where is my bugreportz?")),
+                        WithArg<4>(ReturnCallbackDone(0))));
+    // clang-format on
+    std::string bugreport = "Reported the bug was.";
+    CaptureStdout();
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreport", false, _))
-        .WillOnce(Return(0));
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout(bugreport)), Return(0)));
 
-    const char* args[1024] = {"bugreport"};
+    const char* args[] = {"bugreport"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
+    ASSERT_THAT(GetCapturedStdout(), StrEq(bugreport));
+}
+
+// Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.0 - it will
+// save the bugreport in the current directory with the name provided by the device.
+TEST_F(BugreportTest, NoArgumentsNDevice) {
+    ExpectBugreportzVersion("1.0");
+
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR);
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("generating da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport"};
     ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
 }
 
-// Tests 'adb bugreport file.zip' when it succeeds and device does not support
-// progress.
-TEST_F(BugreportTest, OkLegacy) {
-    SetBugreportzVersion("1.0");
+// Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.1 - it will
+// save the bugreport in the current directory with the name provided by the device.
+TEST_F(BugreportTest, NoArgumentsPostNDevice) {
+    ExpectBugreportzVersion("1.1");
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR);
+    ExpectProgress(50, 100, "da_bugreport.zip");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
+                        WithArg<4>(WriteOnStdout("PROGRESS:50/100\n")),
+                        WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip\n")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("generating da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
+}
+
+// Tests 'adb bugreport file.zip' when it succeeds and device does not support progress.
+TEST_F(BugreportTest, OkNDevice) {
+    ExpectBugreportzVersion("1.0");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
         .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
                         WithArg<4>(ReturnCallbackDone())));
     EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
-                                true, HasSubstr("file.zip")))
+                                true, StrEq("generating file.zip")))
         .WillOnce(Return(true));
 
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
 }
 
 // Tests 'adb bugreport file.zip' when it succeeds but response was sent in
 // multiple buffer writers and without progress updates.
-TEST_F(BugreportTest, OkLegacySplitBuffer) {
-    SetBugreportzVersion("1.0");
+TEST_F(BugreportTest, OkNDeviceSplitBuffer) {
+    ExpectBugreportzVersion("1.0");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
         .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device")),
                         WithArg<4>(WriteOnStdout("/bugreport.zip")),
                         WithArg<4>(ReturnCallbackDone())));
     EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
-                                true, HasSubstr("file.zip")))
+                                true, StrEq("generating file.zip")))
         .WillOnce(Return(true));
 
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
 }
 
 // Tests 'adb bugreport file.zip' when it succeeds and displays progress.
-TEST_F(BugreportTest, Ok) {
-    SetBugreportzVersion("1.1");
+TEST_F(BugreportTest, OkProgress) {
+    ExpectBugreportzVersion("1.1");
     ExpectProgress(1, 100);
     ExpectProgress(10, 100);
     ExpectProgress(50, 100);
     ExpectProgress(99, 100);
-    ExpectProgress(100, 100);
     // clang-format off
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit...
         .WillOnce(DoAll(
+            // Name might change on OK, so make sure the right one is picked.
+            WithArg<4>(WriteOnStdout("BEGIN:/device/bugreport___NOT.zip\n")),
             // Progress line in one write
             WithArg<4>(WriteOnStdout("PROGRESS:1/100\n")),
             // Add some bogus lines
-            WithArg<4>(WriteOnStdout("\nDUDE:SWEET\n\n")),
+            WithArg<4>(WriteOnStdout("\nDUDE:SWEET\n\nBLA\n\nBLA\nBLA\n\n")),
             // Multiple progress lines in one write
             WithArg<4>(WriteOnStdout("PROGRESS:10/100\nPROGRESS:50/100\n")),
             // Progress line in multiple writes
@@ -207,38 +273,76 @@
             WithArg<4>(WriteOnStdout("OK:/device/bugreport")),
             WithArg<4>(WriteOnStdout(".zip")),
             WithArg<4>(ReturnCallbackDone())));
+    // clang-format on
     EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
-                                true, HasSubstr("file.zip")))
+                                true, StrEq("generating file.zip")))
         .WillOnce(Return(true));
 
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport dir' when it succeeds and destination is a directory.
+TEST_F(BugreportTest, OkDirectory) {
+    ExpectBugreportzVersion("1.1");
+    TemporaryDir td;
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR);
+
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
+                        WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("generating da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", td.path};
     ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
 }
 
 // Tests 'adb bugreport file' when it succeeds
 TEST_F(BugreportTest, OkNoExtension) {
-    SetBugreportzVersion("1.1");
-    ExpectProgress(100, 100);
+    ExpectBugreportzVersion("1.1");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
         .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip\n")),
                         WithArg<4>(ReturnCallbackDone())));
     EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
-                                true, HasSubstr("file.zip")))
+                                true, StrEq("generating file.zip")))
         .WillOnce(Return(true));
 
-    const char* args[1024] = {"bugreport", "file"};
+    const char* args[] = {"bugreport", "file"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport dir' when it succeeds and destination is a directory and device runs N.
+TEST_F(BugreportTest, OkNDeviceDirectory) {
+    ExpectBugreportzVersion("1.0");
+    TemporaryDir td;
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR);
+
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
+                        WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("generating da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", td.path};
     ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
 }
 
 // Tests 'adb bugreport file.zip' when the bugreport itself failed
 TEST_F(BugreportTest, BugreportzReturnedFail) {
-    SetBugreportzVersion("1.1");
+    ExpectBugreportzVersion("1.1");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
         .WillOnce(
             DoAll(WithArg<4>(WriteOnStdout("FAIL:D'OH!\n")), WithArg<4>(ReturnCallbackDone())));
 
     CaptureStderr();
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
     ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
 }
@@ -247,13 +351,13 @@
 // was sent in
 // multiple buffer writes
 TEST_F(BugreportTest, BugreportzReturnedFailSplitBuffer) {
-    SetBugreportzVersion("1.1");
+    ExpectBugreportzVersion("1.1");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
         .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL")), WithArg<4>(WriteOnStdout(":D'OH!\n")),
                         WithArg<4>(ReturnCallbackDone())));
 
     CaptureStderr();
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
     ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
 }
@@ -261,13 +365,13 @@
 // Tests 'adb bugreport file.zip' when the bugreportz returned an unsupported
 // response.
 TEST_F(BugreportTest, BugreportzReturnedUnsupported) {
-    SetBugreportzVersion("1.1");
+    ExpectBugreportzVersion("1.1");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
         .WillOnce(DoAll(WithArg<4>(WriteOnStdout("bugreportz? What am I, a zombie?")),
                         WithArg<4>(ReturnCallbackDone())));
 
     CaptureStderr();
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
     ASSERT_THAT(GetCapturedStderr(), HasSubstr("bugreportz? What am I, a zombie?"));
 }
@@ -277,24 +381,31 @@
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
         .WillOnce(Return(666));
 
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(666, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
 }
 
+// Tests 'adb bugreport file.zip' when the bugreportz -v returns status 0 but with no output.
+TEST_F(BugreportTest, BugreportzVersionEmpty) {
+    ExpectBugreportzVersion("");
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
 // Tests 'adb bugreport file.zip' when the main bugreportz command failed
 TEST_F(BugreportTest, BugreportzFailed) {
-    SetBugreportzVersion("1.1");
+    ExpectBugreportzVersion("1.1");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
         .WillOnce(Return(666));
 
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(666, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
 }
 
 // Tests 'adb bugreport file.zip' when the bugreport could not be pulled
 TEST_F(BugreportTest, PullFails) {
-    SetBugreportzVersion("1.1");
-    ExpectProgress(100, 100);
+    ExpectBugreportzVersion("1.1");
     EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
         .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
                         WithArg<4>(ReturnCallbackDone())));
@@ -302,6 +413,6 @@
                                 true, HasSubstr("file.zip")))
         .WillOnce(Return(false));
 
-    const char* args[1024] = {"bugreport", "file.zip"};
+    const char* args[] = {"bugreport", "file.zip"};
     ASSERT_EQ(1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
 }
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 0685b70..23827de 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -81,6 +81,7 @@
 
 static void help() {
     fprintf(stderr, "%s\n", adb_version().c_str());
+    // clang-format off
     fprintf(stderr,
         " -a                            - directs adb to listen on all interfaces for a connection\n"
         " -d                            - directs command to the only connected USB device\n"
@@ -173,9 +174,11 @@
         "                                 (-g: grant all runtime permissions)\n"
         "  adb uninstall [-k] <package> - remove this app package from the device\n"
         "                                 ('-k' means keep the data and cache directories)\n"
-        "  adb bugreport [<zip_file>]   - return all information from the device\n"
-        "                                 that should be included in a bug report.\n"
-        "\n"
+        "  adb bugreport [<path>]       - return all information from the device that should be included in a zipped bug report.\n"
+        "                                 If <path> is a file, the bug report will be saved as that file.\n"
+        "                                 If <path> is a directory, the bug report will be saved in that directory with the name provided by the device.\n"
+        "                                 If <path> is omitted, the bug report will be saved in the current directory with the name provided by the device.\n"
+        "                                 NOTE: if the device does not support zipped bug reports, the bug report will be output on stdout.\n"
         "  adb backup [-f <file>] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all] [-system|-nosystem] [<packages...>]\n"
         "                               - write an archive of the device's data to <file>.\n"
         "                                 If no -f option is supplied then the data is written\n"
@@ -249,8 +252,8 @@
         "  ADB_TRACE                    - Print debug information. A comma separated list of the following values\n"
         "                                 1 or all, adb, sockets, packets, rwx, usb, sync, sysdeps, transport, jdwp\n"
         "  ANDROID_SERIAL               - The serial number to connect to. -s takes priority over this if given.\n"
-        "  ANDROID_LOG_TAGS             - When used with the logcat option, only these debug tags are printed.\n"
-        );
+        "  ANDROID_LOG_TAGS             - When used with the logcat option, only these debug tags are printed.\n");
+    // clang-format on
 }
 
 int usage() {
@@ -1327,7 +1330,7 @@
     if (hint.find_first_of(OS_PATH_SEPARATORS) != std::string::npos) {
         std::string cwd;
         if (!getcwd(&cwd)) {
-            fprintf(stderr, "adb: getcwd failed: %s\n", strerror(errno));
+            perror("adb: getcwd failed");
             return "";
         }
         return android::base::StringPrintf("%s%c%s", cwd.c_str(), OS_PATH_SEPARATOR, hint.c_str());
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index 75dcc86..bd19f88 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -34,6 +34,8 @@
 #include <android-base/unique_fd.h>
 #include <android-base/utf8.h>
 
+#include "sysdeps/stat.h"
+
 /*
  * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
  * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
@@ -199,8 +201,6 @@
     /* nothing really */
 }
 
-#define  lstat    stat   /* no symlinks on Win32 */
-
 #define  S_ISLNK(m)   0   /* no symlinks on Win32 */
 
 extern int  adb_unlink(const char*  path);
@@ -307,27 +307,6 @@
     return isalpha(path[0]) && path[1] == ':' && path[2] == '\\';
 }
 
-// We later define a macro mapping 'stat' to 'adb_stat'. This causes:
-//   struct stat s;
-//   stat(filename, &s);
-// To turn into the following:
-//   struct adb_stat s;
-//   adb_stat(filename, &s);
-// To get this to work, we need to make 'struct adb_stat' the same as
-// 'struct stat'. Note that this definition of 'struct adb_stat' uses the
-// *current* macro definition of stat, so it may actually be inheriting from
-// struct _stat32i64 (or some other remapping).
-struct adb_stat : public stat {};
-
-static_assert(sizeof(struct adb_stat) == sizeof(struct stat),
-    "structures should be the same");
-
-extern int adb_stat(const char* f, struct adb_stat* s);
-
-// stat is already a macro, undefine it so we can redefine it.
-#undef stat
-#define stat adb_stat
-
 // UTF-8 versions of POSIX APIs.
 extern DIR* adb_opendir(const char* dirname);
 extern struct dirent* adb_readdir(DIR* dir);
diff --git a/adb/sysdeps/stat.h b/adb/sysdeps/stat.h
new file mode 100644
index 0000000..5953595
--- /dev/null
+++ b/adb/sysdeps/stat.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#if defined(_WIN32)
+// stat is broken on Win32: stat on a path with a trailing slash or backslash will always fail with
+// ENOENT.
+int adb_stat(const char* path, struct adb_stat* buf);
+
+// We later define a macro mapping 'stat' to 'adb_stat'. This causes:
+//   struct stat s;
+//   stat(filename, &s);
+// To turn into the following:
+//   struct adb_stat s;
+//   adb_stat(filename, &s);
+// To get this to work, we need to make 'struct adb_stat' the same as
+// 'struct stat'. Note that this definition of 'struct adb_stat' uses the
+// *current* macro definition of stat, so it may actually be inheriting from
+// struct _stat32i64 (or some other remapping).
+struct adb_stat : public stat {};
+
+#undef stat
+#define stat adb_stat
+
+// Windows doesn't have lstat.
+#define lstat adb_stat
+
+#endif
diff --git a/adb/sysdeps/stat_test.cpp b/adb/sysdeps/stat_test.cpp
new file mode 100644
index 0000000..2c2e0ee
--- /dev/null
+++ b/adb/sysdeps/stat_test.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 <string>
+
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+#include "adb_utils.h"
+#include "sysdeps.h"
+
+TEST(sysdeps, stat) {
+    TemporaryDir td;
+    TemporaryFile tf;
+
+    struct stat st;
+    ASSERT_EQ(0, stat(td.path, &st));
+    ASSERT_FALSE(S_ISREG(st.st_mode));
+    ASSERT_TRUE(S_ISDIR(st.st_mode));
+
+    ASSERT_EQ(0, stat((std::string(td.path) + '/').c_str(), &st));
+    ASSERT_TRUE(S_ISDIR(st.st_mode));
+
+#if defined(_WIN32)
+    ASSERT_EQ(0, stat((std::string(td.path) + '\\').c_str(), &st));
+    ASSERT_TRUE(S_ISDIR(st.st_mode));
+#endif
+
+    std::string nonexistent_path = std::string(td.path) + "/nonexistent";
+    ASSERT_EQ(-1, stat(nonexistent_path.c_str(), &st));
+    ASSERT_EQ(ENOENT, errno);
+
+    ASSERT_EQ(-1, stat((nonexistent_path + "/").c_str(), &st));
+    ASSERT_EQ(ENOENT, errno);
+
+#if defined(_WIN32)
+    ASSERT_EQ(-1, stat((nonexistent_path + "\\").c_str(), &st));
+    ASSERT_EQ(ENOENT, errno);
+#endif
+
+    ASSERT_EQ(0, stat(tf.path, &st));
+    ASSERT_TRUE(S_ISREG(st.st_mode));
+    ASSERT_FALSE(S_ISDIR(st.st_mode));
+
+    ASSERT_EQ(-1, stat((std::string(tf.path) + '/').c_str(), &st));
+    ASSERT_EQ(ENOTDIR, errno);
+
+#if defined(_WIN32)
+    ASSERT_EQ(-1, stat((std::string(tf.path) + '\\').c_str(), &st));
+    ASSERT_EQ(ENOTDIR, errno);
+#endif
+}
diff --git a/adb/sysdeps/win32/stat.cpp b/adb/sysdeps/win32/stat.cpp
new file mode 100644
index 0000000..844c1ce
--- /dev/null
+++ b/adb/sysdeps/win32/stat.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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 "sysdeps/stat.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/utf8.h>
+
+// Version of stat() that takes a UTF-8 path.
+int adb_stat(const char* path, struct adb_stat* s) {
+// This definition of wstat seems to be missing from <sys/stat.h>.
+#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64)
+#ifdef _USE_32BIT_TIME_T
+#define wstat _wstat32i64
+#else
+#define wstat _wstat64
+#endif
+#else
+// <sys/stat.h> has a function prototype for wstat() that should be available.
+#endif
+
+    std::wstring path_wide;
+    if (!android::base::UTF8ToWide(path, &path_wide)) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    // If the path has a trailing slash, stat will fail with ENOENT regardless of whether the path
+    // is a directory or not.
+    bool expected_directory = false;
+    while (*path_wide.rbegin() == u'/' || *path_wide.rbegin() == u'\\') {
+        path_wide.pop_back();
+        expected_directory = true;
+    }
+
+    struct adb_stat st;
+    int result = wstat(path_wide.c_str(), &st);
+    if (result == 0 && expected_directory) {
+        if (!S_ISDIR(st.st_mode)) {
+            errno = ENOTDIR;
+            return -1;
+        }
+    }
+
+    memcpy(s, &st, sizeof(st));
+    return result;
+}
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index bc09fdc..e641680 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -2298,30 +2298,6 @@
     }
 }
 
-// Version of stat() that takes a UTF-8 path.
-int adb_stat(const char* path, struct adb_stat* s) {
-#pragma push_macro("wstat")
-// This definition of wstat seems to be missing from <sys/stat.h>.
-#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64)
-#ifdef _USE_32BIT_TIME_T
-#define wstat _wstat32i64
-#else
-#define wstat _wstat64
-#endif
-#else
-// <sys/stat.h> has a function prototype for wstat() that should be available.
-#endif
-
-    std::wstring path_wide;
-    if (!android::base::UTF8ToWide(path, &path_wide)) {
-        return -1;
-    }
-
-    return wstat(path_wide.c_str(), s);
-
-#pragma pop_macro("wstat")
-}
-
 // Version of opendir() that takes a UTF-8 path.
 DIR* adb_opendir(const char* path) {
     std::wstring path_wide;
diff --git a/bootstat/boot_event_record_store.cpp b/bootstat/boot_event_record_store.cpp
index ef4f68e..346eada 100644
--- a/bootstat/boot_event_record_store.cpp
+++ b/bootstat/boot_event_record_store.cpp
@@ -25,6 +25,7 @@
 #include <utility>
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include "histogram_logger.h"
 #include "uptime_parser.h"
 
@@ -57,8 +58,10 @@
 
   // Ignore existing bootstat records (which do not contain file content).
   if (!content.empty()) {
-    int32_t value = std::stoi(content);
-    bootstat::LogHistogram("bootstat_mtime_matches_content", value == *uptime);
+    int32_t value;
+    if (android::base::ParseInt(content.c_str(), &value)) {
+      bootstat::LogHistogram("bootstat_mtime_matches_content", value == *uptime);
+    }
   }
 
   return true;
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 08fc5af..26e0ffc 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -28,6 +28,7 @@
 #include <memory>
 #include <string>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <cutils/properties.h>
 #include <log/log.h>
 #include "boot_event_record_store.h"
@@ -147,7 +148,10 @@
   std::string boot_complete_prefix = "boot_complete";
 
   std::string build_date_str = GetProperty("ro.build.date.utc");
-  int32_t build_date = std::stoi(build_date_str);
+  int32_t build_date;
+  if (!android::base::ParseInt(build_date_str.c_str(), &build_date)) {
+    return std::string();
+  }
 
   BootEventRecordStore boot_event_store;
   BootEventRecordStore::BootEventRecord record;
@@ -171,6 +175,10 @@
   // ota_boot_complete.  The latter signifies that the device is booting after
   // a system update.
   std::string boot_complete_prefix = CalculateBootCompletePrefix();
+  if (boot_complete_prefix.empty()) {
+    // The system is hosed because the build date property could not be read.
+    return;
+  }
 
   // post_decrypt_time_elapsed is only logged on encrypted devices.
   if (boot_event_store.GetBootEvent("post_decrypt_time_elapsed", &record)) {
diff --git a/logcat/logcatd.rc b/logcat/logcatd.rc
index ce1a451..4d5c80c 100644
--- a/logcat/logcatd.rc
+++ b/logcat/logcatd.rc
@@ -35,7 +35,8 @@
     # all exec/services are called with umask(077), so no gain beyond 0700
     mkdir /data/misc/logd 0700 logd log
     # logd for write to /data/misc/logd, log group for read from pstore (-L)
-    exec - logd log -- /system/bin/logcat -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 1024 -n ${logd.logpersistd.size:-256}
+    # b/28788401 b/30041146 b/30612424
+    # exec - logd log -- /system/bin/logcat -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 1024 -n ${logd.logpersistd.size:-256}
     start logcatd
 
 # stop logcatd service and clear data
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index a0b7499..da0aecc 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -630,126 +630,124 @@
     const char *tag = "";
     const char *etag = tag;
     size_t taglen = len - (p - buf);
-    if (!isspace(*p) && *p) {
-        const char *bt, *et, *cp;
+    const char *bt = p;
 
-        bt = p;
-        if ((taglen >= 6) && !fast<strncmp>(p, "[INFO]", 6)) {
-            // <PRI>[<TIME>] "[INFO]"<tag> ":" message
-            bt = p + 6;
-            taglen -= 6;
-        }
-        for(et = bt; taglen && *et && (*et != ':') && !isspace(*et); ++et, --taglen) {
-           // skip ':' within [ ... ]
-           if (*et == '[') {
-               while (taglen && *et && *et != ']') {
-                   ++et;
-                   --taglen;
-               }
-            }
-        }
-        for(cp = et; taglen && isspace(*cp); ++cp, --taglen);
-        size_t size;
+    static const char infoBrace[] = "[INFO]";
+    static const size_t infoBraceLen = strlen(infoBrace);
+    if ((taglen >= infoBraceLen) && !fast<strncmp>(p, infoBrace, infoBraceLen)) {
+        // <PRI>[<TIME>] "[INFO]"<tag> ":" message
+        bt = p + infoBraceLen;
+        taglen -= infoBraceLen;
+    }
 
+    const char *et;
+    for (et = bt; taglen && *et && (*et != ':') && !isspace(*et); ++et, --taglen) {
+       // skip ':' within [ ... ]
+       if (*et == '[') {
+           while (taglen && *et && *et != ']') {
+               ++et;
+               --taglen;
+           }
+           if (!taglen) {
+               break;
+           }
+       }
+    }
+    const char *cp;
+    for (cp = et; taglen && isspace(*cp); ++cp, --taglen);
+
+    // Validate tag
+    size_t size = et - bt;
+    if (taglen && size) {
         if (*cp == ':') {
+            // ToDo: handle case insensitive colon separated logging stutter:
+            //       <tag> : <tag>: ...
+
             // One Word
             tag = bt;
             etag = et;
             p = cp + 1;
-        } else if (taglen) {
-            size = et - bt;
-            if ((taglen > size) &&   // enough space for match plus trailing :
-                    (*bt == *cp) &&  // ubber fast<strncmp> pair
-                    fast<strncmp>(bt + 1, cp + 1, size - 1)) {
-                // <PRI>[<TIME>] <tag>_host '<tag>.<num>' : message
-                if (!fast<strncmp>(bt + size - 5, "_host", 5)
-                        && !fast<strncmp>(bt + 1, cp + 1, size - 6)) {
+        } else if ((taglen > size) && (tolower(*bt) == tolower(*cp))) {
+            // clean up any tag stutter
+            if (!fast<strncasecmp>(bt + 1, cp + 1, size - 1)) { // no match
+                // <PRI>[<TIME>] <tag> <tag> : message
+                // <PRI>[<TIME>] <tag> <tag>: message
+                // <PRI>[<TIME>] <tag> '<tag>.<num>' : message
+                // <PRI>[<TIME>] <tag> '<tag><num>' : message
+                // <PRI>[<TIME>] <tag> '<tag><stuff>' : message
+                const char *b = cp;
+                cp += size;
+                taglen -= size;
+                while (--taglen && !isspace(*++cp) && (*cp != ':'));
+                const char *e;
+                for (e = cp; taglen && isspace(*cp); ++cp, --taglen);
+                if (taglen && (*cp == ':')) {
+                    tag = b;
+                    etag = e;
+                    p = cp + 1;
+                }
+            } else {
+                // what about <PRI>[<TIME>] <tag>_host '<tag><stuff>' : message
+                static const char host[] = "_host";
+                static const size_t hostlen = strlen(host);
+                if ((size > hostlen) &&
+                        !fast<strncmp>(bt + size - hostlen, host, hostlen) &&
+                        !fast<strncmp>(bt + 1, cp + 1, size - hostlen - 1)) {
                     const char *b = cp;
-                    cp += size - 5;
-                    taglen -= size - 5;
+                    cp += size - hostlen;
+                    taglen -= size - hostlen;
                     if (*cp == '.') {
                         while (--taglen && !isspace(*++cp) && (*cp != ':'));
                         const char *e;
-                        for(e = cp; taglen && isspace(*cp); ++cp, --taglen);
-                        if (*cp == ':') {
+                        for (e = cp; taglen && isspace(*cp); ++cp, --taglen);
+                        if (taglen && (*cp == ':')) {
                             tag = b;
                             etag = e;
                             p = cp + 1;
                         }
                     }
                 } else {
-                    while (--taglen && !isspace(*++cp) && (*cp != ':'));
-                    const char *e;
-                    for(e = cp; taglen && isspace(*cp); ++cp, --taglen);
-                    // Two words
-                    if (*cp == ':') {
-                        tag = bt;
-                        etag = e;
-                        p = cp + 1;
-                    }
-                }
-            } else if (isspace(cp[size])) {
-                cp += size;
-                taglen -= size;
-                while (--taglen && isspace(*++cp));
-                // <PRI>[<TIME>] <tag> <tag> : message
-                if (*cp == ':') {
-                    tag = bt;
-                    etag = et;
-                    p = cp + 1;
-                }
-            } else if (cp[size] == ':') {
-                // <PRI>[<TIME>] <tag> <tag> : message
-                tag = bt;
-                etag = et;
-                p = cp + size + 1;
-            } else if ((cp[size] == '.') || isdigit(cp[size])) {
-                // <PRI>[<TIME>] <tag> '<tag>.<num>' : message
-                // <PRI>[<TIME>] <tag> '<tag><num>' : message
-                const char *b = cp;
-                cp += size;
-                taglen -= size;
-                while (--taglen && !isspace(*++cp) && (*cp != ':'));
-                const char *e = cp;
-                while (taglen && isspace(*cp)) {
-                    ++cp;
-                    --taglen;
-                }
-                if (*cp == ':') {
-                    tag = b;
-                    etag = e;
-                    p = cp + 1;
-                }
-            } else {
-                while (--taglen && !isspace(*++cp) && (*cp != ':'));
-                const char *e = cp;
-                while (taglen && isspace(*cp)) {
-                    ++cp;
-                    --taglen;
-                }
-                // Two words
-                if (*cp == ':') {
-                    tag = bt;
-                    etag = e;
-                    p = cp + 1;
+                    goto twoWord;
                 }
             }
-        } /* else no tag */
-        size = etag - tag;
-        if ((size <= 1)
-            // register names like x9
-                || ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1])))
-            // register names like x18 but not driver names like en0
-                || ((size == 3) && (isdigit(tag[1]) && isdigit(tag[2])))
-            // blacklist
-                || ((size == 3) && !fast<strncmp>(tag, "CPU", 3))
-                || ((size == 7) && !fast<strncasecmp>(tag, "WARNING", 7))
-                || ((size == 5) && !fast<strncasecmp>(tag, "ERROR", 5))
-                || ((size == 4) && !fast<strncasecmp>(tag, "INFO", 4))) {
-            p = start;
-            etag = tag = "";
+        } else {
+            // <PRI>[<TIME>] <tag> <stuff>' : message
+twoWord:    while (--taglen && !isspace(*++cp) && (*cp != ':'));
+            const char *e;
+            for (e = cp; taglen && isspace(*cp); ++cp, --taglen);
+            // Two words
+            if (taglen && (*cp == ':')) {
+                tag = bt;
+                etag = e;
+                p = cp + 1;
+            }
         }
+    } // else no tag
+
+    static const char cpu[] = "CPU";
+    static const size_t cpuLen = strlen(cpu);
+    static const char warning[] = "WARNING";
+    static const size_t warningLen = strlen(warning);
+    static const char error[] = "ERROR";
+    static const size_t errorLen = strlen(error);
+    static const char info[] = "INFO";
+    static const size_t infoLen = strlen(info);
+
+    size = etag - tag;
+    if ((size <= 1)
+        // register names like x9
+            || ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1])))
+        // register names like x18 but not driver names like en0
+            || ((size == 3) && (isdigit(tag[1]) && isdigit(tag[2])))
+        // blacklist
+            || ((size == cpuLen) && !fast<strncmp>(tag, cpu, cpuLen))
+            || ((size == warningLen) && !fast<strncasecmp>(tag, warning, warningLen))
+            || ((size == errorLen) && !fast<strncasecmp>(tag, error, errorLen))
+            || ((size == infoLen) && !fast<strncasecmp>(tag, info, infoLen))) {
+        p = start;
+        etag = tag = "";
     }
+
     // Suppress additional stutter in tag:
     //   eg: [143:healthd]healthd -> [143:healthd]
     taglen = etag - tag;