Merge "Don't use TEMP_FAILURE_RETRY on close in system/core."
diff --git a/adb/Android.mk b/adb/Android.mk
index 4ffb589..f030041 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -14,8 +14,9 @@
 adb_version := $(shell git -C $(LOCAL_PATH) rev-parse --short=12 HEAD 2>/dev/null)-android
 
 ADB_COMMON_CFLAGS := \
-    -Wall -Werror \
+    -Wall -Wextra -Werror \
     -Wno-unused-parameter \
+    -Wno-missing-field-initializers \
     -DADB_REVISION='"$(adb_version)"' \
 
 # libadb
@@ -45,7 +46,6 @@
 
 LIBADB_CFLAGS := \
     $(ADB_COMMON_CFLAGS) \
-    -Wno-missing-field-initializers \
     -fvisibility=hidden \
 
 LIBADB_darwin_SRC_FILES := \
@@ -77,6 +77,10 @@
 
 LOCAL_SHARED_LIBRARIES := libbase
 
+# Even though we're building a static library (and thus there's no link step for
+# this to take effect), this adds the includes to our path.
+LOCAL_STATIC_LIBRARIES := libbase
+
 include $(BUILD_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
@@ -91,14 +95,11 @@
 LOCAL_SHARED_LIBRARIES := libbase
 
 # Even though we're building a static library (and thus there's no link step for
-# this to take effect), this adds the SSL includes to our path.
-LOCAL_STATIC_LIBRARIES := libcrypto_static
+# this to take effect), this adds the includes to our path.
+LOCAL_STATIC_LIBRARIES := libcrypto_static libbase
 
 ifeq ($(HOST_OS),windows)
     LOCAL_C_INCLUDES += development/host/windows/usb/api/
-    # Windows.h defines an awful ERROR macro that collides with base/logging.h.
-    # Suppress it with NOGDI.
-    LOCAL_CFLAGS += -DNOGDI
 endif
 
 include $(BUILD_HOST_STATIC_LIBRARY)
@@ -165,9 +166,6 @@
 endif
 
 ifeq ($(HOST_OS),windows)
-    # Windows.h defines an awful ERROR macro that collides with base/logging.h.
-    # Suppress it with NOGDI.
-    LOCAL_CFLAGS += -DNOGDI
     LOCAL_LDLIBS += -lws2_32 -lgdi32
     EXTRA_STATIC_LIBS := AdbWinApi
 endif
@@ -260,10 +258,10 @@
     libbase \
     libfs_mgr \
     liblog \
-    libcutils \
-    libc \
     libmincrypt \
     libselinux \
     libext4_utils_static \
+    libcutils \
+    libbase \
 
 include $(BUILD_EXECUTABLE)
diff --git a/adb/CPPLINT.cfg b/adb/CPPLINT.cfg
index 9b906e8..f496490 100644
--- a/adb/CPPLINT.cfg
+++ b/adb/CPPLINT.cfg
@@ -1,2 +1,2 @@
 set noparent
-filter=-build/header_guard,-build/include,-readability/function
+filter=-build/header_guard,-build/include,-readability/function,-whitespace/indent
diff --git a/adb/adb.cpp b/adb/adb.cpp
index c4e3434..2c959a4 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -31,7 +31,10 @@
 #include <time.h>
 
 #include <string>
+#include <vector>
+#include <unordered_map>
 
+#include <base/logging.h>
 #include <base/stringprintf.h>
 #include <base/strings.h>
 
@@ -48,16 +51,25 @@
 #include <sys/mount.h>
 #endif
 
-ADB_MUTEX_DEFINE( D_lock );
+ADB_MUTEX_DEFINE(D_lock);
 
 int HOST = 0;
 
 #if !ADB_HOST
-const char *adb_device_banner = "device";
+const char* adb_device_banner = "device";
+static android::base::LogdLogger gLogdLogger;
 #endif
 
-void fatal(const char *fmt, ...)
-{
+void AdbLogger(android::base::LogId id, android::base::LogSeverity severity,
+               const char* tag, const char* file, unsigned int line,
+               const char* message) {
+    android::base::StderrLogger(id, severity, tag, file, line, message);
+#if !ADB_HOST
+    gLogdLogger(id, severity, tag, file, line, message);
+#endif
+}
+
+void fatal(const char *fmt, ...) {
     va_list ap;
     va_start(ap, fmt);
     fprintf(stderr, "error: ");
@@ -67,8 +79,7 @@
     exit(-1);
 }
 
-void fatal_errno(const char *fmt, ...)
-{
+void fatal_errno(const char* fmt, ...) {
     va_list ap;
     va_start(ap, fmt);
     fprintf(stderr, "error: %s: ", strerror(errno));
@@ -79,7 +90,7 @@
 }
 
 #if !ADB_HOST
-void start_device_log(void) {
+static std::string get_log_file_name() {
     struct tm now;
     time_t t;
     tzset();
@@ -89,13 +100,18 @@
     char timestamp[PATH_MAX];
     strftime(timestamp, sizeof(timestamp), "%Y-%m-%d-%H-%M-%S", &now);
 
-    std::string path = android::base::StringPrintf("/data/adb/adb-%s-%d", timestamp, getpid());
-    int fd = unix_open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0640);
+    return android::base::StringPrintf("/data/adb/adb-%s-%d", timestamp,
+                                       getpid());
+}
+
+void start_device_log(void) {
+    int fd = unix_open(get_log_file_name().c_str(),
+                       O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0640);
     if (fd == -1) {
         return;
     }
 
-    // redirect stdout and stderr to the log file
+    // Redirect stdout and stderr to the log file.
     dup2(fd, STDOUT_FILENO);
     dup2(fd, STDERR_FILENO);
     fprintf(stderr, "--- adb starting (pid %d) ---\n", getpid());
@@ -130,71 +146,58 @@
 #endif
 }
 
-// Split the comma/space/colum/semi-column separated list of tags from the trace
-// setting and build the trace mask from it. note that '1' and 'all' are special
-// cases to enable all tracing.
+// Split the space separated list of tags from the trace setting and build the
+// trace mask from it. note that '1' and 'all' are special cases to enable all
+// tracing.
 //
 // adb's trace setting comes from the ADB_TRACE environment variable, whereas
 // adbd's comes from the system property persist.adb.trace_mask.
-void adb_trace_init() {
+static void setup_trace_mask() {
     const std::string trace_setting = get_trace_setting();
 
-    static const struct {
-        const char*  tag;
-        int           flag;
-    } tags[] = {
-        { "1", 0 },
-        { "all", 0 },
-        { "adb", TRACE_ADB },
-        { "sockets", TRACE_SOCKETS },
-        { "packets", TRACE_PACKETS },
-        { "rwx", TRACE_RWX },
-        { "usb", TRACE_USB },
-        { "sync", TRACE_SYNC },
-        { "sysdeps", TRACE_SYSDEPS },
-        { "transport", TRACE_TRANSPORT },
-        { "jdwp", TRACE_JDWP },
-        { "services", TRACE_SERVICES },
-        { "auth", TRACE_AUTH },
-        { NULL, 0 }
-    };
+    std::unordered_map<std::string, int> trace_flags = {
+        {"1", 0},
+        {"all", 0},
+        {"adb", TRACE_ADB},
+        {"sockets", TRACE_SOCKETS},
+        {"packets", TRACE_PACKETS},
+        {"rwx", TRACE_RWX},
+        {"usb", TRACE_USB},
+        {"sync", TRACE_SYNC},
+        {"sysdeps", TRACE_SYSDEPS},
+        {"transport", TRACE_TRANSPORT},
+        {"jdwp", TRACE_JDWP},
+        {"services", TRACE_SERVICES},
+        {"auth", TRACE_AUTH}};
 
-    if (trace_setting.empty()) {
-        return;
-    }
-
-    // Use a comma/colon/semi-colon/space separated list
-    const char* p = trace_setting.c_str();
-    while (*p) {
-        int  len, tagn;
-
-        const char* q = strpbrk(p, " ,:;");
-        if (q == NULL) {
-            q = p + strlen(p);
+    std::vector<std::string> elements = android::base::Split(trace_setting, " ");
+    for (const auto& elem : elements) {
+        const auto& flag = trace_flags.find(elem);
+        if (flag == trace_flags.end()) {
+            D("Unknown trace flag: %s", flag->first.c_str());
+            continue;
         }
-        len = q - p;
 
-        for (tagn = 0; tags[tagn].tag != NULL; tagn++) {
-            int  taglen = strlen(tags[tagn].tag);
-
-            if (len == taglen && !memcmp(tags[tagn].tag, p, len)) {
-                int  flag = tags[tagn].flag;
-                if (flag == 0) {
-                    adb_trace_mask = ~0;
-                    return;
-                }
-                adb_trace_mask |= (1 << flag);
-                break;
-            }
+        if (flag->second == 0) {
+            // 0 is used for the special values "1" and "all" that enable all
+            // tracing.
+            adb_trace_mask = ~0;
+            return;
+        } else {
+            adb_trace_mask |= 1 << flag->second;
         }
-        p = q;
-        if (*p)
-            p++;
     }
+}
 
+void adb_trace_init(char** argv) {
 #if !ADB_HOST
-    start_device_log();
+    if (isatty(STDOUT_FILENO) == 0) {
+        start_device_log();
+    }
 #endif
+
+    setup_trace_mask();
+    android::base::InitLogging(argv, AdbLogger);
 }
 
 apacket* get_apacket(void)
@@ -369,24 +372,24 @@
 
     const std::string& type = pieces[0];
     if (type == "bootloader") {
-        D("setting connection_state to CS_BOOTLOADER\n");
-        t->connection_state = CS_BOOTLOADER;
+        D("setting connection_state to kCsBootloader\n");
+        t->connection_state = kCsBootloader;
         update_transports();
     } else if (type == "device") {
-        D("setting connection_state to CS_DEVICE\n");
-        t->connection_state = CS_DEVICE;
+        D("setting connection_state to kCsDevice\n");
+        t->connection_state = kCsDevice;
         update_transports();
     } else if (type == "recovery") {
-        D("setting connection_state to CS_RECOVERY\n");
-        t->connection_state = CS_RECOVERY;
+        D("setting connection_state to kCsRecovery\n");
+        t->connection_state = kCsRecovery;
         update_transports();
     } else if (type == "sideload") {
-        D("setting connection_state to CS_SIDELOAD\n");
-        t->connection_state = CS_SIDELOAD;
+        D("setting connection_state to kCsSideload\n");
+        t->connection_state = kCsSideload;
         update_transports();
     } else {
-        D("setting connection_state to CS_HOST\n");
-        t->connection_state = CS_HOST;
+        D("setting connection_state to kCsHost\n");
+        t->connection_state = kCsHost;
     }
 }
 
@@ -406,7 +409,7 @@
             send_packet(p, t);
             if(HOST) send_connect(t);
         } else {
-            t->connection_state = CS_OFFLINE;
+            t->connection_state = kCsOffline;
             handle_offline(t);
             send_packet(p, t);
         }
@@ -414,8 +417,8 @@
 
     case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
             /* XXX verify version, etc */
-        if(t->connection_state != CS_OFFLINE) {
-            t->connection_state = CS_OFFLINE;
+        if(t->connection_state != kCsOffline) {
+            t->connection_state = kCsOffline;
             handle_offline(t);
         }
 
@@ -431,7 +434,7 @@
 
     case A_AUTH:
         if (p->msg.arg0 == ADB_AUTH_TOKEN) {
-            t->connection_state = CS_UNAUTHORIZED;
+            t->connection_state = kCsUnauthorized;
             t->key = adb_auth_nextkey(t->key);
             if (t->key) {
                 send_auth_response(p->data, p->msg.data_length, t);
@@ -757,7 +760,7 @@
         }
 
         std::string error_msg;
-        transport = acquire_one_transport(CS_ANY, type, serial, &error_msg);
+        transport = acquire_one_transport(kCsAny, type, serial, &error_msg);
         if (!transport) {
             SendFail(reply_fd, error_msg);
             return 1;
@@ -826,7 +829,7 @@
         }
 
         std::string error_msg = "unknown failure";
-        transport = acquire_one_transport(CS_ANY, type, serial, &error_msg);
+        transport = acquire_one_transport(kCsAny, type, serial, &error_msg);
 
         if (transport) {
             s->transport = transport;
@@ -889,7 +892,7 @@
 
     if(!strncmp(service,"get-serialno",strlen("get-serialno"))) {
         const char *out = "unknown";
-        transport = acquire_one_transport(CS_ANY, type, serial, NULL);
+        transport = acquire_one_transport(kCsAny, type, serial, NULL);
         if (transport && transport->serial) {
             out = transport->serial;
         }
@@ -899,7 +902,7 @@
     }
     if(!strncmp(service,"get-devpath",strlen("get-devpath"))) {
         const char *out = "unknown";
-        transport = acquire_one_transport(CS_ANY, type, serial, NULL);
+        transport = acquire_one_transport(kCsAny, type, serial, NULL);
         if (transport && transport->devpath) {
             out = transport->devpath;
         }
@@ -916,7 +919,7 @@
     }
 
     if(!strncmp(service,"get-state",strlen("get-state"))) {
-        transport = acquire_one_transport(CS_ANY, type, serial, NULL);
+        transport = acquire_one_transport(kCsAny, type, serial, NULL);
         SendOkay(reply_fd);
         SendProtocolString(reply_fd, transport->connection_state_name());
         return 0;
diff --git a/adb/adb.h b/adb/adb.h
index 7942a86..1be83d7 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -20,6 +20,8 @@
 #include <limits.h>
 #include <sys/types.h>
 
+#include <base/macros.h>
+
 #include "adb_trace.h"
 #include "fdevent.h"
 
@@ -43,7 +45,7 @@
 // Increment this when we want to force users to start a new adb server.
 #define ADB_SERVER_VERSION 32
 
-struct atransport;
+class atransport;
 struct usb_handle;
 
 struct amessage {
@@ -152,65 +154,88 @@
 };
 
 
-/* a transport object models the connection to a remote device or emulator
-** there is one transport per connected device/emulator. a "local transport"
-** connects through TCP (for the emulator), while a "usb transport" through
-** USB (for real devices)
-**
-** note that kTransportHost doesn't really correspond to a real transport
-** object, it's a special value used to indicate that a client wants to
-** connect to a service implemented within the ADB server itself.
-*/
+// A transport object models the connection to a remote device or emulator there
+// is one transport per connected device/emulator. A "local transport" connects
+// through TCP (for the emulator), while a "usb transport" through USB (for real
+// devices).
+//
+// Note that kTransportHost doesn't really correspond to a real transport
+// object, it's a special value used to indicate that a client wants to connect
+// to a service implemented within the ADB server itself.
 enum TransportType {
-        kTransportUsb,
-        kTransportLocal,
-        kTransportAny,
-        kTransportHost,
+    kTransportUsb,
+    kTransportLocal,
+    kTransportAny,
+    kTransportHost,
 };
 
 #define TOKEN_SIZE 20
 
-struct atransport
-{
-    atransport *next;
-    atransport *prev;
+enum ConnectionState {
+    kCsAny = -1,
+    kCsOffline = 0,
+    kCsBootloader,
+    kCsDevice,
+    kCsHost,
+    kCsRecovery,
+    kCsNoPerm,  // Insufficient permissions to communicate with the device.
+    kCsSideload,
+    kCsUnauthorized,
+};
 
-    int (*read_from_remote)(apacket *p, atransport *t);
-    int (*write_to_remote)(apacket *p, atransport *t);
-    void (*close)(atransport *t);
-    void (*kick)(atransport *t);
+class atransport {
+public:
+    // TODO(danalbert): We expose waaaaaaay too much stuff because this was
+    // historically just a struct, but making the whole thing a more idiomatic
+    // class in one go is a very large change. Given how bad our testing is,
+    // it's better to do this piece by piece.
 
-    int fd;
-    int transport_socket;
+    atransport() {
+        auth_fde = {};
+        transport_fde = {};
+    }
+
+    virtual ~atransport() {}
+
+    int (*read_from_remote)(apacket* p, atransport* t) = nullptr;
+    int (*write_to_remote)(apacket* p, atransport* t) = nullptr;
+    void (*close)(atransport* t) = nullptr;
+    void (*kick)(atransport* t) = nullptr;
+
+    int fd = -1;
+    int transport_socket = -1;
     fdevent transport_fde;
-    int ref_count;
-    unsigned sync_token;
-    int connection_state;
-    int online;
-    TransportType type;
+    int ref_count = 0;
+    uint32_t sync_token = 0;
+    ConnectionState connection_state = kCsOffline;
+    bool online = false;
+    TransportType type = kTransportAny;
 
-        /* usb handle or socket fd as needed */
-    usb_handle *usb;
-    int sfd;
+    // USB handle or socket fd as needed.
+    usb_handle* usb = nullptr;
+    int sfd = -1;
 
-        /* used to identify transports for clients */
-    char *serial;
-    char *product;
-    char *model;
-    char *device;
-    char *devpath;
-    int adb_port; // Use for emulators (local transport)
+    // Used to identify transports for clients.
+    char* serial = nullptr;
+    char* product = nullptr;
+    char* model = nullptr;
+    char* device = nullptr;
+    char* devpath = nullptr;
+    int adb_port = -1;  // Use for emulators (local transport)
+    bool kicked = false;
 
-        /* a list of adisconnect callbacks called when the transport is kicked */
-    int          kicked;
-    adisconnect  disconnects;
+    // A list of adisconnect callbacks called when the transport is kicked.
+    adisconnect disconnects = {};
 
-    void *key;
-    unsigned char token[TOKEN_SIZE];
+    void* key = nullptr;
+    unsigned char token[TOKEN_SIZE] = {};
     fdevent auth_fde;
-    unsigned failed_auth_attempts;
+    size_t failed_auth_attempts = 0;
 
     const char* connection_state_name() const;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(atransport);
 };
 
 
@@ -266,7 +291,7 @@
 int get_available_local_transport_index();
 #endif
 int  init_socket_transport(atransport *t, int s, int port, int local);
-void init_usb_transport(atransport *t, usb_handle *usb, int state);
+void init_usb_transport(atransport *t, usb_handle *usb, ConnectionState state);
 
 #if ADB_HOST
 atransport* find_emulator_transport_by_adb_port(int adb_port);
@@ -336,17 +361,7 @@
 
 int adb_commandline(int argc, const char **argv);
 
-int connection_state(atransport *t);
-
-#define CS_ANY       -1
-#define CS_OFFLINE    0
-#define CS_BOOTLOADER 1
-#define CS_DEVICE     2
-#define CS_HOST       3
-#define CS_RECOVERY   4
-#define CS_NOPERM     5 /* Insufficient permissions to communicate with the device */
-#define CS_SIDELOAD   6
-#define CS_UNAUTHORIZED 7
+ConnectionState connection_state(atransport *t);
 
 extern const char *adb_device_banner;
 extern int HOST;
diff --git a/adb/adb_auth_host.cpp b/adb/adb_auth_host.cpp
index 5581516..61a3777 100644
--- a/adb/adb_auth_host.cpp
+++ b/adb/adb_auth_host.cpp
@@ -16,11 +16,6 @@
 
 #define TRACE_TAG TRACE_AUTH
 
-#ifdef _WIN32
-// This blocks some definitions we need on Windows.
-#undef NOGDI
-#endif
-
 #include "sysdeps.h"
 #include "adb_auth.h"
 
diff --git a/adb/adb_trace.h b/adb/adb_trace.h
index 63d4151..dbc7ec8 100644
--- a/adb/adb_trace.h
+++ b/adb/adb_trace.h
@@ -57,9 +57,9 @@
 #define DQ(...) ((void)0)
 #endif  /* !ADB_HOST */
 
-extern int     adb_trace_mask;
-extern unsigned char    adb_trace_output_count;
-void    adb_trace_init(void);
+extern int adb_trace_mask;
+extern unsigned char adb_trace_output_count;
+void adb_trace_init(char**);
 
 #  define ADB_TRACING  ((adb_trace_mask & (1 << TRACE_TAG)) != 0)
 
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index f48182d..0cd6670 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -176,9 +176,7 @@
 
 int main(int argc, char** argv) {
     adb_sysdeps_init();
-
-    android::base::InitLogging(argv);
-    adb_trace_init();
+    adb_trace_init(argv);
     D("Handling commandline()\n");
     return adb_commandline(argc - 1, const_cast<const char**>(argv + 1));
 }
diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp
index 99ff539..c0612cd 100644
--- a/adb/daemon/main.cpp
+++ b/adb/daemon/main.cpp
@@ -230,8 +230,6 @@
 }
 
 int main(int argc, char** argv) {
-    android::base::InitLogging(argv);
-
     while (true) {
         static struct option opts[] = {
             {"root_seclabel", required_argument, nullptr, 's'},
@@ -265,7 +263,7 @@
 
     close_stdin();
 
-    adb_trace_init();
+    adb_trace_init(argv);
 
     /* If adbd runs inside the emulator this will enable adb tracing via
      * adb-debug qemud service in the emulator. */
diff --git a/adb/services.cpp b/adb/services.cpp
index 8ce9f71..a9edbe5 100644
--- a/adb/services.cpp
+++ b/adb/services.cpp
@@ -516,7 +516,7 @@
 struct state_info {
     TransportType transport_type;
     char* serial;
-    int state;
+    ConnectionState state;
 };
 
 static void wait_for_state(int fd, void* cookie)
@@ -665,13 +665,13 @@
 
         if (!strncmp(name, "local", strlen("local"))) {
             sinfo->transport_type = kTransportLocal;
-            sinfo->state = CS_DEVICE;
+            sinfo->state = kCsDevice;
         } else if (!strncmp(name, "usb", strlen("usb"))) {
             sinfo->transport_type = kTransportUsb;
-            sinfo->state = CS_DEVICE;
+            sinfo->state = kCsDevice;
         } else if (!strncmp(name, "any", strlen("any"))) {
             sinfo->transport_type = kTransportAny;
-            sinfo->state = CS_DEVICE;
+            sinfo->state = kCsDevice;
         } else {
             if (sinfo->serial) {
                 free(sinfo->serial);
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 62cba6d..621944e 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -815,7 +815,8 @@
 #else /* !ADB_HOST */
     if (s->transport == NULL) {
         std::string error_msg = "unknown failure";
-        s->transport = acquire_one_transport(CS_ANY, kTransportAny, NULL, &error_msg);
+        s->transport =
+            acquire_one_transport(kCsAny, kTransportAny, NULL, &error_msg);
 
         if (s->transport == NULL) {
             SendFail(s->peer->fd, error_msg);
@@ -824,7 +825,7 @@
     }
 #endif
 
-    if(!(s->transport) || (s->transport->connection_state == CS_OFFLINE)) {
+    if(!(s->transport) || (s->transport->connection_state == kCsOffline)) {
            /* if there's no remote we fail the connection
             ** right here and terminate it
             */
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index b7962b9..a21272f 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -16,9 +16,6 @@
 
 #define TRACE_TAG TRACE_SYSDEPS
 
-// For whatever reason this blocks the definition of ToAscii...
-#undef NOGDI
-
 #include "sysdeps.h"
 
 #include <winsock2.h> /* winsock.h *must* be included before windows.h. */
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 29cf580..379c702 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -26,6 +26,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <list>
+
 #include <base/stringprintf.h>
 
 #include "adb.h"
@@ -33,15 +35,8 @@
 
 static void transport_unref(atransport *t);
 
-static atransport transport_list = {
-    .next = &transport_list,
-    .prev = &transport_list,
-};
-
-static atransport pending_list = {
-    .next = &pending_list,
-    .prev = &pending_list,
-};
+static std::list<atransport*> transport_list;
+static std::list<atransport*> pending_list;
 
 ADB_MUTEX_DEFINE( transport_lock );
 
@@ -553,8 +548,7 @@
         adb_close(t->fd);
 
         adb_mutex_lock(&transport_lock);
-        t->next->prev = t->prev;
-        t->prev->next = t->next;
+        transport_list.remove(t);
         adb_mutex_unlock(&transport_lock);
 
         run_transport_disconnects(t);
@@ -570,19 +564,18 @@
         if (t->devpath)
             free(t->devpath);
 
-        memset(t,0xee,sizeof(atransport));
-        free(t);
+        delete t;
 
         update_transports();
         return;
     }
 
     /* don't create transport threads for inaccessible devices */
-    if (t->connection_state != CS_NOPERM) {
+    if (t->connection_state != kCsNoPerm) {
         /* initial references are the two threads */
         t->ref_count = 2;
 
-        if(adb_socketpair(s)) {
+        if (adb_socketpair(s)) {
             fatal_errno("cannot open transport socketpair");
         }
 
@@ -608,14 +601,8 @@
     }
 
     adb_mutex_lock(&transport_lock);
-    /* remove from pending list */
-    t->next->prev = t->prev;
-    t->prev->next = t->next;
-    /* put us on the master device list */
-    t->next = &transport_list;
-    t->prev = transport_list.prev;
-    t->next->prev = t;
-    t->prev->next = t;
+    pending_list.remove(t);
+    transport_list.push_front(t);
     adb_mutex_unlock(&transport_lock);
 
     t->disconnects.next = t->disconnects.prev = &t->disconnects;
@@ -738,10 +725,8 @@
     return !*to_test;
 }
 
-atransport* acquire_one_transport(int state, TransportType type,
-                                  const char* serial, std::string* error_out)
-{
-    atransport *t;
+atransport* acquire_one_transport(ConnectionState state, TransportType type,
+                                  const char* serial, std::string* error_out) {
     atransport *result = NULL;
     int ambiguous = 0;
 
@@ -749,8 +734,8 @@
     if (error_out) *error_out = android::base::StringPrintf("device '%s' not found", serial);
 
     adb_mutex_lock(&transport_lock);
-    for (t = transport_list.next; t != &transport_list; t = t->next) {
-        if (t->connection_state == CS_NOPERM) {
+    for (auto t : transport_list) {
+        if (t->connection_state == kCsNoPerm) {
             if (error_out) *error_out = "insufficient permissions for device";
             continue;
         }
@@ -801,7 +786,7 @@
     adb_mutex_unlock(&transport_lock);
 
     if (result) {
-        if (result->connection_state == CS_UNAUTHORIZED) {
+        if (result->connection_state == kCsUnauthorized) {
             if (error_out) {
                 *error_out = "device unauthorized.\n";
                 char* ADB_VENDOR_KEYS = getenv("ADB_VENDOR_KEYS");
@@ -814,13 +799,13 @@
         }
 
         /* offline devices are ignored -- they are either being born or dying */
-        if (result && result->connection_state == CS_OFFLINE) {
+        if (result && result->connection_state == kCsOffline) {
             if (error_out) *error_out = "device offline";
             result = NULL;
         }
 
         /* check for required connection state */
-        if (result && state != CS_ANY && result->connection_state != state) {
+        if (result && state != kCsAny && result->connection_state != state) {
             if (error_out) *error_out = "invalid device state";
             result = NULL;
         }
@@ -829,7 +814,7 @@
     if (result) {
         /* found one that we can take */
         if (error_out) *error_out = "success";
-    } else if (state != CS_ANY && (serial || !ambiguous)) {
+    } else if (state != kCsAny && (serial || !ambiguous)) {
         adb_sleep_ms(1000);
         goto retry;
     }
@@ -839,14 +824,14 @@
 
 const char* atransport::connection_state_name() const {
     switch (connection_state) {
-    case CS_OFFLINE: return "offline";
-    case CS_BOOTLOADER: return "bootloader";
-    case CS_DEVICE: return "device";
-    case CS_HOST: return "host";
-    case CS_RECOVERY: return "recovery";
-    case CS_NOPERM: return "no permissions";
-    case CS_SIDELOAD: return "sideload";
-    case CS_UNAUTHORIZED: return "unauthorized";
+    case kCsOffline: return "offline";
+    case kCsBootloader: return "bootloader";
+    case kCsDevice: return "device";
+    case kCsHost: return "host";
+    case kCsRecovery: return "recovery";
+    case kCsNoPerm: return "no permissions";
+    case kCsSideload: return "sideload";
+    case kCsUnauthorized: return "unauthorized";
     default: return "unknown";
     }
 }
@@ -867,7 +852,8 @@
     }
 }
 
-static void append_transport(atransport* t, std::string* result, bool long_listing) {
+static void append_transport(const atransport* t, std::string* result,
+                             bool long_listing) {
     const char* serial = t->serial;
     if (!serial || !serial[0]) {
         serial = "(no serial number)";
@@ -891,7 +877,7 @@
 std::string list_transports(bool long_listing) {
     std::string result;
     adb_mutex_lock(&transport_lock);
-    for (atransport* t = transport_list.next; t != &transport_list; t = t->next) {
+    for (const auto t : transport_list) {
         append_transport(t, &result, long_listing);
     }
     adb_mutex_unlock(&transport_lock);
@@ -899,11 +885,10 @@
 }
 
 /* hack for osx */
-void close_usb_devices()
-{
+void close_usb_devices() {
     adb_mutex_lock(&transport_lock);
-    for (atransport* t = transport_list.next; t != &transport_list; t = t->next) {
-        if ( !t->kicked ) {
+    for (auto t : transport_list) {
+        if (!t->kicked) {
             t->kicked = 1;
             t->kick(t);
         }
@@ -912,47 +897,39 @@
 }
 #endif // ADB_HOST
 
-int register_socket_transport(int s, const char *serial, int port, int local)
-{
-    atransport *t = reinterpret_cast<atransport*>(calloc(1, sizeof(atransport)));
-    if (t == nullptr) {
-        return -1;
-    }
-
-    atransport *n;
-    char buff[32];
+int register_socket_transport(int s, const char *serial, int port, int local) {
+    atransport* t = new atransport();
 
     if (!serial) {
-        snprintf(buff, sizeof buff, "T-%p", t);
-        serial = buff;
+        char buf[32];
+        snprintf(buf, sizeof(buf), "T-%p", t);
+        serial = buf;
     }
+
     D("transport: %s init'ing for socket %d, on port %d\n", serial, s, port);
     if (init_socket_transport(t, s, port, local) < 0) {
-        free(t);
+        delete t;
         return -1;
     }
 
     adb_mutex_lock(&transport_lock);
-    for (n = pending_list.next; n != &pending_list; n = n->next) {
-        if (n->serial && !strcmp(serial, n->serial)) {
+    for (auto transport : pending_list) {
+        if (transport->serial && strcmp(serial, transport->serial) == 0) {
             adb_mutex_unlock(&transport_lock);
-            free(t);
+            delete t;
             return -1;
         }
     }
 
-    for (n = transport_list.next; n != &transport_list; n = n->next) {
-        if (n->serial && !strcmp(serial, n->serial)) {
+    for (auto transport : transport_list) {
+        if (transport->serial && strcmp(serial, transport->serial) == 0) {
             adb_mutex_unlock(&transport_lock);
-            free(t);
+            delete t;
             return -1;
         }
     }
 
-    t->next = &pending_list;
-    t->prev = pending_list.prev;
-    t->next->prev = t;
-    t->prev->next = t;
+    pending_list.push_front(t);
     t->serial = strdup(serial);
     adb_mutex_unlock(&transport_lock);
 
@@ -961,96 +938,83 @@
 }
 
 #if ADB_HOST
-atransport *find_transport(const char *serial)
-{
-    atransport *t;
+atransport *find_transport(const char *serial) {
+    atransport* result = nullptr;
 
     adb_mutex_lock(&transport_lock);
-    for(t = transport_list.next; t != &transport_list; t = t->next) {
-        if (t->serial && !strcmp(serial, t->serial)) {
+    for (auto t : transport_list) {
+        if (t->serial && strcmp(serial, t->serial) == 0) {
+            result = t;
             break;
         }
-     }
+    }
     adb_mutex_unlock(&transport_lock);
 
-    if (t != &transport_list)
-        return t;
-    else
-        return 0;
+    return result;
 }
 
 void unregister_transport(atransport *t)
 {
     adb_mutex_lock(&transport_lock);
-    t->next->prev = t->prev;
-    t->prev->next = t->next;
+    transport_list.remove(t);
     adb_mutex_unlock(&transport_lock);
 
     kick_transport(t);
     transport_unref(t);
 }
 
-// unregisters all non-emulator TCP transports
-void unregister_all_tcp_transports()
-{
-    atransport *t, *next;
+// Unregisters all non-emulator TCP transports.
+void unregister_all_tcp_transports() {
     adb_mutex_lock(&transport_lock);
-    for (t = transport_list.next; t != &transport_list; t = next) {
-        next = t->next;
+    for (auto it = transport_list.begin(); it != transport_list.end(); ) {
+        atransport* t = *it;
         if (t->type == kTransportLocal && t->adb_port == 0) {
-            t->next->prev = t->prev;
-            t->prev->next = next;
-            // we cannot call kick_transport when holding transport_lock
-            if (!t->kicked)
-            {
+            // We cannot call kick_transport when holding transport_lock.
+            if (!t->kicked) {
                 t->kicked = 1;
                 t->kick(t);
             }
             transport_unref_locked(t);
+
+            it = transport_list.erase(it);
+        } else {
+            ++it;
         }
-     }
+    }
 
     adb_mutex_unlock(&transport_lock);
 }
 
 #endif
 
-void register_usb_transport(usb_handle *usb, const char *serial, const char *devpath, unsigned writeable)
-{
-    atransport *t = reinterpret_cast<atransport*>(calloc(1, sizeof(atransport)));
-    if (t == nullptr) fatal("cannot allocate USB atransport");
+void register_usb_transport(usb_handle* usb, const char* serial,
+                            const char* devpath, unsigned writeable) {
+    atransport* t = new atransport();
+
     D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb,
       serial ? serial : "");
-    init_usb_transport(t, usb, (writeable ? CS_OFFLINE : CS_NOPERM));
+    init_usb_transport(t, usb, (writeable ? kCsOffline : kCsNoPerm));
     if(serial) {
         t->serial = strdup(serial);
     }
-    if(devpath) {
+
+    if (devpath) {
         t->devpath = strdup(devpath);
     }
 
     adb_mutex_lock(&transport_lock);
-    t->next = &pending_list;
-    t->prev = pending_list.prev;
-    t->next->prev = t;
-    t->prev->next = t;
+    pending_list.push_front(t);
     adb_mutex_unlock(&transport_lock);
 
     register_transport(t);
 }
 
-/* this should only be used for transports with connection_state == CS_NOPERM */
-void unregister_usb_transport(usb_handle *usb)
-{
-    atransport *t;
+// This should only be used for transports with connection_state == kCsNoPerm.
+void unregister_usb_transport(usb_handle *usb) {
     adb_mutex_lock(&transport_lock);
-    for(t = transport_list.next; t != &transport_list; t = t->next) {
-        if (t->usb == usb && t->connection_state == CS_NOPERM) {
-            t->next->prev = t->prev;
-            t->prev->next = t->next;
-            break;
-        }
-     }
+    transport_list.remove_if([usb](atransport* t) {
+        return t->usb == usb && t->connection_state == kCsNoPerm;
+    });
     adb_mutex_unlock(&transport_lock);
 }
 
diff --git a/adb/transport.h b/adb/transport.h
index 7b799b9..538f63e 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -25,11 +25,11 @@
 
 /*
  * Obtain a transport from the available transports.
- * If state is != CS_ANY, only transports in that state are considered.
+ * If state is != kCsAny, only transports in that state are considered.
  * If serial is non-NULL then only the device with that serial will be chosen.
  * If no suitable transport is found, error is set.
  */
-atransport* acquire_one_transport(int state, TransportType type,
+atransport* acquire_one_transport(ConnectionState state, TransportType type,
                                   const char* serial, std::string* error_out);
 void add_transport_disconnect(atransport* t, adisconnect* dis);
 void remove_transport_disconnect(atransport* t, adisconnect* dis);
@@ -50,7 +50,7 @@
 /* cause new transports to be init'd and added to the list */
 int register_socket_transport(int s, const char* serial, int port, int local);
 
-/* this should only be used for transports with connection_state == CS_NOPERM */
+// This should only be used for transports with connection_state == kCsNoPerm.
 void unregister_usb_transport(usb_handle* usb);
 
 /* these should only be used for the "adb disconnect" command */
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index 5f7449d..d3c4c30 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -387,7 +387,7 @@
     t->write_to_remote = remote_write;
     t->sfd = s;
     t->sync_token = 1;
-    t->connection_state = CS_OFFLINE;
+    t->connection_state = kCsOffline;
     t->type = kTransportLocal;
     t->adb_port = 0;
 
diff --git a/adb/transport_test.cpp b/adb/transport_test.cpp
index 2b3fe3c..4b74adf 100644
--- a/adb/transport_test.cpp
+++ b/adb/transport_test.cpp
@@ -20,34 +20,85 @@
 
 #include "adb.h"
 
+class TestTransport : public atransport {
+public:
+    bool operator==(const atransport& rhs) const {
+        EXPECT_EQ(read_from_remote, rhs.read_from_remote);
+        EXPECT_EQ(write_to_remote, rhs.write_to_remote);
+        EXPECT_EQ(close, rhs.close);
+        EXPECT_EQ(kick, rhs.kick);
+
+        EXPECT_EQ(fd, rhs.fd);
+        EXPECT_EQ(transport_socket, rhs.transport_socket);
+
+        EXPECT_EQ(
+            0, memcmp(&transport_fde, &rhs.transport_fde, sizeof(fdevent)));
+
+        EXPECT_EQ(ref_count, rhs.ref_count);
+        EXPECT_EQ(sync_token, rhs.sync_token);
+        EXPECT_EQ(connection_state, rhs.connection_state);
+        EXPECT_EQ(online, rhs.online);
+        EXPECT_EQ(type, rhs.type);
+
+        EXPECT_EQ(usb, rhs.usb);
+        EXPECT_EQ(sfd, rhs.sfd);
+
+        EXPECT_EQ(serial, rhs.serial);
+        EXPECT_EQ(product, rhs.product);
+        EXPECT_EQ(model, rhs.model);
+        EXPECT_EQ(device, rhs.device);
+        EXPECT_EQ(devpath, rhs.devpath);
+        EXPECT_EQ(adb_port, rhs.adb_port);
+        EXPECT_EQ(kicked, rhs.kicked);
+
+        EXPECT_EQ(
+            0, memcmp(&disconnects, &rhs.disconnects, sizeof(adisconnect)));
+
+        EXPECT_EQ(key, rhs.key);
+        EXPECT_EQ(0, memcmp(token, rhs.token, TOKEN_SIZE));
+        EXPECT_EQ(0, memcmp(&auth_fde, &rhs.auth_fde, sizeof(fdevent)));
+        EXPECT_EQ(failed_auth_attempts, rhs.failed_auth_attempts);
+
+        return true;
+    }
+};
+
 TEST(transport, kick_transport) {
-  atransport t = {};
+  TestTransport t;
+
   // Mutate some member so we can test that the function is run.
   t.kick = [](atransport* trans) { trans->fd = 42; };
-  atransport expected = t;
+
+  TestTransport expected;
+  expected.kick = t.kick;
   expected.fd = 42;
   expected.kicked = 1;
+
   kick_transport(&t);
   ASSERT_EQ(42, t.fd);
   ASSERT_EQ(1, t.kicked);
-  ASSERT_EQ(0, memcmp(&expected, &t, sizeof(atransport)));
+  ASSERT_EQ(expected, t);
 }
 
 TEST(transport, kick_transport_already_kicked) {
   // Ensure that the transport is not modified if the transport has already been
   // kicked.
-  atransport t = {};
+  TestTransport t;
   t.kicked = 1;
   t.kick = [](atransport*) { FAIL() << "Kick should not have been called"; };
-  atransport expected = t;
+
+  TestTransport expected;
+  expected.kicked = 1;
+  expected.kick = t.kick;
+
   kick_transport(&t);
-  ASSERT_EQ(0, memcmp(&expected, &t, sizeof(atransport)));
+  ASSERT_EQ(expected, t);
 }
 
 // Disabled because the function currently segfaults for a zeroed atransport. I
 // want to make sure I understand how this is working at all before I try fixing
 // that.
 TEST(transport, DISABLED_run_transport_disconnects_zeroed_atransport) {
-  atransport t = {};
+  atransport t;
   run_transport_disconnects(&t);
 }
diff --git a/adb/transport_usb.cpp b/adb/transport_usb.cpp
index cdabffe..eb3454d 100644
--- a/adb/transport_usb.cpp
+++ b/adb/transport_usb.cpp
@@ -80,7 +80,7 @@
     usb_kick(t->usb);
 }
 
-void init_usb_transport(atransport *t, usb_handle *h, int state)
+void init_usb_transport(atransport *t, usb_handle *h, ConnectionState state)
 {
     D("transport: usb\n");
     t->close = remote_close;
diff --git a/base/include/base/logging.h b/base/include/base/logging.h
index 4a15f43..283a7bc 100644
--- a/base/include/base/logging.h
+++ b/base/include/base/logging.h
@@ -13,18 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #ifndef BASE_LOGGING_H
 #define BASE_LOGGING_H
 
-#ifdef ERROR
-#error ERROR is already defined. If this is Windows code, #define NOGDI before \
-including anything.
-#endif
-
+// NOTE: For Windows, you must include logging.h after windows.h to allow the
+// following code to suppress the evil ERROR macro:
 #ifdef _WIN32
-#ifndef NOGDI
-#define NOGDI // Suppress the evil ERROR macro.
+// windows.h includes wingdi.h which defines an evil macro ERROR.
+#ifdef ERROR
+#undef ERROR
 #endif
 #endif
 
diff --git a/base/include/base/strings.h b/base/include/base/strings.h
index 5dbc5fb..638f845 100644
--- a/base/include/base/strings.h
+++ b/base/include/base/strings.h
@@ -17,6 +17,7 @@
 #ifndef BASE_STRINGS_H
 #define BASE_STRINGS_H
 
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -34,9 +35,24 @@
 // Trims whitespace off both ends of the given string.
 std::string Trim(const std::string& s);
 
-// Joins a vector of strings into a single string, using the given separator.
-template <typename StringT>
-std::string Join(const std::vector<StringT>& strings, char separator);
+// Joins a container of things into a single string, using the given separator.
+template <typename ContainerT>
+std::string Join(const ContainerT& things, char separator) {
+  if (things.empty()) {
+    return "";
+  }
+
+  std::ostringstream result;
+  result << *things.begin();
+  for (auto it = std::next(things.begin()); it != things.end(); ++it) {
+    result << separator << *it;
+  }
+  return result.str();
+}
+
+// We instantiate the common cases in strings.cpp.
+extern template std::string Join(const std::vector<std::string>&, char);
+extern template std::string Join(const std::vector<const char*>&, char);
 
 // Tests whether 's' starts with 'prefix'.
 bool StartsWith(const std::string& s, const char* prefix);
diff --git a/base/logging.cpp b/base/logging.cpp
index 7a08c39..34229a2 100644
--- a/base/logging.cpp
+++ b/base/logging.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
 #include "base/logging.h"
 
 #include <libgen.h>
@@ -34,8 +38,6 @@
 
 #ifndef _WIN32
 #include <mutex>
-#else
-#include <windows.h>
 #endif
 
 #include "base/macros.h"
diff --git a/base/strings.cpp b/base/strings.cpp
index d3375d9..bac983b 100644
--- a/base/strings.cpp
+++ b/base/strings.cpp
@@ -79,25 +79,10 @@
   return s.substr(start_index, end_index - start_index + 1);
 }
 
-template <typename StringT>
-std::string Join(const std::vector<StringT>& strings, char separator) {
-  if (strings.empty()) {
-    return "";
-  }
-
-  std::string result(strings[0]);
-  for (size_t i = 1; i < strings.size(); ++i) {
-    result += separator;
-    result += strings[i];
-  }
-  return result;
-}
-
-// Explicit instantiations.
-template std::string Join<std::string>(const std::vector<std::string>& strings,
-                                       char separator);
-template std::string Join<const char*>(const std::vector<const char*>& strings,
-                                       char separator);
+// These cases are probably the norm, so we mark them extern in the header to
+// aid compile time and binary size.
+template std::string Join(const std::vector<std::string>&, char);
+template std::string Join(const std::vector<const char*>&, char);
 
 bool StartsWith(const std::string& s, const char* prefix) {
   return s.compare(0, strlen(prefix), prefix) == 0;
diff --git a/base/strings_test.cpp b/base/strings_test.cpp
index 46a1ab5..5f67575 100644
--- a/base/strings_test.cpp
+++ b/base/strings_test.cpp
@@ -20,6 +20,8 @@
 
 #include <string>
 #include <vector>
+#include <set>
+#include <unordered_set>
 
 TEST(strings, split_empty) {
   std::vector<std::string> parts = android::base::Split("", ",");
@@ -121,6 +123,17 @@
   ASSERT_EQ(",,,", android::base::Join(list, ','));
 }
 
+TEST(strings, join_simple_ints) {
+  std::set<int> list = {1, 2, 3};
+  ASSERT_EQ("1,2,3", android::base::Join(list, ','));
+}
+
+TEST(strings, join_unordered_set) {
+  std::unordered_set<int> list = {1, 2};
+  ASSERT_TRUE("1,2" == android::base::Join(list, ',') ||
+              "2,1" == android::base::Join(list, ','));
+}
+
 TEST(strings, startswith_empty) {
   ASSERT_FALSE(android::base::StartsWith("", "foo"));
   ASSERT_TRUE(android::base::StartsWith("", ""));
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index e139bcd..7acfd6e 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -280,6 +280,17 @@
             "  flashall                                 flash boot, system, vendor and if found,\n"
             "                                           recovery\n"
             "  flash <partition> [ <filename> ]         write a file to a flash partition\n"
+            "  flashing lock                            locks the device. Prevents flashing"
+            "                                           partitions\n"
+            "  flashing unlock                          unlocks the device. Allows user to"
+            "                                           flash any partition except the ones"
+            "                                           that are related to bootloader\n"
+            "  flashing lock_critical                   Prevents flashing bootloader related"
+            "                                           partitions\n"
+            "  flashing unlock_critical                 Enables flashing bootloader related"
+            "                                           partitions\n"
+            "  flashing get_unlock_ability              Queries bootloader to see if the"
+            "                                           device is unlocked\n"
             "  erase <partition>                        erase a flash partition\n"
             "  format[:[<fs type>][:[<size>]] <partition> format a flash partition.\n"
             "                                           Can override the fs type and/or\n"
@@ -1201,6 +1212,16 @@
             wants_reboot = 1;
         } else if(!strcmp(*argv, "oem")) {
             argc = do_oem_command(argc, argv);
+        } else if(!strcmp(*argv, "flashing") && argc == 2) {
+            if(!strcmp(*(argv+1), "unlock") || !strcmp(*(argv+1), "lock")
+               || !strcmp(*(argv+1), "unlock_critical")
+               || !strcmp(*(argv+1), "lock_critical")
+               || !strcmp(*(argv+1), "get_unlock_ability")) {
+              argc = do_oem_command(argc, argv);
+            } else {
+              usage();
+              return 1;
+            }
         } else {
             usage();
             return 1;
diff --git a/include/nativebridge/native_bridge.h b/include/nativebridge/native_bridge.h
index 523dc49..18300bc 100644
--- a/include/nativebridge/native_bridge.h
+++ b/include/nativebridge/native_bridge.h
@@ -18,6 +18,7 @@
 #define NATIVE_BRIDGE_H_
 
 #include "jni.h"
+#include <signal.h>
 #include <stdint.h>
 #include <sys/types.h>
 
@@ -26,6 +27,12 @@
 struct NativeBridgeRuntimeCallbacks;
 struct NativeBridgeRuntimeValues;
 
+// Function pointer type for sigaction. This is mostly the signature of a signal handler, except
+// for the return type. The runtime needs to know whether the signal was handled or should be given
+// to the chain.
+typedef bool (*NativeBridgeSignalHandlerFn)(int, siginfo_t*, void*);
+
+
 // Open the native bridge, if any. Should be called by Runtime::Init(). A null library filename
 // signals that we do not want to load a native bridge.
 bool LoadNativeBridge(const char* native_bridge_library_filename,
@@ -63,6 +70,16 @@
 // True if native library is valid and is for an ABI that is supported by native bridge.
 bool NativeBridgeIsSupported(const char* libpath);
 
+// Returns the version number of the native bridge. This information is available after a
+// successful LoadNativeBridge() and before closing it, that is, as long as NativeBridgeAvailable()
+// returns true. Returns 0 otherwise.
+uint32_t NativeBridgeGetVersion();
+
+// Returns a signal handler that the bridge would like to be managed. Only valid for a native
+// bridge supporting the version 2 interface. Will return null if the bridge does not support
+// version 2, or if it doesn't have a signal handler it wants to be known.
+NativeBridgeSignalHandlerFn NativeBridgeGetSignalHandler(int signal);
+
 // Returns whether we have seen a native bridge error. This could happen because the library
 // was not found, rejected, could not be initialized and so on.
 //
@@ -127,6 +144,31 @@
   //    NULL if not supported by native bridge.
   //    Otherwise, return all environment values to be set after fork.
   const struct NativeBridgeRuntimeValues* (*getAppEnv)(const char* instruction_set);
+
+  // Added callbacks in version 2.
+
+  // Check whether the bridge is compatible with the given version. A bridge may decide not to be
+  // forwards- or backwards-compatible, and libnativebridge will then stop using it.
+  //
+  // Parameters:
+  //     bridge_version [IN] the version of libnativebridge.
+  // Returns:
+  //     true iff the native bridge supports the given version of libnativebridge.
+  bool (*isCompatibleWith)(uint32_t bridge_version);
+
+  // A callback to retrieve a native bridge's signal handler for the specified signal. The runtime
+  // will ensure that the signal handler is being called after the runtime's own handler, but before
+  // all chained handlers. The native bridge should not try to install the handler by itself, as
+  // that will potentially lead to cycles.
+  //
+  // Parameters:
+  //     signal [IN] the signal for which the handler is asked for. Currently, only SIGSEGV is
+  //                 supported by the runtime.
+  // Returns:
+  //     NULL if the native bridge doesn't use a handler or doesn't want it to be managed by the
+  //     runtime.
+  //     Otherwise, a pointer to the signal handler.
+  NativeBridgeSignalHandlerFn (*getSignalHandler)(int signal);
 };
 
 // Runtime interfaces to native bridge.
diff --git a/libcutils/klog.c b/libcutils/klog.c
index f574f08..710dc66 100644
--- a/libcutils/klog.c
+++ b/libcutils/klog.c
@@ -40,6 +40,11 @@
 void klog_init(void) {
     if (klog_fd >= 0) return; /* Already initialized */
 
+    klog_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
+    if (klog_fd >= 0) {
+        return;
+    }
+
     static const char* name = "/dev/__kmsg__";
     if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
         klog_fd = open(name, O_WRONLY | O_CLOEXEC);
diff --git a/libnativebridge/native_bridge.cc b/libnativebridge/native_bridge.cc
index 6fa4b39..f63497b 100644
--- a/libnativebridge/native_bridge.cc
+++ b/libnativebridge/native_bridge.cc
@@ -83,7 +83,7 @@
 static void* native_bridge_handle = nullptr;
 // Pointer to the callbacks. Available as soon as LoadNativeBridge succeeds, but only initialized
 // later.
-static NativeBridgeCallbacks* callbacks = nullptr;
+static const NativeBridgeCallbacks* callbacks = nullptr;
 // Callbacks provided by the environment to the bridge. Passed to LoadNativeBridge.
 static const NativeBridgeRuntimeCallbacks* runtime_callbacks = nullptr;
 
@@ -96,7 +96,7 @@
 // and hard code the directory name again here.
 static constexpr const char* kCodeCacheDir = "code_cache";
 
-static constexpr uint32_t kNativeBridgeCallbackVersion = 1;
+static constexpr uint32_t kLibNativeBridgeVersion = 2;
 
 // Characters allowed in a native bridge filename. The first character must
 // be in [a-zA-Z] (expected 'l' for "libx"). The rest must be in [a-zA-Z0-9._-].
@@ -121,7 +121,9 @@
     // First character must be [a-zA-Z].
     if (!CharacterAllowed(*ptr, true))  {
       // Found an invalid fist character, don't accept.
-      ALOGE("Native bridge library %s has been rejected for first character %c", nb_library_filename, *ptr);
+      ALOGE("Native bridge library %s has been rejected for first character %c",
+            nb_library_filename,
+            *ptr);
       return false;
     } else {
       // For the rest, be more liberal.
@@ -139,8 +141,22 @@
   }
 }
 
-static bool VersionCheck(NativeBridgeCallbacks* cb) {
-  return cb != nullptr && cb->version == kNativeBridgeCallbackVersion;
+static bool VersionCheck(const NativeBridgeCallbacks* cb) {
+  // Libnativebridge is now designed to be forward-compatible. So only "0" is an unsupported
+  // version.
+  if (cb == nullptr || cb->version == 0) {
+    return false;
+  }
+
+  // If this is a v2+ bridge, it may not be forwards- or backwards-compatible. Check.
+  if (cb->version >= 2) {
+    if (!callbacks->isCompatibleWith(kLibNativeBridgeVersion)) {
+      // TODO: Scan which version is supported, and fall back to handle it.
+      return false;
+    }
+  }
+
+  return true;
 }
 
 static void CloseNativeBridge(bool with_error) {
@@ -321,7 +337,7 @@
 }
 
 // Set up the environment for the bridged app.
-static void SetupEnvironment(NativeBridgeCallbacks* callbacks, JNIEnv* env, const char* isa) {
+static void SetupEnvironment(const NativeBridgeCallbacks* callbacks, JNIEnv* env, const char* isa) {
   // Need a JNIEnv* to do anything.
   if (env == nullptr) {
     ALOGW("No JNIEnv* to set up app environment.");
@@ -485,4 +501,18 @@
   return false;
 }
 
+uint32_t NativeBridgeGetVersion() {
+  if (NativeBridgeAvailable()) {
+    return callbacks->version;
+  }
+  return 0;
+}
+
+NativeBridgeSignalHandlerFn NativeBridgeGetSignalHandler(int signal) {
+  if (NativeBridgeInitialized() && callbacks->version >= 2) {
+    return callbacks->getSignalHandler(signal);
+  }
+  return nullptr;
+}
+
 };  // namespace android
diff --git a/libnativebridge/tests/Android.mk b/libnativebridge/tests/Android.mk
index f28c490..285e8c2 100644
--- a/libnativebridge/tests/Android.mk
+++ b/libnativebridge/tests/Android.mk
@@ -11,6 +11,8 @@
     CodeCacheExists_test.cpp \
     CompleteFlow_test.cpp \
     InvalidCharsNativeBridge_test.cpp \
+    NativeBridge2Signal_test.cpp \
+    NativeBridgeVersion_test.cpp \
     NeedsNativeBridge_test.cpp \
     PreInitializeNativeBridge_test.cpp \
     PreInitializeNativeBridgeFail1_test.cpp \
diff --git a/libnativebridge/tests/Android.nativebridge-dummy.mk b/libnativebridge/tests/Android.nativebridge-dummy.mk
index 1caf50a..2efc176 100644
--- a/libnativebridge/tests/Android.nativebridge-dummy.mk
+++ b/libnativebridge/tests/Android.nativebridge-dummy.mk
@@ -32,3 +32,39 @@
 LOCAL_MULTILIB := both
 
 include $(BUILD_HOST_SHARED_LIBRARY)
+
+
+# v2.
+
+NATIVE_BRIDGE2_COMMON_SRC_FILES := \
+  DummyNativeBridge2.cpp
+
+# Shared library for target
+# ========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:= libnativebridge2-dummy
+
+LOCAL_SRC_FILES:= $(NATIVE_BRIDGE2_COMMON_SRC_FILES)
+LOCAL_CLANG := true
+LOCAL_CFLAGS += -Werror -Wall
+LOCAL_CPPFLAGS := -std=gnu++11 -fvisibility=protected
+LOCAL_LDFLAGS := -ldl
+LOCAL_MULTILIB := both
+
+include $(BUILD_SHARED_LIBRARY)
+
+# Shared library for host
+# ========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:= libnativebridge2-dummy
+
+LOCAL_SRC_FILES:= $(NATIVE_BRIDGE2_COMMON_SRC_FILES)
+LOCAL_CLANG := true
+LOCAL_CFLAGS += -Werror -Wall
+LOCAL_CPPFLAGS := -std=gnu++11 -fvisibility=protected
+LOCAL_LDFLAGS := -ldl
+LOCAL_MULTILIB := both
+
+include $(BUILD_HOST_SHARED_LIBRARY)
diff --git a/libnativebridge/tests/DummyNativeBridge2.cpp b/libnativebridge/tests/DummyNativeBridge2.cpp
new file mode 100644
index 0000000..6920c74
--- /dev/null
+++ b/libnativebridge/tests/DummyNativeBridge2.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+// A dummy implementation of the native-bridge interface.
+
+#include "nativebridge/native_bridge.h"
+
+#include <signal.h>
+
+// NativeBridgeCallbacks implementations
+extern "C" bool native_bridge2_initialize(const android::NativeBridgeRuntimeCallbacks* /* art_cbs */,
+                                         const char* /* app_code_cache_dir */,
+                                         const char* /* isa */) {
+  return true;
+}
+
+extern "C" void* native_bridge2_loadLibrary(const char* /* libpath */, int /* flag */) {
+  return nullptr;
+}
+
+extern "C" void* native_bridge2_getTrampoline(void* /* handle */, const char* /* name */,
+                                             const char* /* shorty */, uint32_t /* len */) {
+  return nullptr;
+}
+
+extern "C" bool native_bridge2_isSupported(const char* /* libpath */) {
+  return false;
+}
+
+extern "C" const struct android::NativeBridgeRuntimeValues* native_bridge2_getAppEnv(
+    const char* /* abi */) {
+  return nullptr;
+}
+
+extern "C" bool native_bridge2_is_compatible_compatible_with(uint32_t version) {
+  // For testing, allow 1 and 2, but disallow 3+.
+  return version <= 2;
+}
+
+static bool native_bridge2_dummy_signal_handler(int, siginfo_t*, void*) {
+  // TODO: Implement something here. We'd either have to have a death test with a log here, or
+  //       we'd have to be able to resume after the faulting instruction...
+  return true;
+}
+
+extern "C" android::NativeBridgeSignalHandlerFn native_bridge2_get_signal_handler(int signal) {
+  if (signal == SIGSEGV) {
+    return &native_bridge2_dummy_signal_handler;
+  }
+  return nullptr;
+}
+
+android::NativeBridgeCallbacks NativeBridgeItf {
+  .version = 2,
+  .initialize = &native_bridge2_initialize,
+  .loadLibrary = &native_bridge2_loadLibrary,
+  .getTrampoline = &native_bridge2_getTrampoline,
+  .isSupported = &native_bridge2_isSupported,
+  .getAppEnv = &native_bridge2_getAppEnv,
+  .isCompatibleWith = &native_bridge2_is_compatible_compatible_with,
+  .getSignalHandler = &native_bridge2_get_signal_handler
+};
+
diff --git a/libnativebridge/tests/NativeBridge2Signal_test.cpp b/libnativebridge/tests/NativeBridge2Signal_test.cpp
new file mode 100644
index 0000000..44e45e3
--- /dev/null
+++ b/libnativebridge/tests/NativeBridge2Signal_test.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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 "NativeBridgeTest.h"
+
+#include <signal.h>
+#include <unistd.h>
+
+namespace android {
+
+constexpr const char* kNativeBridgeLibrary2 = "libnativebridge2-dummy.so";
+
+TEST_F(NativeBridgeTest, V2_Signal) {
+    // Init
+    ASSERT_TRUE(LoadNativeBridge(kNativeBridgeLibrary2, nullptr));
+    ASSERT_TRUE(NativeBridgeAvailable());
+    ASSERT_TRUE(PreInitializeNativeBridge(".", "isa"));
+    ASSERT_TRUE(NativeBridgeAvailable());
+    ASSERT_TRUE(InitializeNativeBridge(nullptr, nullptr));
+    ASSERT_TRUE(NativeBridgeAvailable());
+
+    ASSERT_EQ(2U, NativeBridgeGetVersion());
+    ASSERT_NE(nullptr, NativeBridgeGetSignalHandler(SIGSEGV));
+
+    // Clean-up code_cache
+    ASSERT_EQ(0, rmdir(kCodeCache));
+}
+
+}  // namespace android
diff --git a/libnativebridge/tests/NativeBridgeVersion_test.cpp b/libnativebridge/tests/NativeBridgeVersion_test.cpp
new file mode 100644
index 0000000..d3f9a80
--- /dev/null
+++ b/libnativebridge/tests/NativeBridgeVersion_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "NativeBridgeTest.h"
+
+#include <unistd.h>
+
+namespace android {
+
+TEST_F(NativeBridgeTest, Version) {
+    // When a bridge isn't loaded, we expect 0.
+    EXPECT_EQ(NativeBridgeGetVersion(), 0U);
+
+    // After our dummy bridge has been loaded, we expect 1.
+    ASSERT_TRUE(LoadNativeBridge(kNativeBridgeLibrary, nullptr));
+    EXPECT_EQ(NativeBridgeGetVersion(), 1U);
+
+    // Unload
+    UnloadNativeBridge();
+
+    // Version information is gone.
+    EXPECT_EQ(NativeBridgeGetVersion(), 0U);
+}
+
+}  // namespace android
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index c33dca6..8c0a0be 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -295,7 +295,8 @@
         ssize_t index = -1;
         while((index = next(index)) >= 0) {
             LogBufferElement *l = editEntryAt(index).getLast();
-            if ((l->getDropped() >= 4) && (current > l->getRealTime().nsec())) {
+            if ((l->getDropped() >= EXPIRE_THRESHOLD)
+                    && (current > l->getRealTime().nsec())) {
                 removeAt(index);
                 index = -1;
             }
@@ -387,6 +388,7 @@
         bool kick = false;
         bool leading = true;
         LogBufferElementLast last;
+        log_time start(log_time::EPOCH);
         for(it = mLogElements.begin(); it != mLogElements.end();) {
             LogBufferElement *e = *it;
 
@@ -446,11 +448,29 @@
             }
 
             if (e->getUid() != worst) {
+                if (start != log_time::EPOCH) {
+                    static const timespec too_old = {
+                        EXPIRE_HOUR_THRESHOLD * 60 * 60, 0
+                    };
+                    start = e->getRealTime() + too_old;
+                }
                 last.clear(e);
                 ++it;
                 continue;
             }
 
+            if ((start != log_time::EPOCH) && (e->getRealTime() > start)) {
+                // KISS. Really a heuristic rather than algorithmically strong,
+                // a crude mechanism, the following loops will move the oldest
+                // watermark possibly wiping out the extra EXPIRE_HOUR_THRESHOLD
+                // we just thought we were preserving. We count on the typical
+                // pruneRows of 10% of total not being a sledgehammer.
+                // A stronger algorithm would have us loop back to the top if
+                // we have worst-UID enabled and we start expiring messages
+                // below less than EXPIRE_HOUR_THRESHOLD old.
+                break;
+            }
+
             pruneRows--;
             if (pruneRows == 0) {
                 break;
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index 5dabaac..3dcf9d1 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -48,6 +48,11 @@
 
 class LogBuffer;
 
+#define EXPIRE_HOUR_THRESHOLD 24 // Only expire chatty UID logs to preserve
+                                 // non-chatty UIDs less than this age in hours
+#define EXPIRE_THRESHOLD 4       // A smaller expire count is considered too
+                                 // chatty for the temporal expire messages
+
 class LogBufferElement {
     const log_id_t mLogId;
     const uid_t mUid;
diff --git a/toolbox/Android.mk b/toolbox/Android.mk
index 5a58d19..ad99a39 100644
--- a/toolbox/Android.mk
+++ b/toolbox/Android.mk
@@ -45,6 +45,7 @@
     ioctl \
     ionice \
     log \
+    ls \
     lsof \
     mount \
     nandread \
diff --git a/toolbox/ls.c b/toolbox/ls.c
new file mode 100644
index 0000000..9a89dd4
--- /dev/null
+++ b/toolbox/ls.c
@@ -0,0 +1,588 @@
+#include <dirent.h>
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <selinux/selinux.h>
+
+// simple dynamic array of strings.
+typedef struct {
+    int count;
+    int capacity;
+    void** items;
+} strlist_t;
+
+#define STRLIST_INITIALIZER { 0, 0, NULL }
+
+/* Used to iterate over a strlist_t
+ * _list   :: pointer to strlist_t object
+ * _item   :: name of local variable name defined within the loop with
+ *            type 'char*'
+ * _stmnt  :: C statement executed in each iteration
+ *
+ * This macro is only intended for simple uses. Do not add or remove items
+ * to/from the list during iteration.
+ */
+#define  STRLIST_FOREACH(_list,_item,_stmnt) \
+    do { \
+        int _nn_##__LINE__ = 0; \
+        for (;_nn_##__LINE__ < (_list)->count; ++ _nn_##__LINE__) { \
+            char* _item = (char*)(_list)->items[_nn_##__LINE__]; \
+            _stmnt; \
+        } \
+    } while (0)
+
+static void dynarray_reserve_more( strlist_t *a, int count ) {
+    int old_cap = a->capacity;
+    int new_cap = old_cap;
+    const int max_cap = INT_MAX/sizeof(void*);
+    void** new_items;
+    int new_count = a->count + count;
+
+    if (count <= 0)
+        return;
+
+    if (count > max_cap - a->count)
+        abort();
+
+    new_count = a->count + count;
+
+    while (new_cap < new_count) {
+        old_cap = new_cap;
+        new_cap += (new_cap >> 2) + 4;
+        if (new_cap < old_cap || new_cap > max_cap) {
+            new_cap = max_cap;
+        }
+    }
+    new_items = realloc(a->items, new_cap*sizeof(void*));
+    if (new_items == NULL)
+        abort();
+
+    a->items = new_items;
+    a->capacity = new_cap;
+}
+
+void strlist_init( strlist_t *list ) {
+    list->count = list->capacity = 0;
+    list->items = NULL;
+}
+
+// append a new string made of the first 'slen' characters from 'str'
+// followed by a trailing zero.
+void strlist_append_b( strlist_t *list, const void* str, size_t  slen ) {
+    char *copy = malloc(slen+1);
+    memcpy(copy, str, slen);
+    copy[slen] = '\0';
+    if (list->count >= list->capacity)
+        dynarray_reserve_more(list, 1);
+    list->items[list->count++] = copy;
+}
+
+// append the copy of a given input string to a strlist_t.
+void strlist_append_dup( strlist_t *list, const char *str) {
+    strlist_append_b(list, str, strlen(str));
+}
+
+// note: strlist_done will free all the strings owned by the list.
+void strlist_done( strlist_t *list ) {
+    STRLIST_FOREACH(list, string, free(string));
+    free(list->items);
+    list->items = NULL;
+    list->count = list->capacity = 0;
+}
+
+static int strlist_compare_strings(const void* a, const void* b) {
+    const char *sa = *(const char **)a;
+    const char *sb = *(const char **)b;
+    return strcmp(sa, sb);
+}
+
+/* sort the strings in a given list (using strcmp) */
+void strlist_sort( strlist_t *list ) {
+    if (list->count > 0) {
+        qsort(list->items, (size_t)list->count, sizeof(void*), strlist_compare_strings);
+    }
+}
+
+
+// bits for flags argument
+#define LIST_LONG           (1 << 0)
+#define LIST_ALL            (1 << 1)
+#define LIST_RECURSIVE      (1 << 2)
+#define LIST_DIRECTORIES    (1 << 3)
+#define LIST_SIZE           (1 << 4)
+#define LIST_LONG_NUMERIC   (1 << 5)
+#define LIST_CLASSIFY       (1 << 6)
+#define LIST_MACLABEL       (1 << 7)
+#define LIST_INODE          (1 << 8)
+
+// fwd
+static int listpath(const char *name, int flags);
+
+static char mode2kind(mode_t mode)
+{
+    switch(mode & S_IFMT){
+    case S_IFSOCK: return 's';
+    case S_IFLNK: return 'l';
+    case S_IFREG: return '-';
+    case S_IFDIR: return 'd';
+    case S_IFBLK: return 'b';
+    case S_IFCHR: return 'c';
+    case S_IFIFO: return 'p';
+    default: return '?';
+    }
+}
+
+void strmode(mode_t mode, char *out)
+{
+    *out++ = mode2kind(mode);
+
+    *out++ = (mode & 0400) ? 'r' : '-';
+    *out++ = (mode & 0200) ? 'w' : '-';
+    if(mode & 04000) {
+        *out++ = (mode & 0100) ? 's' : 'S';
+    } else {
+        *out++ = (mode & 0100) ? 'x' : '-';
+    }
+    *out++ = (mode & 040) ? 'r' : '-';
+    *out++ = (mode & 020) ? 'w' : '-';
+    if(mode & 02000) {
+        *out++ = (mode & 010) ? 's' : 'S';
+    } else {
+        *out++ = (mode & 010) ? 'x' : '-';
+    }
+    *out++ = (mode & 04) ? 'r' : '-';
+    *out++ = (mode & 02) ? 'w' : '-';
+    if(mode & 01000) {
+        *out++ = (mode & 01) ? 't' : 'T';
+    } else {
+        *out++ = (mode & 01) ? 'x' : '-';
+    }
+    *out = 0;
+}
+
+static void user2str(uid_t uid, char *out, size_t out_size)
+{
+    struct passwd *pw = getpwuid(uid);
+    if(pw) {
+        strlcpy(out, pw->pw_name, out_size);
+    } else {
+        snprintf(out, out_size, "%d", uid);
+    }
+}
+
+static void group2str(gid_t gid, char *out, size_t out_size)
+{
+    struct group *gr = getgrgid(gid);
+    if(gr) {
+        strlcpy(out, gr->gr_name, out_size);
+    } else {
+        snprintf(out, out_size, "%d", gid);
+    }
+}
+
+static int show_total_size(const char *dirname, DIR *d, int flags)
+{
+    struct dirent *de;
+    char tmp[1024];
+    struct stat s;
+    int sum = 0;
+
+    /* run through the directory and sum up the file block sizes */
+    while ((de = readdir(d)) != 0) {
+        if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
+            continue;
+        if (de->d_name[0] == '.' && (flags & LIST_ALL) == 0)
+            continue;
+
+        if (strcmp(dirname, "/") == 0)
+            snprintf(tmp, sizeof(tmp), "/%s", de->d_name);
+        else
+            snprintf(tmp, sizeof(tmp), "%s/%s", dirname, de->d_name);
+
+        if (lstat(tmp, &s) < 0) {
+            fprintf(stderr, "stat failed on %s: %s\n", tmp, strerror(errno));
+            rewinddir(d);
+            return -1;
+        }
+
+        sum += s.st_blocks / 2;
+    }
+
+    printf("total %d\n", sum);
+    rewinddir(d);
+    return 0;
+}
+
+static int listfile_size(const char *path, const char *filename, struct stat *s,
+                         int flags)
+{
+    if(!s || !path) {
+        return -1;
+    }
+
+    /* blocks are 512 bytes, we want output to be KB */
+    if ((flags & LIST_SIZE) != 0) {
+        printf("%lld ", (long long)s->st_blocks / 2);
+    }
+
+    if ((flags & LIST_CLASSIFY) != 0) {
+        char filetype = mode2kind(s->st_mode);
+        if (filetype != 'l') {
+            printf("%c ", filetype);
+        } else {
+            struct stat link_dest;
+            if (!stat(path, &link_dest)) {
+                printf("l%c ", mode2kind(link_dest.st_mode));
+            } else {
+                fprintf(stderr, "stat '%s' failed: %s\n", path, strerror(errno));
+                printf("l? ");
+            }
+        }
+    }
+
+    printf("%s\n", filename);
+
+    return 0;
+}
+
+static int listfile_long(const char *path, struct stat *s, int flags)
+{
+    char date[32];
+    char mode[16];
+    char user[32];
+    char group[32];
+    const char *name;
+
+    if(!s || !path) {
+        return -1;
+    }
+
+    /* name is anything after the final '/', or the whole path if none*/
+    name = strrchr(path, '/');
+    if(name == 0) {
+        name = path;
+    } else {
+        name++;
+    }
+
+    strmode(s->st_mode, mode);
+    if (flags & LIST_LONG_NUMERIC) {
+        snprintf(user, sizeof(user), "%u", s->st_uid);
+        snprintf(group, sizeof(group), "%u", s->st_gid);
+    } else {
+        user2str(s->st_uid, user, sizeof(user));
+        group2str(s->st_gid, group, sizeof(group));
+    }
+
+    strftime(date, 32, "%Y-%m-%d %H:%M", localtime((const time_t*)&s->st_mtime));
+    date[31] = 0;
+
+// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+// MMMMMMMM UUUUUUUU GGGGGGGGG XXXXXXXX YYYY-MM-DD HH:MM NAME (->LINK)
+
+    switch(s->st_mode & S_IFMT) {
+    case S_IFBLK:
+    case S_IFCHR:
+        printf("%s %-8s %-8s %3d, %3d %s %s\n",
+               mode, user, group,
+               major(s->st_rdev), minor(s->st_rdev),
+               date, name);
+        break;
+    case S_IFREG:
+        printf("%s %-8s %-8s %8lld %s %s\n",
+               mode, user, group, (long long)s->st_size, date, name);
+        break;
+    case S_IFLNK: {
+        char linkto[256];
+        ssize_t len;
+
+        len = readlink(path, linkto, 256);
+        if(len < 0) return -1;
+
+        if(len > 255) {
+            linkto[252] = '.';
+            linkto[253] = '.';
+            linkto[254] = '.';
+            linkto[255] = 0;
+        } else {
+            linkto[len] = 0;
+        }
+
+        printf("%s %-8s %-8s          %s %s -> %s\n",
+               mode, user, group, date, name, linkto);
+        break;
+    }
+    default:
+        printf("%s %-8s %-8s          %s %s\n",
+               mode, user, group, date, name);
+
+    }
+    return 0;
+}
+
+static int listfile_maclabel(const char *path, struct stat *s)
+{
+    char mode[16];
+    char user[32];
+    char group[32];
+    char *maclabel = NULL;
+    const char *name;
+
+    if(!s || !path) {
+        return -1;
+    }
+
+    /* name is anything after the final '/', or the whole path if none*/
+    name = strrchr(path, '/');
+    if(name == 0) {
+        name = path;
+    } else {
+        name++;
+    }
+
+    lgetfilecon(path, &maclabel);
+    if (!maclabel) {
+        return -1;
+    }
+
+    strmode(s->st_mode, mode);
+    user2str(s->st_uid, user, sizeof(user));
+    group2str(s->st_gid, group, sizeof(group));
+
+    switch(s->st_mode & S_IFMT) {
+    case S_IFLNK: {
+        char linkto[256];
+        ssize_t len;
+
+        len = readlink(path, linkto, sizeof(linkto));
+        if(len < 0) return -1;
+
+        if((size_t)len > sizeof(linkto)-1) {
+            linkto[sizeof(linkto)-4] = '.';
+            linkto[sizeof(linkto)-3] = '.';
+            linkto[sizeof(linkto)-2] = '.';
+            linkto[sizeof(linkto)-1] = 0;
+        } else {
+            linkto[len] = 0;
+        }
+
+        printf("%s %-8s %-8s          %s %s -> %s\n",
+               mode, user, group, maclabel, name, linkto);
+        break;
+    }
+    default:
+        printf("%s %-8s %-8s          %s %s\n",
+               mode, user, group, maclabel, name);
+
+    }
+
+    free(maclabel);
+
+    return 0;
+}
+
+static int listfile(const char *dirname, const char *filename, int flags)
+{
+    struct stat s;
+
+    if ((flags & (LIST_LONG | LIST_SIZE | LIST_CLASSIFY | LIST_MACLABEL | LIST_INODE)) == 0) {
+        printf("%s\n", filename);
+        return 0;
+    }
+
+    char tmp[4096];
+    const char* pathname = filename;
+
+    if (dirname != NULL) {
+        snprintf(tmp, sizeof(tmp), "%s/%s", dirname, filename);
+        pathname = tmp;
+    } else {
+        pathname = filename;
+    }
+
+    if(lstat(pathname, &s) < 0) {
+        fprintf(stderr, "lstat '%s' failed: %s\n", pathname, strerror(errno));
+        return -1;
+    }
+
+    if(flags & LIST_INODE) {
+        printf("%8llu ", (unsigned long long)s.st_ino);
+    }
+
+    if ((flags & LIST_MACLABEL) != 0) {
+        return listfile_maclabel(pathname, &s);
+    } else if ((flags & LIST_LONG) != 0) {
+        return listfile_long(pathname, &s, flags);
+    } else /*((flags & LIST_SIZE) != 0)*/ {
+        return listfile_size(pathname, filename, &s, flags);
+    }
+}
+
+static int listdir(const char *name, int flags)
+{
+    char tmp[4096];
+    DIR *d;
+    struct dirent *de;
+    strlist_t  files = STRLIST_INITIALIZER;
+
+    d = opendir(name);
+    if(d == 0) {
+        fprintf(stderr, "opendir failed, %s\n", strerror(errno));
+        return -1;
+    }
+
+    if ((flags & LIST_SIZE) != 0) {
+        show_total_size(name, d, flags);
+    }
+
+    while((de = readdir(d)) != 0){
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue;
+        if(de->d_name[0] == '.' && (flags & LIST_ALL) == 0) continue;
+
+        strlist_append_dup(&files, de->d_name);
+    }
+
+    strlist_sort(&files);
+    STRLIST_FOREACH(&files, filename, listfile(name, filename, flags));
+    strlist_done(&files);
+
+    if (flags & LIST_RECURSIVE) {
+        strlist_t subdirs = STRLIST_INITIALIZER;
+
+        rewinddir(d);
+
+        while ((de = readdir(d)) != 0) {
+            struct stat s;
+            int err;
+
+            if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+                continue;
+            if (de->d_name[0] == '.' && (flags & LIST_ALL) == 0)
+                continue;
+
+            if (!strcmp(name, "/"))
+                snprintf(tmp, sizeof(tmp), "/%s", de->d_name);
+            else
+                snprintf(tmp, sizeof(tmp), "%s/%s", name, de->d_name);
+
+            /*
+             * If the name ends in a '/', use stat() so we treat it like a
+             * directory even if it's a symlink.
+             */
+            if (tmp[strlen(tmp)-1] == '/')
+                err = stat(tmp, &s);
+            else
+                err = lstat(tmp, &s);
+
+            if (err < 0) {
+                perror(tmp);
+                closedir(d);
+                return -1;
+            }
+
+            if (S_ISDIR(s.st_mode)) {
+                strlist_append_dup(&subdirs, tmp);
+            }
+        }
+        strlist_sort(&subdirs);
+        STRLIST_FOREACH(&subdirs, path, {
+            printf("\n%s:\n", path);
+            listdir(path, flags);
+        });
+        strlist_done(&subdirs);
+    }
+
+    closedir(d);
+    return 0;
+}
+
+static int listpath(const char *name, int flags)
+{
+    struct stat s;
+    int err;
+
+    /*
+     * If the name ends in a '/', use stat() so we treat it like a
+     * directory even if it's a symlink.
+     */
+    if (name[strlen(name)-1] == '/')
+        err = stat(name, &s);
+    else
+        err = lstat(name, &s);
+
+    if (err < 0) {
+        perror(name);
+        return -1;
+    }
+
+    if ((flags & LIST_DIRECTORIES) == 0 && S_ISDIR(s.st_mode)) {
+        if (flags & LIST_RECURSIVE)
+            printf("\n%s:\n", name);
+        return listdir(name, flags);
+    } else {
+        /* yeah this calls stat() again*/
+        return listfile(NULL, name, flags);
+    }
+}
+
+int ls_main(int argc, char **argv)
+{
+    int flags = 0;
+
+    if(argc > 1) {
+        int i;
+        int err = 0;
+        strlist_t  files = STRLIST_INITIALIZER;
+
+        for (i = 1; i < argc; i++) {
+            if (argv[i][0] == '-') {
+                /* an option ? */
+                const char *arg = argv[i]+1;
+                while (arg[0]) {
+                    switch (arg[0]) {
+                    case 'l': flags |= LIST_LONG; break;
+                    case 'n': flags |= LIST_LONG | LIST_LONG_NUMERIC; break;
+                    case 's': flags |= LIST_SIZE; break;
+                    case 'R': flags |= LIST_RECURSIVE; break;
+                    case 'd': flags |= LIST_DIRECTORIES; break;
+                    case 'Z': flags |= LIST_MACLABEL; break;
+                    case 'a': flags |= LIST_ALL; break;
+                    case 'F': flags |= LIST_CLASSIFY; break;
+                    case 'i': flags |= LIST_INODE; break;
+                    default:
+                        fprintf(stderr, "%s: Unknown option '-%c'. Aborting.\n", "ls", arg[0]);
+                        exit(1);
+                    }
+                    arg++;
+                }
+            } else {
+                /* not an option ? */
+                strlist_append_dup(&files, argv[i]);
+            }
+        }
+
+        if (files.count > 0) {
+            STRLIST_FOREACH(&files, path, {
+                if (listpath(path, flags) != 0) {
+                    err = EXIT_FAILURE;
+                }
+            });
+            strlist_done(&files);
+            return err;
+        }
+    }
+
+    // list working directory if no files or directories were specified
+    return listpath(".", flags);
+}