Merge "Add a specialized LocalUnwinder object."
diff --git a/adb/Android.bp b/adb/Android.bp
index 99de54e..1f41e4f 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -122,6 +122,7 @@
     "sysdeps_test.cpp",
     "sysdeps/stat_test.cpp",
     "transport_test.cpp",
+    "types_test.cpp",
 ]
 
 cc_library_host_static {
diff --git a/adb/adb_unique_fd.h b/adb/adb_unique_fd.h
index 9c02cbe..d1dc9d1 100644
--- a/adb/adb_unique_fd.h
+++ b/adb/adb_unique_fd.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <errno.h>
 #include <unistd.h>
 
 #include <android-base/unique_fd.h>
@@ -28,11 +29,42 @@
 using unique_fd = android::base::unique_fd_impl<AdbCloser>;
 
 #if !defined(_WIN32)
-inline bool Pipe(unique_fd* read, unique_fd* write) {
+inline bool Pipe(unique_fd* read, unique_fd* write, int flags = 0) {
     int pipefd[2];
+#if !defined(__APPLE__)
+    if (pipe2(pipefd, flags) != 0) {
+        return false;
+    }
+#else
+    // Darwin doesn't have pipe2. Implement it ourselves.
+    if (flags != 0 && (flags & ~(O_CLOEXEC | O_NONBLOCK)) != 0) {
+        errno = EINVAL;
+        return false;
+    }
+
     if (pipe(pipefd) != 0) {
         return false;
     }
+
+    if (flags & O_CLOEXEC) {
+        if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 ||
+            fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
+            close(pipefd[0]);
+            close(pipefd[1]);
+            return false;
+        }
+    }
+
+    if (flags & O_NONBLOCK) {
+        if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 ||
+            fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) {
+            close(pipefd[0]);
+            close(pipefd[1]);
+            return false;
+        }
+    }
+#endif
+
     read->reset(pipefd[0]);
     write->reset(pipefd[1]);
     return true;
diff --git a/adb/benchmark_device.py b/adb/benchmark_device.py
new file mode 100755
index 0000000..00c2315
--- /dev/null
+++ b/adb/benchmark_device.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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.
+#
+
+import os
+import statistics
+import time
+
+import adb
+
+def lock_min(device):
+    device.shell_nocheck(["""
+        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
+            echo userspace > $x/scaling_governor
+            cat $x/scaling_min_freq > $x/scaling_setspeed
+        done
+    """])
+
+def lock_max(device):
+    device.shell_nocheck(["""
+        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
+            echo userspace > $x/scaling_governor
+            cat $x/scaling_max_freq > $x/scaling_setspeed
+        done
+    """])
+
+def unlock(device):
+    device.shell_nocheck(["""
+        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
+            echo ondemand > $x/scaling_governor
+            echo sched > $x/scaling_governor
+            echo schedutil > $x/scaling_governor
+        done
+    """])
+
+def harmonic_mean(xs):
+    return 1.0 / statistics.mean([1.0 / x for x in xs])
+
+def analyze(name, speeds):
+    median = statistics.median(speeds)
+    mean = harmonic_mean(speeds)
+    stddev = statistics.stdev(speeds)
+    msg = "%s: %d runs: median %.2f MiB/s, mean %.2f MiB/s, stddev: %.2f MiB/s"
+    print(msg % (name, len(speeds), median, mean, stddev))
+
+def benchmark_push(device=None, file_size_mb=100):
+    if device == None:
+        device = adb.get_device()
+
+    lock_max(device)
+
+    remote_path = "/dev/null"
+    local_path = "/tmp/adb_benchmark_temp"
+
+    with open(local_path, "wb") as f:
+        f.truncate(file_size_mb * 1024 * 1024)
+
+    speeds = list()
+    for _ in range(0, 5):
+        begin = time.time()
+        device.push(local=local_path, remote=remote_path)
+        end = time.time()
+        speeds.append(file_size_mb / float(end - begin))
+
+    analyze("push %dMiB" % file_size_mb, speeds)
+
+def benchmark_pull(device=None, file_size_mb=100):
+    if device == None:
+        device = adb.get_device()
+
+    lock_max(device)
+
+    remote_path = "/data/local/tmp/adb_benchmark_temp"
+    local_path = "/tmp/adb_benchmark_temp"
+
+    device.shell(["dd", "if=/dev/zero", "of=" + remote_path, "bs=1m",
+                  "count=" + str(file_size_mb)])
+    speeds = list()
+    for _ in range(0, 5):
+        begin = time.time()
+        device.pull(remote=remote_path, local=local_path)
+        end = time.time()
+        speeds.append(file_size_mb / float(end - begin))
+
+    analyze("pull %dMiB" % file_size_mb, speeds)
+
+def benchmark_shell(device=None, file_size_mb=100):
+    if device == None:
+        device = adb.get_device()
+
+    lock_max(device)
+
+    speeds = list()
+    for _ in range(0, 5):
+        begin = time.time()
+        device.shell(["dd", "if=/dev/zero", "bs=1m",
+                      "count=" + str(file_size_mb)])
+        end = time.time()
+        speeds.append(file_size_mb / float(end - begin))
+
+    analyze("shell %dMiB" % file_size_mb, speeds)
+
+def main():
+    benchmark_pull()
+
+if __name__ == "__main__":
+    main()
diff --git a/adb/socket.h b/adb/socket.h
index 27e5b05..0905aab 100644
--- a/adb/socket.h
+++ b/adb/socket.h
@@ -62,7 +62,7 @@
     int fd = -1;
 
     // queue of data waiting to be written
-    std::deque<Range> packet_queue;
+    IOVector packet_queue;
 
     std::string smart_socket_data;
 
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 9a6dcbe..de3215d 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -113,14 +113,14 @@
 };
 
 static SocketFlushResult local_socket_flush_incoming(asocket* s) {
-    while (!s->packet_queue.empty()) {
-        Range& r = s->packet_queue.front();
-
-        int rc = adb_write(s->fd, r.data(), r.size());
-        if (rc == static_cast<int>(r.size())) {
-            s->packet_queue.pop_front();
+    if (!s->packet_queue.empty()) {
+        std::vector<adb_iovec> iov = s->packet_queue.iovecs();
+        ssize_t rc = adb_writev(s->fd, iov.data(), iov.size());
+        if (rc > 0 && static_cast<size_t>(rc) == s->packet_queue.size()) {
+            s->packet_queue.clear();
         } else if (rc > 0) {
-            r.drop_front(rc);
+            // TODO: Implement a faster drop_front?
+            s->packet_queue.take_front(rc);
             fdevent_add(s->fde, FDE_WRITE);
             return SocketFlushResult::TryAgain;
         } else if (rc == -1 && errno == EAGAIN) {
@@ -130,7 +130,6 @@
             // We failed to write, but it's possible that we can still read from the socket.
             // Give that a try before giving up.
             s->has_write_error = true;
-            break;
         }
     }
 
@@ -217,8 +216,7 @@
 static int local_socket_enqueue(asocket* s, apacket::payload_type data) {
     D("LS(%d): enqueue %zu", s->id, data.size());
 
-    Range r(std::move(data));
-    s->packet_queue.push_back(std::move(r));
+    s->packet_queue.append(std::move(data));
     switch (local_socket_flush_incoming(s)) {
         case SocketFlushResult::Destroyed:
             return -1;
@@ -622,7 +620,7 @@
     D("SS(%d): enqueue %zu", s->id, data.size());
 
     if (s->smart_socket_data.empty()) {
-        // TODO: Make this a BlockChain?
+        // TODO: Make this an IOVector?
         s->smart_socket_data.assign(data.begin(), data.end());
     } else {
         std::copy(data.begin(), data.end(), std::back_inserter(s->smart_socket_data));
diff --git a/adb/types.h b/adb/types.h
index dd3e063..c6b3f07 100644
--- a/adb/types.h
+++ b/adb/types.h
@@ -17,11 +17,15 @@
 #pragma once
 
 #include <algorithm>
+#include <deque>
+#include <type_traits>
 #include <utility>
+#include <vector>
 
 #include <android-base/logging.h>
 
 #include "sysdeps/memory.h"
+#include "sysdeps/uio.h"
 
 // Essentially std::vector<char>, except without zero initialization or reallocation.
 struct Block {
@@ -130,34 +134,205 @@
     payload_type payload;
 };
 
-struct Range {
-    explicit Range(apacket::payload_type data) : data_(std::move(data)) {}
+struct IOVector {
+    using value_type = char;
+    using block_type = Block;
+    using size_type = size_t;
 
-    Range(const Range& copy) = delete;
-    Range& operator=(const Range& copy) = delete;
+    IOVector() {}
 
-    Range(Range&& move) = default;
-    Range& operator=(Range&& move) = default;
+    explicit IOVector(std::unique_ptr<block_type> block) {
+        append(std::move(block));
+    }
 
-    size_t size() const { return data_.size() - begin_offset_ - end_offset_; };
+    IOVector(const IOVector& copy) = delete;
+    IOVector(IOVector&& move) : IOVector() {
+        *this = std::move(move);
+    }
+
+    IOVector& operator=(const IOVector& copy) = delete;
+    IOVector& operator=(IOVector&& move) {
+        chain_ = std::move(move.chain_);
+        chain_length_ = move.chain_length_;
+        begin_offset_ = move.begin_offset_;
+        end_offset_ = move.end_offset_;
+
+        move.chain_.clear();
+        move.chain_length_ = 0;
+        move.begin_offset_ = 0;
+        move.end_offset_ = 0;
+
+        return *this;
+    }
+
+    size_type size() const { return chain_length_ - begin_offset_ - end_offset_; }
     bool empty() const { return size() == 0; }
 
-    void drop_front(size_t n) {
-        CHECK_GE(size(), n);
-        begin_offset_ += n;
+    void clear() {
+        chain_length_ = 0;
+        begin_offset_ = 0;
+        end_offset_ = 0;
+        chain_.clear();
     }
 
-    void drop_end(size_t n) {
-        CHECK_GE(size(), n);
-        end_offset_ += n;
+    // Split the first |len| bytes out of this chain into its own.
+    IOVector take_front(size_type len) {
+        IOVector head;
+
+        if (len == 0) {
+            return head;
+        }
+        CHECK_GE(size(), len);
+
+        std::shared_ptr<const block_type> first_block = chain_.front();
+        CHECK_GE(first_block->size(), begin_offset_);
+        head.append_shared(std::move(first_block));
+        head.begin_offset_ = begin_offset_;
+
+        while (head.size() < len) {
+            pop_front_block();
+            CHECK(!chain_.empty());
+
+            head.append_shared(chain_.front());
+        }
+
+        if (head.size() == len) {
+            // Head takes full ownership of the last block it took.
+            head.end_offset_ = 0;
+            begin_offset_ = 0;
+            pop_front_block();
+        } else {
+            // Head takes partial ownership of the last block it took.
+            size_t bytes_taken = head.size() - len;
+            head.end_offset_ = bytes_taken;
+            CHECK_GE(chain_.front()->size(), bytes_taken);
+            begin_offset_ = chain_.front()->size() - bytes_taken;
+        }
+
+        return head;
     }
 
-    char* data() { return &data_[0] + begin_offset_; }
+    // Add a nonempty block to the chain.
+    // The end of the chain must be a complete block (i.e. end_offset_ == 0).
+    void append(std::unique_ptr<const block_type> block) {
+        CHECK_NE(0ULL, block->size());
+        CHECK_EQ(0ULL, end_offset_);
+        chain_length_ += block->size();
+        chain_.emplace_back(std::move(block));
+    }
 
-    apacket::payload_type::iterator begin() { return data_.begin() + begin_offset_; }
-    apacket::payload_type::iterator end() { return data_.end() - end_offset_; }
+    void append(block_type&& block) { append(std::make_unique<block_type>(std::move(block))); }
 
-    apacket::payload_type data_;
+    void trim_front() {
+        if (begin_offset_ == 0) {
+            return;
+        }
+
+        const block_type* first_block = chain_.front().get();
+        auto copy = std::make_unique<block_type>(first_block->size() - begin_offset_);
+        memcpy(copy->data(), first_block->data() + begin_offset_, copy->size());
+        chain_.front() = std::move(copy);
+
+        chain_length_ -= begin_offset_;
+        begin_offset_ = 0;
+    }
+
+  private:
+    // append, except takes a shared_ptr.
+    // Private to prevent exterior mutation of blocks.
+    void append_shared(std::shared_ptr<const block_type> block) {
+        CHECK_NE(0ULL, block->size());
+        CHECK_EQ(0ULL, end_offset_);
+        chain_length_ += block->size();
+        chain_.emplace_back(std::move(block));
+    }
+
+    // Drop the front block from the chain, and update chain_length_ appropriately.
+    void pop_front_block() {
+        chain_length_ -= chain_.front()->size();
+        begin_offset_ = 0;
+        chain_.pop_front();
+    }
+
+    // Iterate over the blocks with a callback with an operator()(const char*, size_t).
+    template <typename Fn>
+    void iterate_blocks(Fn&& callback) const {
+        if (chain_.size() == 0) {
+            return;
+        }
+
+        for (size_t i = 0; i < chain_.size(); ++i) {
+            const std::shared_ptr<const block_type>& block = chain_.at(i);
+            const char* begin = block->data();
+            size_t length = block->size();
+
+            // Note that both of these conditions can be true if there's only one block.
+            if (i == 0) {
+                CHECK_GE(block->size(), begin_offset_);
+                begin += begin_offset_;
+                length -= begin_offset_;
+            }
+
+            if (i == chain_.size() - 1) {
+                CHECK_GE(length, end_offset_);
+                length -= end_offset_;
+            }
+
+            callback(begin, length);
+        }
+    }
+
+  public:
+    // Copy all of the blocks into a single block.
+    template <typename CollectionType = block_type>
+    CollectionType coalesce() const {
+        CollectionType result;
+        if (size() == 0) {
+            return result;
+        }
+
+        result.resize(size());
+
+        size_t offset = 0;
+        iterate_blocks([&offset, &result](const char* data, size_t len) {
+            memcpy(&result[offset], data, len);
+            offset += len;
+        });
+
+        return result;
+    }
+
+    template <typename FunctionType>
+    auto coalesced(FunctionType&& f) const ->
+        typename std::result_of<FunctionType(const char*, size_t)>::type {
+        if (chain_.size() == 1) {
+            // If we only have one block, we can use it directly.
+            return f(chain_.front()->data() + begin_offset_, size());
+        } else {
+            // Otherwise, copy to a single block.
+            auto data = coalesce();
+            return f(data.data(), data.size());
+        }
+    }
+
+    // Get a list of iovecs that can be used to write out all of the blocks.
+    std::vector<adb_iovec> iovecs() const {
+        std::vector<adb_iovec> result;
+        iterate_blocks([&result](const char* data, size_t len) {
+            adb_iovec iov;
+            iov.iov_base = const_cast<char*>(data);
+            iov.iov_len = len;
+            result.emplace_back(iov);
+        });
+
+        return result;
+    }
+
+  private:
+    // Total length of all of the blocks in the chain.
+    size_t chain_length_ = 0;
+
     size_t begin_offset_ = 0;
     size_t end_offset_ = 0;
+    std::deque<std::shared_ptr<const block_type>> chain_;
 };
diff --git a/adb/types_test.cpp b/adb/types_test.cpp
new file mode 100644
index 0000000..31ab90a
--- /dev/null
+++ b/adb/types_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 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 <gtest/gtest.h>
+
+#include "sysdeps/memory.h"
+#include "types.h"
+
+static std::unique_ptr<IOVector::block_type> create_block(const std::string& string) {
+    return std::make_unique<IOVector::block_type>(string.begin(), string.end());
+}
+
+static std::unique_ptr<IOVector::block_type> create_block(char value, size_t len) {
+    auto block = std::make_unique<IOVector::block_type>();
+    block->resize(len);
+    memset(&(*block)[0], value, len);
+    return block;
+}
+
+template <typename T>
+static std::unique_ptr<IOVector::block_type> copy_block(T&& block) {
+    auto copy = std::make_unique<IOVector::block_type>();
+    copy->assign(block->begin(), block->end());
+    return copy;
+}
+
+TEST(IOVector, empty) {
+    // Empty IOVector.
+    IOVector bc;
+    CHECK_EQ(0ULL, bc.coalesce().size());
+}
+
+TEST(IOVector, single_block) {
+    // A single block.
+    auto block = create_block('x', 100);
+    IOVector bc;
+    bc.append(copy_block(block));
+    ASSERT_EQ(100ULL, bc.size());
+    auto coalesced = bc.coalesce();
+    ASSERT_EQ(*block, coalesced);
+}
+
+TEST(IOVector, single_block_split) {
+    // One block split.
+    IOVector bc;
+    bc.append(create_block("foobar"));
+    IOVector foo = bc.take_front(3);
+    ASSERT_EQ(3ULL, foo.size());
+    ASSERT_EQ(3ULL, bc.size());
+    ASSERT_EQ(*create_block("foo"), foo.coalesce());
+    ASSERT_EQ(*create_block("bar"), bc.coalesce());
+}
+
+TEST(IOVector, aligned_split) {
+    IOVector bc;
+    bc.append(create_block("foo"));
+    bc.append(create_block("bar"));
+    bc.append(create_block("baz"));
+    ASSERT_EQ(9ULL, bc.size());
+
+    IOVector foo = bc.take_front(3);
+    ASSERT_EQ(3ULL, foo.size());
+    ASSERT_EQ(*create_block("foo"), foo.coalesce());
+
+    IOVector bar = bc.take_front(3);
+    ASSERT_EQ(3ULL, bar.size());
+    ASSERT_EQ(*create_block("bar"), bar.coalesce());
+
+    IOVector baz = bc.take_front(3);
+    ASSERT_EQ(3ULL, baz.size());
+    ASSERT_EQ(*create_block("baz"), baz.coalesce());
+
+    ASSERT_EQ(0ULL, bc.size());
+}
+
+TEST(IOVector, misaligned_split) {
+    IOVector bc;
+    bc.append(create_block("foo"));
+    bc.append(create_block("bar"));
+    bc.append(create_block("baz"));
+    bc.append(create_block("qux"));
+    bc.append(create_block("quux"));
+
+    // Aligned left, misaligned right, across multiple blocks.
+    IOVector foob = bc.take_front(4);
+    ASSERT_EQ(4ULL, foob.size());
+    ASSERT_EQ(*create_block("foob"), foob.coalesce());
+
+    // Misaligned left, misaligned right, in one block.
+    IOVector a = bc.take_front(1);
+    ASSERT_EQ(1ULL, a.size());
+    ASSERT_EQ(*create_block("a"), a.coalesce());
+
+    // Misaligned left, misaligned right, across two blocks.
+    IOVector rba = bc.take_front(3);
+    ASSERT_EQ(3ULL, rba.size());
+    ASSERT_EQ(*create_block("rba"), rba.coalesce());
+
+    // Misaligned left, misaligned right, across three blocks.
+    IOVector zquxquu = bc.take_front(7);
+    ASSERT_EQ(7ULL, zquxquu.size());
+    ASSERT_EQ(*create_block("zquxquu"), zquxquu.coalesce());
+
+    ASSERT_EQ(1ULL, bc.size());
+    ASSERT_EQ(*create_block("x"), bc.coalesce());
+}
diff --git a/base/include/android-base/logging.h b/base/include/android-base/logging.h
index cc7aaf6..05a12e7 100644
--- a/base/include/android-base/logging.h
+++ b/base/include/android-base/logging.h
@@ -100,8 +100,17 @@
                                        unsigned int, const char*)>;
 using AbortFunction = std::function<void(const char*)>;
 
+// Loggers for use with InitLogging/SetLogger.
+
+// Log to the kernel log (dmesg).
 void KernelLogger(LogId, LogSeverity, const char*, const char*, unsigned int, const char*);
+// Log to stderr in the full logcat format (with pid/tid/time/tag details).
 void StderrLogger(LogId, LogSeverity, const char*, const char*, unsigned int, const char*);
+// Log just the message to stdout/stderr (without pid/tid/time/tag details).
+// The choice of stdout versus stderr is based on the severity.
+// Errors are also prefixed by the program name (as with err(3)/error(3)).
+// Useful for replacing printf(3)/perror(3)/err(3)/error(3) in command-line tools.
+void StdioLogger(LogId, LogSeverity, const char*, const char*, unsigned int, const char*);
 
 void DefaultAborter(const char* abort_message);
 
diff --git a/base/include/android-base/test_utils.h b/base/include/android-base/test_utils.h
index b95fa07..b29676f 100644
--- a/base/include/android-base/test_utils.h
+++ b/base/include/android-base/test_utils.h
@@ -58,21 +58,33 @@
   DISALLOW_COPY_AND_ASSIGN(TemporaryDir);
 };
 
-class CapturedStderr {
+class CapturedStdFd {
  public:
-  CapturedStderr();
-  ~CapturedStderr();
+  CapturedStdFd(int std_fd);
+  ~CapturedStdFd();
 
   int fd() const;
+  std::string str();
 
  private:
-  void init();
-  void reset();
+  void Init();
+  void Reset();
 
   TemporaryFile temp_file_;
-  int old_stderr_;
+  int std_fd_;
+  int old_fd_;
 
-  DISALLOW_COPY_AND_ASSIGN(CapturedStderr);
+  DISALLOW_COPY_AND_ASSIGN(CapturedStdFd);
+};
+
+class CapturedStderr : public CapturedStdFd {
+ public:
+  CapturedStderr() : CapturedStdFd(STDERR_FILENO) {}
+};
+
+class CapturedStdout : public CapturedStdFd {
+ public:
+  CapturedStdout() : CapturedStdFd(STDOUT_FILENO) {}
 };
 
 #define ASSERT_MATCH(str, pattern)                                             \
diff --git a/base/logging.cpp b/base/logging.cpp
index a33da22..978d56d 100644
--- a/base/logging.cpp
+++ b/base/logging.cpp
@@ -212,6 +212,16 @@
           timestamp, getpid(), GetThreadId(), file, line, message);
 }
 
+void StdioLogger(LogId, LogSeverity severity, const char* /*tag*/, const char* /*file*/,
+                 unsigned int /*line*/, const char* message) {
+  if (severity >= WARNING) {
+    fflush(stdout);
+    fprintf(stderr, "%s: %s\n", getprogname(), message);
+  } else {
+    fprintf(stdout, "%s\n", message);
+  }
+}
+
 void DefaultAborter(const char* abort_message) {
 #ifdef __ANDROID__
   android_set_abort_message(abort_message);
diff --git a/base/logging_test.cpp b/base/logging_test.cpp
index 5f689fa..75b4ea0 100644
--- a/base/logging_test.cpp
+++ b/base/logging_test.cpp
@@ -206,11 +206,9 @@
 }
 #endif
 
-static void CheckMessage(const CapturedStderr& cap, android::base::LogSeverity severity,
+static void CheckMessage(CapturedStderr& cap, android::base::LogSeverity severity,
                          const char* expected, const char* expected_tag = nullptr) {
-  std::string output;
-  ASSERT_EQ(0, lseek(cap.fd(), 0, SEEK_SET));
-  android::base::ReadFdToString(cap.fd(), &output);
+  std::string output = cap.str();
 
   // We can't usefully check the output of any of these on Windows because we
   // don't have std::regex, but we can at least make sure we printed at least as
@@ -623,3 +621,23 @@
   }
   CheckMessage(cap, android::base::LogSeverity::INFO, expected_msg, expected_tag);
 }
+
+TEST(logging, StdioLogger) {
+  std::string err_str;
+  std::string out_str;
+  {
+    CapturedStderr cap_err;
+    CapturedStdout cap_out;
+    android::base::SetLogger(android::base::StdioLogger);
+    LOG(INFO) << "out";
+    LOG(ERROR) << "err";
+    err_str = cap_err.str();
+    out_str = cap_out.str();
+  }
+
+  // For INFO we expect just the literal "out\n".
+  ASSERT_EQ("out\n", out_str) << out_str;
+  // Whereas ERROR logging includes the program name.
+  ASSERT_EQ(android::base::Basename(android::base::GetExecutablePath()) + ": err\n", err_str)
+      << err_str;
+}
diff --git a/base/test_utils.cpp b/base/test_utils.cpp
index 1619c21..5096369 100644
--- a/base/test_utils.cpp
+++ b/base/test_utils.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include "android-base/logging.h"
 #include "android-base/test_utils.h"
 
 #include <fcntl.h>
@@ -33,6 +32,9 @@
 
 #include <string>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
 #ifdef _WIN32
 int mkstemp(char* template_name) {
   if (_mktemp(template_name) == nullptr) {
@@ -123,31 +125,38 @@
   return (mkdtemp(path) != nullptr);
 }
 
-CapturedStderr::CapturedStderr() : old_stderr_(-1) {
-  init();
+CapturedStdFd::CapturedStdFd(int std_fd) : std_fd_(std_fd), old_fd_(-1) {
+  Init();
 }
 
-CapturedStderr::~CapturedStderr() {
-  reset();
+CapturedStdFd::~CapturedStdFd() {
+  Reset();
 }
 
-int CapturedStderr::fd() const {
+int CapturedStdFd::fd() const {
   return temp_file_.fd;
 }
 
-void CapturedStderr::init() {
+std::string CapturedStdFd::str() {
+  std::string result;
+  CHECK_EQ(0, TEMP_FAILURE_RETRY(lseek(fd(), 0, SEEK_SET)));
+  android::base::ReadFdToString(fd(), &result);
+  return result;
+}
+
+void CapturedStdFd::Init() {
 #if defined(_WIN32)
   // On Windows, stderr is often buffered, so make sure it is unbuffered so
   // that we can immediately read back what was written to stderr.
-  CHECK_EQ(0, setvbuf(stderr, NULL, _IONBF, 0));
+  if (std_fd_ == STDERR_FILENO) CHECK_EQ(0, setvbuf(stderr, NULL, _IONBF, 0));
 #endif
-  old_stderr_ = dup(STDERR_FILENO);
-  CHECK_NE(-1, old_stderr_);
-  CHECK_NE(-1, dup2(fd(), STDERR_FILENO));
+  old_fd_ = dup(std_fd_);
+  CHECK_NE(-1, old_fd_);
+  CHECK_NE(-1, dup2(fd(), std_fd_));
 }
 
-void CapturedStderr::reset() {
-  CHECK_NE(-1, dup2(old_stderr_, STDERR_FILENO));
-  CHECK_EQ(0, close(old_stderr_));
+void CapturedStdFd::Reset() {
+  CHECK_NE(-1, dup2(old_fd_, std_fd_));
+  CHECK_EQ(0, close(old_fd_));
   // Note: cannot restore prior setvbuf() setting.
 }
diff --git a/init/host_init_parser.cpp b/init/host_init_parser.cpp
index 5232b7e..df497ea 100644
--- a/init/host_init_parser.cpp
+++ b/init/host_init_parser.cpp
@@ -48,7 +48,7 @@
 #include "generated_stub_builtin_function_map.h"
 
 int main(int argc, char** argv) {
-    android::base::InitLogging(argv, &android::base::StderrLogger);
+    android::base::InitLogging(argv, &android::base::StdioLogger);
     if (argc != 2) {
         LOG(ERROR) << "Usage: " << argv[0] << " <init file to parse>";
         return -1;
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 741fde0..d1c427d 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -95,6 +95,11 @@
 
 void CreateSerializedPropertyInfo();
 
+struct PropertyAuditData {
+    const ucred* cr;
+    const char* name;
+};
+
 void property_init() {
     mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
     CreateSerializedPropertyInfo();
@@ -111,7 +116,7 @@
         return false;
     }
 
-    property_audit_data audit_data;
+    PropertyAuditData audit_data;
 
     audit_data.name = name.c_str();
     audit_data.cr = &cr;
@@ -393,6 +398,35 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(SocketConnection);
 };
 
+bool CheckControlPropertyPerms(const std::string& name, const std::string& value,
+                               const std::string& source_context, const ucred& cr) {
+    // We check the legacy method first but these properties are dontaudit, so we only log an audit
+    // if the newer method fails as well.  We only do this with the legacy ctl. properties.
+    if (name == "ctl.start" || name == "ctl.stop" || name == "ctl.restart") {
+        // The legacy permissions model is that ctl. properties have their name ctl.<action> and
+        // their value is the name of the service to apply that action to.  Permissions for these
+        // actions are based on the service, so we must create a fake name of ctl.<service> to
+        // check permissions.
+        auto control_string_legacy = "ctl." + value;
+        const char* target_context_legacy = nullptr;
+        const char* type_legacy = nullptr;
+        property_info_area->GetPropertyInfo(control_string_legacy.c_str(), &target_context_legacy,
+                                            &type_legacy);
+
+        if (CheckMacPerms(control_string_legacy, target_context_legacy, source_context.c_str(), cr)) {
+            return true;
+        }
+    }
+
+    auto control_string_full = name + "$" + value;
+    const char* target_context_full = nullptr;
+    const char* type_full = nullptr;
+    property_info_area->GetPropertyInfo(control_string_full.c_str(), &target_context_full,
+                                        &type_full);
+
+    return CheckMacPerms(control_string_full, target_context_full, source_context.c_str(), cr);
+}
+
 // This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
 uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                            const std::string& source_context, const ucred& cr, std::string* error) {
@@ -402,15 +436,9 @@
     }
 
     if (StartsWith(name, "ctl.")) {
-        // ctl. properties have their name ctl.<action> and their value is the name of the service
-        // to apply that action to.  Permissions for these actions are based on the service, so we
-        // must create a fake name of ctl.<service> to check permissions.
-        auto control_string = "ctl." + value;
-        const char* target_context = nullptr;
-        const char* type = nullptr;
-        property_info_area->GetPropertyInfo(control_string.c_str(), &target_context, &type);
-        if (!CheckMacPerms(control_string, target_context, source_context.c_str(), cr)) {
-            *error = StringPrintf("Unable to '%s' service %s", name.c_str() + 4, value.c_str());
+        if (!CheckControlPropertyPerms(name, value, source_context, cr)) {
+            *error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
+                                  value.c_str());
             return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
         }
 
@@ -742,7 +770,7 @@
 }
 
 static int SelinuxAuditCallback(void* data, security_class_t /*cls*/, char* buf, size_t len) {
-    property_audit_data* d = reinterpret_cast<property_audit_data*>(data);
+    auto* d = reinterpret_cast<PropertyAuditData*>(data);
 
     if (!d || !d->name || !d->cr) {
         LOG(ERROR) << "AuditCallback invoked with null data arguments!";
diff --git a/init/property_service.h b/init/property_service.h
index 4a354c2..cacd987 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -26,11 +26,6 @@
 namespace android {
 namespace init {
 
-struct property_audit_data {
-    const ucred* cr;
-    const char* name;
-};
-
 extern uint32_t (*property_set)(const std::string& name, const std::string& value);
 
 uint32_t HandlePropertySet(const std::string& name, const std::string& value,
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index 0ebb226..7fef106 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -46,6 +46,8 @@
                                              "%s:%d: %s CHECK '" #predicate "' failed.",\
                                              __FILE__, __LINE__, __FUNCTION__)
 
+using namespace std::string_literals;
+
 namespace android {
 
 #if defined(__ANDROID__)
@@ -236,10 +238,15 @@
       // Different name is useful for debugging
       namespace_name = kVendorClassloaderNamespaceName;
       ALOGD("classloader namespace configured for unbundled vendor apk. library_path=%s", library_path.c_str());
-    } else if (!oem_public_libraries_.empty()) {
-      // oem_public_libraries are NOT available to vendor apks, otherwise it
+    } else {
+      // oem and product public libraries are NOT available to vendor apks, otherwise it
       // would be system->vendor violation.
-      system_exposed_libraries = system_exposed_libraries + ":" + oem_public_libraries_.c_str();
+      if (!oem_public_libraries_.empty()) {
+        system_exposed_libraries = system_exposed_libraries + ':' + oem_public_libraries_;
+      }
+      if (!product_public_libraries_.empty()) {
+        system_exposed_libraries = system_exposed_libraries + ':' + product_public_libraries_;
+      }
     }
 
     NativeLoaderNamespace native_loader_ns;
@@ -351,6 +358,8 @@
     std::string vndksp_native_libraries_system_config =
             root_dir + kVndkspNativeLibrariesSystemConfigPathFromRoot;
 
+    std::string product_public_native_libraries_dir = "/product/etc";
+
     std::string error_msg;
     LOG_ALWAYS_FATAL_IF(
         !ReadConfig(public_native_libraries_system_config, &sonames, always_true, &error_msg),
@@ -373,7 +382,7 @@
     //
     // TODO(dimitry): this is a bit misleading since we do not know
     // if the vendor public library is going to be opened from /vendor/lib
-    // we might as well end up loading them from /system/lib
+    // we might as well end up loading them from /system/lib or /product/lib
     // For now we rely on CTS test to catch things like this but
     // it should probably be addressed in the future.
     for (const auto& soname : sonames) {
@@ -387,48 +396,15 @@
     // system libs that are exposed to apps. The libs in the txt files must be
     // named as lib<name>.<companyname>.so.
     sonames.clear();
-    std::string dirname = base::Dirname(public_native_libraries_system_config);
-    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(dirname.c_str()), closedir);
-    if (dir != nullptr) {
-      // Failing to opening the dir is not an error, which can happen in
-      // webview_zygote.
-      struct dirent* ent;
-      while ((ent = readdir(dir.get())) != nullptr) {
-        if (ent->d_type != DT_REG && ent->d_type != DT_LNK) {
-          continue;
-        }
-        const std::string filename(ent->d_name);
-        if (android::base::StartsWith(filename, kPublicNativeLibrariesExtensionConfigPrefix) &&
-            android::base::EndsWith(filename, kPublicNativeLibrariesExtensionConfigSuffix)) {
-          const size_t start = kPublicNativeLibrariesExtensionConfigPrefixLen;
-          const size_t end = filename.size() - kPublicNativeLibrariesExtensionConfigSuffixLen;
-          const std::string company_name = filename.substr(start, end - start);
-          const std::string config_file_path = dirname + "/" + filename;
-          LOG_ALWAYS_FATAL_IF(
-              company_name.empty(),
-              "Error extracting company name from public native library list file path \"%s\"",
-              config_file_path.c_str());
-          LOG_ALWAYS_FATAL_IF(
-              !ReadConfig(
-                  config_file_path, &sonames,
-                  [&company_name](const std::string& soname, std::string* error_msg) {
-                    if (android::base::StartsWith(soname, "lib") &&
-                        android::base::EndsWith(soname, "." + company_name + ".so")) {
-                      return true;
-                    } else {
-                      *error_msg = "Library name \"" + soname +
-                                   "\" does not end with the company name: " + company_name + ".";
-                      return false;
-                    }
-                  },
-                  &error_msg),
-              "Error reading public native library list from \"%s\": %s", config_file_path.c_str(),
-              error_msg.c_str());
-        }
-      }
-    }
+    ReadExtensionLibraries(base::Dirname(public_native_libraries_system_config).c_str(), &sonames);
     oem_public_libraries_ = base::Join(sonames, ':');
 
+    // read /product/etc/public.libraries-<companyname>.txt which contain partner defined
+    // product libs that are exposed to apps.
+    sonames.clear();
+    ReadExtensionLibraries(product_public_native_libraries_dir.c_str(), &sonames);
+    product_public_libraries_ = base::Join(sonames, ':');
+
     // Insert VNDK version to llndk and vndksp config file names.
     insert_vndk_version_str(&llndk_native_libraries_system_config);
     insert_vndk_version_str(&vndksp_native_libraries_system_config);
@@ -448,11 +424,54 @@
     vendor_public_libraries_ = base::Join(sonames, ':');
   }
 
-  void Reset() {
-    namespaces_.clear();
-  }
+  void Reset() { namespaces_.clear(); }
 
  private:
+  void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonames) {
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(dirname), closedir);
+    if (dir != nullptr) {
+      // Failing to opening the dir is not an error, which can happen in
+      // webview_zygote.
+      while (struct dirent* ent = readdir(dir.get())) {
+        if (ent->d_type != DT_REG && ent->d_type != DT_LNK) {
+          continue;
+        }
+        const std::string filename(ent->d_name);
+        if (android::base::StartsWith(filename, kPublicNativeLibrariesExtensionConfigPrefix) &&
+            android::base::EndsWith(filename, kPublicNativeLibrariesExtensionConfigSuffix)) {
+          const size_t start = kPublicNativeLibrariesExtensionConfigPrefixLen;
+          const size_t end = filename.size() - kPublicNativeLibrariesExtensionConfigSuffixLen;
+          const std::string company_name = filename.substr(start, end - start);
+          const std::string config_file_path = dirname + "/"s + filename;
+          LOG_ALWAYS_FATAL_IF(
+              company_name.empty(),
+              "Error extracting company name from public native library list file path \"%s\"",
+              config_file_path.c_str());
+
+          std::string error_msg;
+
+          LOG_ALWAYS_FATAL_IF(
+              !ReadConfig(
+                  config_file_path, sonames,
+                  [&company_name](const std::string& soname, std::string* error_msg) {
+                    if (android::base::StartsWith(soname, "lib") &&
+                        android::base::EndsWith(soname, "." + company_name + ".so")) {
+                      return true;
+                    } else {
+                      *error_msg = "Library name \"" + soname +
+                                   "\" does not end with the company name: " + company_name + ".";
+                      return false;
+                    }
+                  },
+                  &error_msg),
+              "Error reading public native library list from \"%s\": %s", config_file_path.c_str(),
+              error_msg.c_str());
+        }
+      }
+    }
+  }
+
+
   bool ReadConfig(const std::string& configFile, std::vector<std::string>* sonames,
                   const std::function<bool(const std::string& /* soname */,
                                            std::string* /* error_msg */)>& check_soname,
@@ -559,6 +578,7 @@
   std::string system_public_libraries_;
   std::string vendor_public_libraries_;
   std::string oem_public_libraries_;
+  std::string product_public_libraries_;
   std::string system_llndk_libraries_;
   std::string system_vndksp_libraries_;
 
diff --git a/libnativeloader/test/Android.bp b/libnativeloader/test/Android.bp
index 5cf88b0..d528f30 100644
--- a/libnativeloader/test/Android.bp
+++ b/libnativeloader/test/Android.bp
@@ -49,3 +49,23 @@
         "libbase",
     ],
 }
+
+cc_library {
+    name: "libfoo.product1",
+    srcs: ["test.cpp"],
+    cflags: ["-DLIBNAME=\"libfoo.product1.so\""],
+    product_specific: true,
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+cc_library {
+    name: "libbar.product1",
+    srcs: ["test.cpp"],
+    cflags: ["-DLIBNAME=\"libbar.product1.so\""],
+    product_specific: true,
+    shared_libs: [
+        "libbase",
+    ],
+}
diff --git a/libnativeloader/test/Android.mk b/libnativeloader/test/Android.mk
index e625454..65e7b09 100644
--- a/libnativeloader/test/Android.mk
+++ b/libnativeloader/test/Android.mk
@@ -30,6 +30,13 @@
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
+LOCAL_MODULE := public.libraries-product1.txt
+LOCAL_SRC_FILES:= $(LOCAL_MODULE)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_ETC)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
 LOCAL_PACKAGE_NAME := oemlibrarytest-system
 LOCAL_MODULE_TAGS := tests
 LOCAL_MANIFEST_FILE := system/AndroidManifest.xml
diff --git a/libnativeloader/test/public.libraries-product1.txt b/libnativeloader/test/public.libraries-product1.txt
new file mode 100644
index 0000000..358154c
--- /dev/null
+++ b/libnativeloader/test/public.libraries-product1.txt
@@ -0,0 +1,2 @@
+libfoo.product1.so
+libbar.product1.so
diff --git a/libnativeloader/test/src/android/test/app/TestActivity.java b/libnativeloader/test/src/android/test/app/TestActivity.java
index 214892d..a7a455d 100644
--- a/libnativeloader/test/src/android/test/app/TestActivity.java
+++ b/libnativeloader/test/src/android/test/app/TestActivity.java
@@ -29,6 +29,8 @@
          tryLoadingLib("bar.oem1");
          tryLoadingLib("foo.oem2");
          tryLoadingLib("bar.oem2");
+         tryLoadingLib("foo.product1");
+         tryLoadingLib("bar.product1");
     }
 
     private void tryLoadingLib(String name) {
diff --git a/libstats/stats_event_list.c b/libstats/stats_event_list.c
index 966bb08..3d746db 100644
--- a/libstats/stats_event_list.c
+++ b/libstats/stats_event_list.c
@@ -131,7 +131,7 @@
 }
 
 static int __write_to_stats_daemon(struct iovec* vec, size_t nr) {
-    int ret, save_errno;
+    int save_errno;
     struct timespec ts;
     size_t len, i;
 
@@ -145,14 +145,7 @@
     save_errno = errno;
     clock_gettime(CLOCK_REALTIME, &ts);
 
-    ret = 0;
-
-    ssize_t retval;
-    retval = (*statsdLoggerWrite.write)(&ts, vec, nr);
-    if (ret >= 0) {
-        ret = retval;
-    }
-
+    int ret = (int)(*statsdLoggerWrite.write)(&ts, vec, nr);
     errno = save_errno;
     return ret;
 }
@@ -178,4 +171,4 @@
     ret = write_to_statsd(vec, nr);
     errno = save_errno;
     return ret;
-}
\ No newline at end of file
+}
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index ba96cc8..51e3f9e 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -37,7 +37,8 @@
 ###############################################################################
 namespace.default.isolated = true
 
-namespace.default.search.paths = /system/${LIB}
+namespace.default.search.paths  = /system/${LIB}
+namespace.default.search.paths += /product/${LIB}
 
 # We can't have entire /system/${LIB} as permitted paths because doing so
 # makes it possible to load libs in /system/${LIB}/vndk* directories by
@@ -49,6 +50,7 @@
 namespace.default.permitted.paths  = /system/${LIB}/drm
 namespace.default.permitted.paths += /system/${LIB}/extractors
 namespace.default.permitted.paths += /system/${LIB}/hw
+namespace.default.permitted.paths += /product/${LIB}
 # These are where odex files are located. libart has to be able to dlopen the files
 namespace.default.permitted.paths += /system/framework
 namespace.default.permitted.paths += /system/app
@@ -68,6 +70,8 @@
 
 namespace.default.asan.search.paths  = /data/asan/system/${LIB}
 namespace.default.asan.search.paths +=           /system/${LIB}
+namespace.default.asan.search.paths += /data/asan/product/${LIB}
+namespace.default.asan.search.paths +=           /product/${LIB}
 
 namespace.default.asan.permitted.paths  = /data
 namespace.default.asan.permitted.paths += /system/${LIB}/drm
@@ -83,6 +87,7 @@
 namespace.default.asan.permitted.paths += /odm/app
 namespace.default.asan.permitted.paths += /odm/priv-app
 namespace.default.asan.permitted.paths += /oem/app
+namespace.default.asan.permitted.paths += /product/${LIB}
 namespace.default.asan.permitted.paths += /product/framework
 namespace.default.asan.permitted.paths += /product/app
 namespace.default.asan.permitted.paths += /product/priv-app
@@ -320,10 +325,13 @@
 ###############################################################################
 namespace.system.isolated = false
 
-namespace.system.search.paths = /system/${LIB}
+namespace.system.search.paths  = /system/${LIB}
+namespace.system.search.paths += /product/${LIB}
 
 namespace.system.asan.search.paths  = /data/asan/system/${LIB}
 namespace.system.asan.search.paths +=           /system/${LIB}
+namespace.system.asan.search.paths += /data/asan/product/${LIB}
+namespace.system.asan.search.paths +=           /product/${LIB}
 
 ###############################################################################
 # Namespace config for binaries under /postinstall.
@@ -335,4 +343,5 @@
 ###############################################################################
 [postinstall]
 namespace.default.isolated = false
-namespace.default.search.paths = /system/${LIB}
+namespace.default.search.paths  = /system/${LIB}
+namespace.default.search.paths += /product/${LIB}
diff --git a/rootdir/etc/ld.config.vndk_lite.txt b/rootdir/etc/ld.config.vndk_lite.txt
index 1fd4195..ab03755 100644
--- a/rootdir/etc/ld.config.vndk_lite.txt
+++ b/rootdir/etc/ld.config.vndk_lite.txt
@@ -40,6 +40,7 @@
 namespace.default.search.paths  = /system/${LIB}
 namespace.default.search.paths += /odm/${LIB}
 namespace.default.search.paths += /vendor/${LIB}
+namespace.default.search.paths += /product/${LIB}
 
 namespace.default.asan.search.paths  = /data/asan/system/${LIB}
 namespace.default.asan.search.paths +=           /system/${LIB}
@@ -47,6 +48,8 @@
 namespace.default.asan.search.paths +=           /odm/${LIB}
 namespace.default.asan.search.paths += /data/asan/vendor/${LIB}
 namespace.default.asan.search.paths +=           /vendor/${LIB}
+namespace.default.asan.search.paths += /data/asan/product/${LIB}
+namespace.default.asan.search.paths +=           /product/${LIB}
 
 ###############################################################################
 # "sphal" namespace
@@ -205,6 +208,7 @@
 namespace.default.search.paths += /system/${LIB}/vndk%VNDK_VER%
 namespace.default.search.paths += /system/${LIB}/vndk-sp%VNDK_VER%
 namespace.default.search.paths += /system/${LIB}
+namespace.default.search.paths += /product/${LIB}
 
 namespace.default.asan.search.paths  = /data/asan/odm/${LIB}
 namespace.default.asan.search.paths +=           /odm/${LIB}
@@ -224,6 +228,8 @@
 namespace.default.asan.search.paths +=           /system/${LIB}/vndk-sp%VNDK_VER%
 namespace.default.asan.search.paths += /data/asan/system/${LIB}
 namespace.default.asan.search.paths +=           /system/${LIB}
+namespace.default.asan.search.paths += /data/asan/product/${LIB}
+namespace.default.asan.search.paths +=           /product/${LIB}
 
 ###############################################################################
 # Namespace config for binaries under /postinstall.
@@ -235,4 +241,5 @@
 ###############################################################################
 [postinstall]
 namespace.default.isolated = false
-namespace.default.search.paths = /system/${LIB}
+namespace.default.search.paths  = /system/${LIB}
+namespace.default.search.paths += /product/${LIB}