Merge "logcat: Minor fixes"
diff --git a/logd/Android.mk b/logd/Android.mk
index 127a66b..e65e9ff 100644
--- a/logd/Android.mk
+++ b/logd/Android.mk
@@ -32,8 +32,9 @@
 #        "s/^\([0-9]*\)[ \t]*$1[ \t].*/-D`echo $1 | tr a-z A-Z`_LOG_TAG=\1/p" \
 #        $(LOCAL_PATH)/$2/event.logtags)
 #  event_flag := $(call event_logtags,auditd)
+#  event_flag += $(call event_logtags,logd)
 # so make sure we do not regret hard-coding it as follows:
-event_flag := -DAUDITD_LOG_TAG=1003
+event_flag := -DAUDITD_LOG_TAG=1003 -DLOGD_LOG_TAG=1004
 
 LOCAL_CFLAGS := -Werror $(event_flag)
 
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index a5844a3..260e237 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -279,7 +279,7 @@
         size_t worst_sizes = 0;
         size_t second_worst_sizes = 0;
 
-        if ((id != LOG_ID_CRASH) && mPrune.worstUidEnabled()) {
+        if (worstUidEnabledForLogid(id) && mPrune.worstUidEnabled()) {
             std::unique_ptr<const UidEntry *[]> sorted = stats.sort(2, id);
 
             if (sorted.get()) {
@@ -297,6 +297,8 @@
         }
 
         bool kick = false;
+        bool leading = true;
+        LogBufferElement *last = NULL;
         for(it = mLogElements.begin(); it != mLogElements.end();) {
             LogBufferElement *e = *it;
 
@@ -309,26 +311,87 @@
                 continue;
             }
 
-            uid_t uid = e->getUid();
+            unsigned short dropped = e->getDropped();
 
-            // !Worst and !BlackListed?
-            if ((uid != worst) && (!hasBlacklist || !mPrune.naughty(e))) {
+            // remove any leading drops
+            if (leading && dropped) {
+                it = erase(it);
+                continue;
+            }
+
+            pid_t pid = e->getPid();
+
+            // merge any drops
+            if (last && dropped
+             && ((dropped + last->getDropped()) < USHRT_MAX)
+             && (last->getPid() == pid)
+             && (last->getTid() == e->getTid())) {
+                it = mLogElements.erase(it);
+                stats.erase(e);
+                delete e;
+                last->setDropped(dropped + last->getDropped());
+                continue;
+            }
+
+            leading = false;
+
+            if (hasBlacklist && mPrune.naughty(e)) {
+                last = NULL;
+                it = erase(it);
+                if (dropped) {
+                    continue;
+                }
+
+                pruneRows--;
+                if (pruneRows == 0) {
+                    break;
+                }
+
+                if (e->getUid() == worst) {
+                    kick = true;
+                    if (worst_sizes < second_worst_sizes) {
+                        break;
+                    }
+                    worst_sizes -= e->getMsgLen();
+                }
+                continue;
+            }
+
+            if (dropped) {
+                last = e;
                 ++it;
                 continue;
             }
 
-            unsigned short len = e->getMsgLen();
-            it = erase(it);
+            if (e->getUid() != worst) {
+                last = NULL;
+                ++it;
+                continue;
+            }
+
             pruneRows--;
             if (pruneRows == 0) {
                 break;
             }
 
-            if (uid != worst) {
-                continue;
-            }
-
             kick = true;
+
+            unsigned short len = e->getMsgLen();
+            stats.drop(e);
+            e->setDropped(1);
+            // merge any drops
+            if (last
+             && (last->getDropped() < (USHRT_MAX - 1))
+             && (last->getPid() == pid)
+             && (last->getTid() == e->getTid())) {
+                it = mLogElements.erase(it);
+                stats.erase(e);
+                delete e;
+                last->setDropped(last->getDropped() + 1);
+            } else {
+                last = e;
+                ++it;
+            }
             if (worst_sizes < second_worst_sizes) {
                 break;
             }
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index 5e780b5..a173e63 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -14,14 +14,17 @@
  * limitations under the License.
  */
 
+#include <endian.h>
 #include <stdio.h>
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
 
 #include <log/logger.h>
+#include <private/android_logger.h>
 
 #include "LogBufferElement.h"
+#include "LogCommand.h"
 #include "LogReader.h"
 
 const uint64_t LogBufferElement::FLUSH_ERROR(0);
@@ -45,11 +48,59 @@
     delete [] mMsg;
 }
 
+// assumption: mMsg == NULL
+size_t LogBufferElement::populateDroppedMessage(char *&buffer, bool privileged) {
+    static const char format_uid[] = "uid=%u dropped=%u";
+    static const size_t unprivileged_offset = 7;
+    static const char tag[] = "logd";
+
+    size_t len;
+    if (privileged) {
+        len = snprintf(NULL, 0, format_uid, mUid, mDropped);
+    } else {
+        len = snprintf(NULL, 0, format_uid + unprivileged_offset, mDropped);
+    }
+
+    size_t hdrLen;
+    if (mLogId == LOG_ID_EVENTS) {
+        hdrLen = sizeof(android_log_event_string_t);
+    } else {
+        hdrLen = 1 + sizeof(tag);
+    }
+
+    buffer = static_cast<char *>(calloc(1, hdrLen + len + 1));
+    if (!buffer) {
+        return 0;
+    }
+
+    size_t retval = hdrLen + len;
+    if (mLogId == LOG_ID_EVENTS) {
+        android_log_event_string_t *e = reinterpret_cast<android_log_event_string_t *>(buffer);
+
+        e->header.tag = htole32(LOGD_LOG_TAG);
+        e->type = EVENT_TYPE_STRING;
+        e->length = htole32(len);
+    } else {
+        ++retval;
+        buffer[0] = ANDROID_LOG_INFO;
+        strcpy(buffer + 1, tag);
+    }
+
+    if (privileged) {
+        snprintf(buffer + hdrLen, len + 1, format_uid, mUid, mDropped);
+    } else {
+        snprintf(buffer + hdrLen, len + 1, format_uid + unprivileged_offset, mDropped);
+    }
+
+    return retval;
+}
+
 uint64_t LogBufferElement::flushTo(SocketClient *reader) {
     struct logger_entry_v3 entry;
+
     memset(&entry, 0, sizeof(struct logger_entry_v3));
+
     entry.hdr_size = sizeof(struct logger_entry_v3);
-    entry.len = mMsgLen;
     entry.lid = mLogId;
     entry.pid = mPid;
     entry.tid = mTid;
@@ -59,11 +110,26 @@
     struct iovec iovec[2];
     iovec[0].iov_base = &entry;
     iovec[0].iov_len = sizeof(struct logger_entry_v3);
-    iovec[1].iov_base = mMsg;
-    iovec[1].iov_len = mMsgLen;
-    if (reader->sendDatav(iovec, 2)) {
-        return FLUSH_ERROR;
+
+    char *buffer = NULL;
+
+    if (!mMsg) {
+        entry.len = populateDroppedMessage(buffer, clientHasLogCredentials(reader));
+        if (!entry.len) {
+            return mSequence;
+        }
+        iovec[1].iov_base = buffer;
+    } else {
+        entry.len = mMsgLen;
+        iovec[1].iov_base = mMsg;
+    }
+    iovec[1].iov_len = entry.len;
+
+    uint64_t retval = reader->sendDatav(iovec, 2) ? FLUSH_ERROR : mSequence;
+
+    if (buffer) {
+        free(buffer);
     }
 
-    return mSequence;
+    return retval;
 }
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index 93671da..7b6456d 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -18,6 +18,7 @@
 #define _LOGD_LOG_BUFFER_ELEMENT_H__
 
 #include <stdatomic.h>
+#include <stdlib.h>
 #include <sys/types.h>
 
 #include <sysutils/SocketClient.h>
@@ -32,17 +33,27 @@
 
 }
 
+static inline bool worstUidEnabledForLogid(log_id_t id) {
+    return (id != LOG_ID_CRASH) && (id != LOG_ID_EVENTS);
+}
+
 class LogBufferElement {
     const log_id_t mLogId;
     const uid_t mUid;
     const pid_t mPid;
     const pid_t mTid;
     char *mMsg;
-    const unsigned short mMsgLen;
+    union {
+        const unsigned short mMsgLen; // mMSg != NULL
+        unsigned short mDropped;      // mMsg == NULL
+    };
     const uint64_t mSequence;
     const log_time mRealTime;
     static atomic_int_fast64_t sequence;
 
+    // assumption: mMsg == NULL
+    size_t populateDroppedMessage(char *&buffer, bool privileged);
+
 public:
     LogBufferElement(log_id_t log_id, log_time realtime,
                      uid_t uid, pid_t pid, pid_t tid,
@@ -53,7 +64,15 @@
     uid_t getUid(void) const { return mUid; }
     pid_t getPid(void) const { return mPid; }
     pid_t getTid(void) const { return mTid; }
-    unsigned short getMsgLen() const { return mMsgLen; }
+    unsigned short getDropped(void) const { return mMsg ? 0 : mDropped; }
+    unsigned short setDropped(unsigned short value) {
+        if (mMsg) {
+            free(mMsg);
+            mMsg = NULL;
+        }
+        return mDropped = value;
+    }
+    unsigned short getMsgLen() const { return mMsg ? mMsgLen : 0; }
     uint64_t getSequence(void) const { return mSequence; }
     static uint64_t getCurrentSequence(void) { return sequence.load(memory_order_relaxed); }
     log_time getRealTime(void) const { return mRealTime; }
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index 3fbcfd6..40fbfae 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -71,16 +71,19 @@
     ++mElements[log_id];
 
     uid_t uid = e->getUid();
+    unsigned short dropped = e->getDropped();
     android::hash_t hash = android::hash_type(uid);
     uidTable_t &table = uidTable[log_id];
     ssize_t index = table.find(-1, hash, uid);
     if (index == -1) {
         UidEntry initEntry(uid);
         initEntry.add(size);
+        initEntry.add_dropped(dropped);
         table.add(hash, initEntry);
     } else {
         UidEntry &entry = table.editEntryAt(index);
         entry.add(size);
+        entry.add_dropped(dropped);
     }
 
     mSizesTotal[log_id] += size;
@@ -96,6 +99,7 @@
     if (index == -1) {
         PidEntry initEntry(pid, uid, android::pidToName(pid));
         initEntry.add(size);
+        initEntry.add_dropped(dropped);
         pidTable.add(hash, initEntry);
     } else {
         PidEntry &entry = pidTable.editEntryAt(index);
@@ -109,6 +113,7 @@
             }
         }
         entry.add(size);
+        entry.add_dropped(dropped);
     }
 }
 
@@ -119,12 +124,13 @@
     --mElements[log_id];
 
     uid_t uid = e->getUid();
+    unsigned short dropped = e->getDropped();
     android::hash_t hash = android::hash_type(uid);
     uidTable_t &table = uidTable[log_id];
     ssize_t index = table.find(-1, hash, uid);
     if (index != -1) {
         UidEntry &entry = table.editEntryAt(index);
-        if (entry.subtract(size)) {
+        if (entry.subtract(size) || entry.subtract_dropped(dropped)) {
             table.removeAt(index);
         }
     }
@@ -138,12 +144,43 @@
     index = pidTable.find(-1, hash, pid);
     if (index != -1) {
         PidEntry &entry = pidTable.editEntryAt(index);
-        if (entry.subtract(size)) {
+        if (entry.subtract(size) || entry.subtract_dropped(dropped)) {
             pidTable.removeAt(index);
         }
     }
 }
 
+// Atomically set an entry to drop
+// entry->setDropped(1) must follow this call, caller should do this explicitly.
+void LogStatistics::drop(LogBufferElement *e) {
+    log_id_t log_id = e->getLogId();
+    unsigned short size = e->getMsgLen();
+    mSizes[log_id] -= size;
+
+    uid_t uid = e->getUid();
+    android::hash_t hash = android::hash_type(uid);
+    typeof uidTable[0] &table = uidTable[log_id];
+    ssize_t index = table.find(-1, hash, uid);
+    if (index != -1) {
+        UidEntry &entry = table.editEntryAt(index);
+        entry.subtract(size);
+        entry.add_dropped(1);
+    }
+
+    if (!enable) {
+        return;
+    }
+
+    pid_t pid = e->getPid();
+    hash = android::hash_type(pid);
+    index = pidTable.find(-1, hash, pid);
+    if (index != -1) {
+        PidEntry &entry = pidTable.editEntryAt(index);
+        entry.subtract(size);
+        entry.add_dropped(1);
+    }
+}
+
 // caller must own and free character string
 char *LogStatistics::uidToName(uid_t uid) {
     // Local hard coded favourites
@@ -191,12 +228,22 @@
 }
 
 static void format_line(android::String8 &output,
-        android::String8 &name, android::String8 &size) {
-    static const size_t total_len = 70;
+        android::String8 &name, android::String8 &size, android::String8 &pruned) {
+    static const size_t pruned_len = 6;
+    static const size_t total_len = 70 + pruned_len;
 
-    output.appendFormat("%s%*s\n", name.string(),
-        (int)std::max(total_len - name.length() - 1, size.length() + 1),
-        size.string());
+    ssize_t drop_len = std::max(pruned.length() + 1, pruned_len);
+    ssize_t size_len = std::max(size.length() + 1,
+                                total_len - name.length() - drop_len - 1);
+
+    if (pruned.length()) {
+        output.appendFormat("%s%*s%*s\n", name.string(),
+                                          (int)size_len, size.string(),
+                                          (int)drop_len, pruned.string());
+    } else {
+        output.appendFormat("%s%*s\n", name.string(),
+                                       (int)size_len, size.string());
+    }
 }
 
 void LogStatistics::format(char **buf, uid_t uid, unsigned int logMask) {
@@ -285,14 +332,18 @@
                     output.appendFormat(
                         "\n\nChattiest UIDs in %s:\n",
                         android_log_id_to_name(id));
-                    android::String8 name("UID");
-                    android::String8 size("Size");
-                    format_line(output, name, size);
                 } else {
                     output.appendFormat(
                         "\n\nLogging for your UID in %s:\n",
                         android_log_id_to_name(id));
                 }
+                android::String8 name("UID");
+                android::String8 size("Size");
+                android::String8 pruned("Pruned");
+                if (!worstUidEnabledForLogid(id)) {
+                    pruned.setTo("");
+                }
+                format_line(output, name, size, pruned);
                 headerPrinted = true;
             }
 
@@ -307,7 +358,13 @@
             android::String8 size("");
             size.appendFormat("%zu", entry->getSizes());
 
-            format_line(output, name, size);
+            android::String8 pruned("");
+            size_t dropped = entry->getDropped();
+            if (dropped) {
+                pruned.appendFormat("%zu", dropped);
+            }
+
+            format_line(output, name, size, pruned);
         }
     }
 
@@ -330,7 +387,8 @@
                 }
                 android::String8 name("  PID/UID");
                 android::String8 size("Size");
-                format_line(output, name, size);
+                android::String8 pruned("Pruned");
+                format_line(output, name, size, pruned);
                 headerPrinted = true;
             }
 
@@ -350,7 +408,13 @@
             android::String8 size("");
             size.appendFormat("%zu", entry->getSizes());
 
-            format_line(output, name, size);
+            android::String8 pruned("");
+            size_t dropped = entry->getDropped();
+            if (dropped) {
+                pruned.appendFormat("%zu", dropped);
+            }
+
+            format_line(output, name, size, pruned);
         }
     }
 
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
index a65ffe0..f3110d7 100644
--- a/logd/LogStatistics.h
+++ b/logd/LogStatistics.h
@@ -78,13 +78,18 @@
 struct UidEntry {
     const uid_t uid;
     size_t size;
+    size_t dropped;
 
-    UidEntry(uid_t uid):uid(uid),size(0) { }
+    UidEntry(uid_t uid):uid(uid),size(0),dropped(0) { }
 
     inline const uid_t&getKey() const { return uid; }
     size_t getSizes() const { return size; }
+    size_t getDropped() const { return dropped; }
+
     inline void add(size_t s) { size += s; }
-    inline bool subtract(size_t s) { size -= s; return !size; }
+    inline void add_dropped(size_t d) { dropped += d; }
+    inline bool subtract(size_t s) { size -= s; return !dropped && !size; }
+    inline bool subtract_dropped(size_t d) { dropped -= d; return !dropped && !size; }
 };
 
 struct PidEntry {
@@ -92,13 +97,15 @@
     uid_t uid;
     char *name;
     size_t size;
+    size_t dropped;
 
-    PidEntry(pid_t p, uid_t u, char *n):pid(p),uid(u),name(n),size(0) { }
+    PidEntry(pid_t p, uid_t u, char *n):pid(p),uid(u),name(n),size(0),dropped(0) { }
     PidEntry(const PidEntry &c):
         pid(c.pid),
         uid(c.uid),
         name(c.name ? strdup(c.name) : NULL),
-        size(c.size) { }
+        size(c.size),
+        dropped(c.dropped) { }
     ~PidEntry() { free(name); }
 
     const pid_t&getKey() const { return pid; }
@@ -107,8 +114,11 @@
     const char*getName() const { return name; }
     char *setName(char *n) { free(name); return name = n; }
     size_t getSizes() const { return size; }
+    size_t getDropped() const { return dropped; }
     inline void add(size_t s) { size += s; }
-    inline bool subtract(size_t s) { size -= s; return !size; }
+    inline void add_dropped(size_t d) { dropped += d; }
+    inline bool subtract(size_t s) { size -= s; return !dropped && !size; }
+    inline bool subtract_dropped(size_t d) { dropped -= d; return !dropped && !size; }
 };
 
 // Log Statistics
@@ -134,6 +144,10 @@
 
     void add(LogBufferElement *entry);
     void subtract(LogBufferElement *entry);
+    // entry->setDropped(1) must follow this call
+    void drop(LogBufferElement *entry);
+    // Correct for merging two entries referencing dropped content
+    void erase(LogBufferElement *e) { --mElements[e->getLogId()]; }
 
     std::unique_ptr<const UidEntry *[]> sort(size_t n, log_id i) { return uidTable[i].sort(n); }
 
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
index 6910854..bee940d 100644
--- a/logd/LogWhiteBlackList.cpp
+++ b/logd/LogWhiteBlackList.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <ctype.h>
-#include <malloc.h>
 
 #include <utils/String8.h>
 
@@ -53,7 +52,7 @@
 }
 
 PruneList::PruneList()
-        : mWorstUidEnabled(false) {
+        : mWorstUidEnabled(true) {
     mNaughty.clear();
     mNice.clear();
 }
@@ -71,7 +70,7 @@
 }
 
 int PruneList::init(char *str) {
-    mWorstUidEnabled = false;
+    mWorstUidEnabled = true;
     PruneCollection::iterator it;
     for (it = mNice.begin(); it != mNice.end();) {
         delete (*it);
diff --git a/logd/event.logtags b/logd/event.logtags
index a63f034..db8c19b 100644
--- a/logd/event.logtags
+++ b/logd/event.logtags
@@ -34,3 +34,4 @@
 # TODO: generate ".java" and ".h" files with integer constants from this file.
 
 1003  auditd (avc|3)
+1004  logd (dropped|3)
diff --git a/rootdir/init.rc b/rootdir/init.rc
index e6f4f4a..a2b8f59 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -321,6 +321,9 @@
     # Set SELinux security contexts on upgrade or policy update.
     restorecon_recursive /data
 
+    # Check any timezone data in /data is newer than the copy in /system, delete if not.
+    exec u:r:tzdatacheck:s0 system system -- /system/bin/tzdatacheck /system/usr/share/zoneinfo /data/misc/zoneinfo
+
     # If there is no fs-post-data action in the init.<device>.rc file, you
     # must uncomment this line, otherwise encrypted filesystems
     # won't work.
diff --git a/tzdatacheck/Android.mk b/tzdatacheck/Android.mk
new file mode 100644
index 0000000..0e25f7d
--- /dev/null
+++ b/tzdatacheck/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# ========================================================
+# Executable
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES:= tzdatacheck.cpp
+LOCAL_MODULE := tzdatacheck
+LOCAL_SHARED_LIBRARIES := libbase libcutils liblog
+LOCAL_CFLAGS := -Werror
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES:= tzdatacheck.cpp
+LOCAL_MODULE := tzdatacheck
+LOCAL_SHARED_LIBRARIES := libbase libcutils liblog
+LOCAL_CFLAGS := -Werror
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/tzdatacheck/tzdatacheck.cpp b/tzdatacheck/tzdatacheck.cpp
new file mode 100644
index 0000000..31f7b55
--- /dev/null
+++ b/tzdatacheck/tzdatacheck.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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 <errno.h>
+#include <ftw.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+
+static const char* TZDATA_FILENAME = "/tzdata";
+// tzdata file header (as much as we need for the version):
+// byte[11] tzdata_version  -- e.g. "tzdata2012f"
+static const int TZ_HEADER_LENGTH = 11;
+
+static void usage() {
+    std::cerr << "Usage: tzdatacheck SYSTEM_TZ_DIR DATA_TZ_DIR\n"
+            "\n"
+            "Compares the headers of two tzdata files. If the one in SYSTEM_TZ_DIR is the\n"
+            "same or a higher version than the one in DATA_TZ_DIR the DATA_TZ_DIR is renamed\n"
+            "and then deleted.\n";
+    exit(1);
+}
+
+/*
+ * Opens a file and fills headerBytes with the first byteCount bytes from the file. It is a fatal
+ * error if the file is too small or cannot be opened. If the file does not exist false is returned.
+ * If the bytes were read successfully then true is returned.
+ */
+static bool readHeader(const std::string& tzDataFileName, char* headerBytes, size_t byteCount) {
+    FILE* tzDataFile = fopen(tzDataFileName.c_str(), "r");
+    if (tzDataFile == nullptr) {
+        if (errno == ENOENT) {
+            return false;
+        } else {
+            PLOG(FATAL) << "Error opening tzdata file " << tzDataFileName;
+        }
+    }
+    size_t bytesRead = fread(headerBytes, 1, byteCount, tzDataFile);
+    if (bytesRead != byteCount) {
+        LOG(FATAL) << tzDataFileName << " is too small. " << byteCount << " bytes required";
+    }
+    fclose(tzDataFile);
+    return true;
+}
+
+/* Checks the contents of headerBytes. It is a fatal error if it not a tzdata header. */
+static void checkValidHeader(const std::string& fileName, char* headerBytes) {
+    if (strncmp("tzdata", headerBytes, 6) != 0) {
+        LOG(FATAL) << fileName << " does not start with the expected bytes (tzdata)";
+    }
+}
+
+/* Return the parent directory of dirName. */
+static std::string getParentDir(const std::string& dirName) {
+    std::unique_ptr<char> mutable_dirname(strdup(dirName.c_str()));
+    return dirname(mutable_dirname.get());
+}
+
+/* Deletes a single file, symlink or directory. Called from nftw(). */
+static int deleteFn(const char* fpath, const struct stat*, int typeflag, struct FTW*) {
+    LOG(DEBUG) << "Inspecting " << fpath;
+    switch (typeflag) {
+    case FTW_F:
+    case FTW_SL:
+        LOG(DEBUG) << "Unlinking " << fpath;
+        if (unlink(fpath)) {
+            PLOG(WARNING) << "Failed to unlink file/symlink " << fpath;
+        }
+        break;
+    case FTW_D:
+    case FTW_DP:
+        LOG(DEBUG) << "Removing dir " << fpath;
+        if (rmdir(fpath)) {
+            PLOG(WARNING) << "Failed to remove dir " << fpath;
+        }
+        break;
+    default:
+        LOG(WARNING) << "Unsupported file type " << fpath << ": " << typeflag;
+        break;
+    }
+    return 0;
+}
+
+/*
+ * Deletes dirToDelete and returns true if it is successful in removing or moving the directory out
+ * of the way. If dirToDelete does not exist this function does nothing and returns true.
+ *
+ * During deletion, this function first renames the directory to a temporary name. If the temporary
+ * directory cannot be created, or the directory cannot be renamed, false is returned. After the
+ * rename, deletion of files and subdirs beneath the directory is performed on a "best effort"
+ * basis. Symlinks beneath the directory are not followed.
+ */
+static bool deleteDir(const std::string& dirToDelete) {
+    // Check whether the dir exists.
+    struct stat buf;
+    if (stat(dirToDelete.c_str(), &buf) == 0) {
+      if (!S_ISDIR(buf.st_mode)) {
+        LOG(WARNING) << dirToDelete << " is not a directory";
+        return false;
+      }
+    } else {
+      if (errno == ENOENT) {
+          PLOG(INFO) << "Directory does not exist: " << dirToDelete;
+          return true;
+      } else {
+          PLOG(WARNING) << "Unable to stat " << dirToDelete;
+          return false;
+      }
+    }
+
+    // First, rename dirToDelete.
+    std::string tempDirNameTemplate = getParentDir(dirToDelete);
+    tempDirNameTemplate += "/tempXXXXXX";
+
+    // Create an empty directory with the temporary name. For this we need a non-const char*.
+    std::vector<char> tempDirName(tempDirNameTemplate.length() + 1);
+    strcpy(&tempDirName[0], tempDirNameTemplate.c_str());
+    if (mkdtemp(&tempDirName[0]) == nullptr) {
+        PLOG(WARNING) << "Unable to create a temporary directory: " << tempDirNameTemplate;
+        return false;
+    }
+
+    // Rename dirToDelete to tempDirName.
+    int rc = rename(dirToDelete.c_str(), &tempDirName[0]);
+    if (rc == -1) {
+        PLOG(WARNING) << "Unable to rename directory from " << dirToDelete << " to "
+                << &tempDirName[0];
+        return false;
+    }
+
+    // Recursively delete contents of tempDirName.
+    rc = nftw(&tempDirName[0], deleteFn, 10 /* openFiles */,
+            FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
+    if (rc == -1) {
+        LOG(INFO) << "Could not delete directory: " << &tempDirName[0];
+    }
+    return true;
+}
+
+/*
+ * After a platform update it is likely that timezone data found on the system partition will be
+ * newer than the version found in the data partition. This tool detects this case and removes the
+ * version in /data along with any update metadata.
+ *
+ * Note: This code is related to code in com.android.server.updates.TzDataInstallReceiver. The
+ * paths for the metadata and current timezone data must match.
+ *
+ * Typically on device the two args will be:
+ *   /system/usr/share/zoneinfo /data/misc/zoneinfo
+ *
+ * See usage() for usage notes.
+ */
+int main(int argc, char* argv[]) {
+    if (argc != 3) {
+        usage();
+    }
+
+    const char* systemZoneInfoDir = argv[1];
+    const char* dataZoneInfoDir = argv[2];
+
+    std::string dataCurrentDirName(dataZoneInfoDir);
+    dataCurrentDirName += "/current";
+    std::string dataTzDataFileName(dataCurrentDirName);
+    dataTzDataFileName += TZDATA_FILENAME;
+
+    std::vector<char> dataTzDataHeader;
+    dataTzDataHeader.reserve(TZ_HEADER_LENGTH);
+
+    bool dataFileExists = readHeader(dataTzDataFileName, dataTzDataHeader.data(), TZ_HEADER_LENGTH);
+    if (!dataFileExists) {
+        LOG(INFO) << "tzdata file " << dataTzDataFileName << " does not exist. No action required.";
+        return 0;
+    }
+    checkValidHeader(dataTzDataFileName, dataTzDataHeader.data());
+
+    std::string systemTzDataFileName(systemZoneInfoDir);
+    systemTzDataFileName += TZDATA_FILENAME;
+    std::vector<char> systemTzDataHeader;
+    systemTzDataHeader.reserve(TZ_HEADER_LENGTH);
+    bool systemFileExists =
+            readHeader(systemTzDataFileName, systemTzDataHeader.data(), TZ_HEADER_LENGTH);
+    if (!systemFileExists) {
+        LOG(FATAL) << systemTzDataFileName << " does not exist or could not be opened";
+    }
+    checkValidHeader(systemTzDataFileName, systemTzDataHeader.data());
+
+    if (strncmp(&systemTzDataHeader[0], &dataTzDataHeader[0], TZ_HEADER_LENGTH) < 0) {
+        LOG(INFO) << "tzdata file " << dataTzDataFileName << " is the newer than "
+                << systemTzDataFileName << ". No action required.";
+    } else {
+        // We have detected the case this tool is intended to prevent. Go fix it.
+        LOG(INFO) << "tzdata file " << dataTzDataFileName << " is the same as or older than "
+                << systemTzDataFileName << "; fixing...";
+
+        // Delete the update metadata
+        std::string dataUpdatesDirName(dataZoneInfoDir);
+        dataUpdatesDirName += "/updates";
+        LOG(INFO) << "Removing: " << dataUpdatesDirName;
+        bool deleted = deleteDir(dataUpdatesDirName);
+        if (!deleted) {
+            LOG(WARNING) << "Deletion of install metadata " << dataUpdatesDirName
+                    << " was not successful";
+        }
+
+        // Delete the TZ data
+        LOG(INFO) << "Removing: " << dataCurrentDirName;
+        deleted = deleteDir(dataCurrentDirName);
+        if (!deleted) {
+            LOG(WARNING) << "Deletion of tzdata " << dataCurrentDirName << " was not successful";
+        }
+    }
+
+    return 0;
+}