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}