Merge "trusty:storageproxyd: Add ufs to parse_dev_type"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 51d5755..d6945e3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -25,6 +25,9 @@
"name": "libcutils_test"
},
{
+ "name": "libmodprobe_tests"
+ },
+ {
"name": "libprocinfo_test"
},
{
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 9b663be..460ddde 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1167,7 +1167,7 @@
std::string host;
int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
std::string error;
- if (address.starts_with("vsock:")) {
+ if (address.starts_with("vsock:") || address.starts_with("localfilesystem:")) {
serial = address;
} else if (!android::base::ParseNetAddress(address, &host, &port, &serial, &error)) {
SendFail(reply_fd, android::base::StringPrintf("couldn't parse '%s': %s",
diff --git a/adb/adb.h b/adb/adb.h
index c6cb06a..e7fcc91 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -200,7 +200,7 @@
#define ADB_SUBCLASS 0x42
#define ADB_PROTOCOL 0x1
-void local_init(int port);
+void local_init(const std::string& addr);
bool local_connect(int port);
int local_connect_arbitrary_ports(int console_port, int adb_port, std::string* error);
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 6465ffe..813a8a9 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -137,8 +137,8 @@
" run remote shell command (interactive shell if no command given)\n"
" -e: choose escape character, or \"none\"; default '~'\n"
" -n: don't read from stdin\n"
- " -T: disable PTY allocation\n"
- " -t: force PTY allocation\n"
+ " -T: disable pty allocation\n"
+ " -t: allocate a pty if on a tty (-tt: force pty allocation)\n"
" -x: disable remote exit codes and stdout/stderr separation\n"
" emu COMMAND run emulator console command\n"
"\n"
diff --git a/adb/client/file_sync_client.cpp b/adb/client/file_sync_client.cpp
index 703eb2f..fbfeb53 100644
--- a/adb/client/file_sync_client.cpp
+++ b/adb/client/file_sync_client.cpp
@@ -52,6 +52,8 @@
#include <android-base/strings.h>
#include <android-base/stringprintf.h>
+typedef void(sync_ls_cb)(unsigned mode, uint64_t size, uint64_t time, const char* name);
+
struct syncsendbuf {
unsigned id;
unsigned size;
@@ -210,6 +212,7 @@
Error("failed to get feature set: %s", error.c_str());
} else {
have_stat_v2_ = CanUseFeature(features_, kFeatureStat2);
+ have_ls_v2_ = CanUseFeature(features_, kFeatureLs2);
fd.reset(adb_connect("sync:", &error));
if (fd < 0) {
Error("connect failed: %s", error.c_str());
@@ -357,7 +360,7 @@
<< msg.stat_v1.id;
}
- if (msg.stat_v1.mode == 0 && msg.stat_v1.size == 0 && msg.stat_v1.time == 0) {
+ if (msg.stat_v1.mode == 0 && msg.stat_v1.size == 0 && msg.stat_v1.mtime == 0) {
// There's no way for us to know what the error was.
errno = ENOPROTOOPT;
return false;
@@ -365,13 +368,52 @@
st->st_mode = msg.stat_v1.mode;
st->st_size = msg.stat_v1.size;
- st->st_ctime = msg.stat_v1.time;
- st->st_mtime = msg.stat_v1.time;
+ st->st_ctime = msg.stat_v1.mtime;
+ st->st_mtime = msg.stat_v1.mtime;
}
return true;
}
+ bool SendLs(const char* path) {
+ return SendRequest(have_ls_v2_ ? ID_LIST_V2 : ID_LIST_V1, path);
+ }
+
+ private:
+ template <bool v2>
+ static bool FinishLsImpl(borrowed_fd fd, const std::function<sync_ls_cb>& callback) {
+ using dent_type =
+ std::conditional_t<v2, decltype(syncmsg::dent_v2), decltype(syncmsg::dent_v1)>;
+
+ while (true) {
+ dent_type dent;
+ if (!ReadFdExactly(fd, &dent, sizeof(dent))) return false;
+
+ uint32_t expected_id = v2 ? ID_DENT_V2 : ID_DENT_V1;
+ if (dent.id == ID_DONE) return true;
+ if (dent.id != expected_id) return false;
+
+ // Maximum length of a file name excluding null terminator (NAME_MAX) on Linux is 255.
+ char buf[256];
+ size_t len = dent.namelen;
+ if (len > 255) return false;
+
+ if (!ReadFdExactly(fd, buf, len)) return false;
+ buf[len] = 0;
+
+ callback(dent.mode, dent.size, dent.mtime, buf);
+ }
+ }
+
+ public:
+ bool FinishLs(const std::function<sync_ls_cb>& callback) {
+ if (have_ls_v2_) {
+ return FinishLsImpl<true>(this->fd, callback);
+ } else {
+ return FinishLsImpl<false>(this->fd, callback);
+ }
+ }
+
// Sending header, payload, and footer in a single write makes a huge
// difference to "adb sync" performance.
bool SendSmallFile(const char* path_and_mode,
@@ -578,6 +620,7 @@
bool expect_done_;
FeatureSet features_;
bool have_stat_v2_;
+ bool have_ls_v2_;
TransferLedger global_ledger_;
TransferLedger current_ledger_;
@@ -609,28 +652,9 @@
}
};
-typedef void (sync_ls_cb)(unsigned mode, unsigned size, unsigned time, const char* name);
-
static bool sync_ls(SyncConnection& sc, const char* path,
const std::function<sync_ls_cb>& func) {
- if (!sc.SendRequest(ID_LIST, path)) return false;
-
- while (true) {
- syncmsg msg;
- if (!ReadFdExactly(sc.fd, &msg.dent, sizeof(msg.dent))) return false;
-
- if (msg.dent.id == ID_DONE) return true;
- if (msg.dent.id != ID_DENT) return false;
-
- size_t len = msg.dent.namelen;
- if (len > 256) return false; // TODO: resize buffer? continue?
-
- char buf[257];
- if (!ReadFdExactly(sc.fd, buf, len)) return false;
- buf[len] = 0;
-
- func(msg.dent.mode, msg.dent.size, msg.dent.time, buf);
- }
+ return sc.SendLs(path) && sc.FinishLs(func);
}
static bool sync_stat(SyncConnection& sc, const char* path, struct stat* st) {
@@ -787,9 +811,8 @@
SyncConnection sc;
if (!sc.IsValid()) return false;
- return sync_ls(sc, path, [](unsigned mode, unsigned size, unsigned time,
- const char* name) {
- printf("%08x %08x %08x %s\n", mode, size, time, name);
+ return sync_ls(sc, path, [](unsigned mode, uint64_t size, uint64_t time, const char* name) {
+ printf("%08x %08" PRIx64 " %08" PRIx64 " %s\n", mode, size, time, name);
});
}
@@ -1062,7 +1085,7 @@
file_list->push_back(ci);
// Put the files/dirs in rpath on the lists.
- auto callback = [&](unsigned mode, unsigned size, unsigned time, const char* name) {
+ auto callback = [&](unsigned mode, uint64_t size, uint64_t time, const char* name) {
if (IsDotOrDotDot(name)) {
return;
}
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index 0c5c28f..e5ffe4c 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -129,7 +129,7 @@
}
if (!getenv("ADB_EMU") || strcmp(getenv("ADB_EMU"), "0") != 0) {
- local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT);
+ local_init(android::base::StringPrintf("tcp:%d", DEFAULT_ADB_LOCAL_TRANSPORT_PORT));
}
std::string error;
diff --git a/adb/daemon/auth.cpp b/adb/daemon/auth.cpp
index 2e84ce6..ec4ab4a 100644
--- a/adb/daemon/auth.cpp
+++ b/adb/daemon/auth.cpp
@@ -16,36 +16,72 @@
#define TRACE_TAG AUTH
-#include "adb.h"
-#include "adb_auth.h"
-#include "adb_io.h"
-#include "fdevent/fdevent.h"
#include "sysdeps.h"
-#include "transport.h"
#include <resolv.h>
#include <stdio.h>
#include <string.h>
-#include <iomanip>
#include <algorithm>
+#include <iomanip>
+#include <map>
#include <memory>
#include <adbd_auth.h>
#include <android-base/file.h>
+#include <android-base/no_destructor.h>
#include <android-base/strings.h>
#include <crypto_utils/android_pubkey.h>
#include <openssl/obj_mac.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
+#include "adb.h"
+#include "adb_auth.h"
+#include "adb_io.h"
+#include "fdevent/fdevent.h"
+#include "transport.h"
+#include "types.h"
+
static AdbdAuthContext* auth_ctx;
static void adb_disconnected(void* unused, atransport* t);
static struct adisconnect adb_disconnect = {adb_disconnected, nullptr};
+static android::base::NoDestructor<std::map<uint32_t, weak_ptr<atransport>>> transports;
+static uint32_t transport_auth_id = 0;
+
bool auth_required = true;
+static void* transport_to_callback_arg(atransport* transport) {
+ uint32_t id = transport_auth_id++;
+ (*transports)[id] = transport->weak();
+ return reinterpret_cast<void*>(id);
+}
+
+static atransport* transport_from_callback_arg(void* id) {
+ uint64_t id_u64 = reinterpret_cast<uint64_t>(id);
+ if (id_u64 > std::numeric_limits<uint32_t>::max()) {
+ LOG(FATAL) << "transport_from_callback_arg called on out of range value: " << id_u64;
+ }
+
+ uint32_t id_u32 = static_cast<uint32_t>(id_u64);
+ auto it = transports->find(id_u32);
+ if (it == transports->end()) {
+ LOG(ERROR) << "transport_from_callback_arg failed to find transport for id " << id_u32;
+ return nullptr;
+ }
+
+ atransport* t = it->second.get();
+ if (!t) {
+ LOG(WARNING) << "transport_from_callback_arg found already destructed transport";
+ return nullptr;
+ }
+
+ transports->erase(it);
+ return t;
+}
+
static void IteratePublicKeys(std::function<bool(std::string_view public_key)> f) {
adbd_auth_get_public_keys(
auth_ctx,
@@ -111,9 +147,16 @@
static void adbd_auth_key_authorized(void* arg, uint64_t id) {
LOG(INFO) << "adb client authorized";
- auto* transport = static_cast<atransport*>(arg);
- transport->auth_id = id;
- adbd_auth_verified(transport);
+ fdevent_run_on_main_thread([=]() {
+ LOG(INFO) << "arg = " << reinterpret_cast<uintptr_t>(arg);
+ auto* transport = transport_from_callback_arg(arg);
+ if (!transport) {
+ LOG(ERROR) << "authorization received for deleted transport, ignoring";
+ return;
+ }
+ transport->auth_id = id;
+ adbd_auth_verified(transport);
+ });
}
void adbd_auth_init(void) {
@@ -158,7 +201,8 @@
void adbd_auth_confirm_key(atransport* t) {
LOG(INFO) << "prompting user to authorize key";
t->AddDisconnect(&adb_disconnect);
- adbd_auth_prompt_user(auth_ctx, t->auth_key.data(), t->auth_key.size(), t);
+ adbd_auth_prompt_user(auth_ctx, t->auth_key.data(), t->auth_key.size(),
+ transport_to_callback_arg(t));
}
void adbd_notify_framework_connected_key(atransport* t) {
diff --git a/adb/daemon/file_sync_service.cpp b/adb/daemon/file_sync_service.cpp
index 0e70d47..d6af708 100644
--- a/adb/daemon/file_sync_service.cpp
+++ b/adb/daemon/file_sync_service.cpp
@@ -139,7 +139,7 @@
lstat(path, &st);
msg.stat_v1.mode = st.st_mode;
msg.stat_v1.size = st.st_size;
- msg.stat_v1.time = st.st_mtime;
+ msg.stat_v1.mtime = st.st_mtime;
return WriteFdExactly(s, &msg.stat_v1, sizeof(msg.stat_v1));
}
@@ -174,40 +174,73 @@
return WriteFdExactly(s, &msg.stat_v2, sizeof(msg.stat_v2));
}
+template <bool v2>
static bool do_list(int s, const char* path) {
dirent* de;
- syncmsg msg;
- msg.dent.id = ID_DENT;
+ using MessageType =
+ std::conditional_t<v2, decltype(syncmsg::dent_v2), decltype(syncmsg::dent_v1)>;
+ MessageType msg;
+ uint32_t msg_id;
+ if constexpr (v2) {
+ msg_id = ID_DENT_V2;
+ } else {
+ msg_id = ID_DENT_V1;
+ }
std::unique_ptr<DIR, int(*)(DIR*)> d(opendir(path), closedir);
if (!d) goto done;
while ((de = readdir(d.get()))) {
+ memset(&msg, 0, sizeof(msg));
+ msg.id = msg_id;
+
std::string filename(StringPrintf("%s/%s", path, de->d_name));
struct stat st;
if (lstat(filename.c_str(), &st) == 0) {
- size_t d_name_length = strlen(de->d_name);
- msg.dent.mode = st.st_mode;
- msg.dent.size = st.st_size;
- msg.dent.time = st.st_mtime;
- msg.dent.namelen = d_name_length;
+ msg.mode = st.st_mode;
+ msg.size = st.st_size;
+ msg.mtime = st.st_mtime;
- if (!WriteFdExactly(s, &msg.dent, sizeof(msg.dent)) ||
- !WriteFdExactly(s, de->d_name, d_name_length)) {
- return false;
+ if constexpr (v2) {
+ msg.dev = st.st_dev;
+ msg.ino = st.st_ino;
+ msg.nlink = st.st_nlink;
+ msg.uid = st.st_uid;
+ msg.gid = st.st_gid;
+ msg.atime = st.st_atime;
+ msg.ctime = st.st_ctime;
}
+ } else {
+ if constexpr (v2) {
+ msg.error = errno;
+ } else {
+ continue;
+ }
+ }
+
+ size_t d_name_length = strlen(de->d_name);
+ msg.namelen = d_name_length;
+
+ if (!WriteFdExactly(s, &msg, sizeof(msg)) ||
+ !WriteFdExactly(s, de->d_name, d_name_length)) {
+ return false;
}
}
done:
- msg.dent.id = ID_DONE;
- msg.dent.mode = 0;
- msg.dent.size = 0;
- msg.dent.time = 0;
- msg.dent.namelen = 0;
- return WriteFdExactly(s, &msg.dent, sizeof(msg.dent));
+ memset(&msg, 0, sizeof(msg));
+ msg.id = ID_DONE;
+ return WriteFdExactly(s, &msg, sizeof(msg));
+}
+
+static bool do_list_v1(int s, const char* path) {
+ return do_list<false>(s, path);
+}
+
+static bool do_list_v2(int s, const char* path) {
+ return do_list<true>(s, path);
}
// Make sure that SendFail from adb_io.cpp isn't accidentally used in this file.
@@ -499,8 +532,10 @@
return "lstat_v2";
case ID_STAT_V2:
return "stat_v2";
- case ID_LIST:
- return "list";
+ case ID_LIST_V1:
+ return "list_v1";
+ case ID_LIST_V2:
+ return "list_v2";
case ID_SEND:
return "send";
case ID_RECV:
@@ -546,8 +581,11 @@
case ID_STAT_V2:
if (!do_stat_v2(fd, request.id, name)) return false;
break;
- case ID_LIST:
- if (!do_list(fd, name)) return false;
+ case ID_LIST_V1:
+ if (!do_list_v1(fd, name)) return false;
+ break;
+ case ID_LIST_V2:
+ if (!do_list_v2(fd, name)) return false;
break;
case ID_SEND:
if (!do_send(fd, name, buffer)) return false;
diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp
index 7277cc8..3322574 100644
--- a/adb/daemon/main.cpp
+++ b/adb/daemon/main.cpp
@@ -32,11 +32,13 @@
#include <sys/prctl.h>
#include <memory>
+#include <vector>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#if defined(__ANDROID__)
#include <libminijail.h>
@@ -51,6 +53,7 @@
#include "adb_auth.h"
#include "adb_listeners.h"
#include "adb_utils.h"
+#include "socket_spec.h"
#include "transport.h"
#include "mdns.h"
@@ -179,12 +182,26 @@
}
#endif
-static void setup_port(int port) {
- LOG(INFO) << "adbd listening on port " << port;
- local_init(port);
+static void setup_adb(const std::vector<std::string>& addrs) {
#if defined(__ANDROID__)
+ // Get the first valid port from addrs and setup mDNS.
+ int port = -1;
+ std::string error;
+ for (const auto& addr : addrs) {
+ port = get_host_socket_spec_port(addr, &error);
+ if (port != -1) {
+ break;
+ }
+ }
+ if (port == -1) {
+ port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
+ }
setup_mdns(port);
#endif
+ for (const auto& addr : addrs) {
+ LOG(INFO) << "adbd listening on " << addr;
+ local_init(addr);
+ }
}
int adbd_main(int server_port) {
@@ -248,25 +265,38 @@
// If one of these properties is set, also listen on that port.
// If one of the properties isn't set and we couldn't listen on usb, listen
// on the default port.
- std::string prop_port = android::base::GetProperty("service.adb.tcp.port", "");
- if (prop_port.empty()) {
- prop_port = android::base::GetProperty("persist.adb.tcp.port", "");
- }
+ std::vector<std::string> addrs;
+ std::string prop_addr = android::base::GetProperty("service.adb.listen_addrs", "");
+ if (prop_addr.empty()) {
+ std::string prop_port = android::base::GetProperty("service.adb.tcp.port", "");
+ if (prop_port.empty()) {
+ prop_port = android::base::GetProperty("persist.adb.tcp.port", "");
+ }
#if !defined(__ANDROID__)
- if (prop_port.empty() && getenv("ADBD_PORT")) {
- prop_port = getenv("ADBD_PORT");
- }
+ if (prop_port.empty() && getenv("ADBD_PORT")) {
+ prop_port = getenv("ADBD_PORT");
+ }
#endif
- int port;
- if (sscanf(prop_port.c_str(), "%d", &port) == 1 && port > 0) {
- D("using port=%d", port);
- // Listen on TCP port specified by service.adb.tcp.port property.
- setup_port(port);
- } else if (!is_usb) {
- // Listen on default port.
- setup_port(DEFAULT_ADB_LOCAL_TRANSPORT_PORT);
+ int port;
+ if (sscanf(prop_port.c_str(), "%d", &port) == 1 && port > 0) {
+ D("using tcp port=%d", port);
+ // Listen on TCP and VSOCK port specified by service.adb.tcp.port property.
+ addrs.push_back(android::base::StringPrintf("tcp:%d", port));
+ addrs.push_back(android::base::StringPrintf("vsock:%d", port));
+ setup_adb(addrs);
+ } else if (!is_usb) {
+ // Listen on default port.
+ addrs.push_back(
+ android::base::StringPrintf("tcp:%d", DEFAULT_ADB_LOCAL_TRANSPORT_PORT));
+ addrs.push_back(
+ android::base::StringPrintf("vsock:%d", DEFAULT_ADB_LOCAL_TRANSPORT_PORT));
+ setup_adb(addrs);
+ }
+ } else {
+ addrs = android::base::Split(prop_addr, ",");
+ setup_adb(addrs);
}
D("adbd_main(): pre init_jdwp()");
diff --git a/adb/daemon/shell_service.cpp b/adb/daemon/shell_service.cpp
index 0fb14c4..f62032d 100644
--- a/adb/daemon/shell_service.cpp
+++ b/adb/daemon/shell_service.cpp
@@ -85,7 +85,6 @@
#include <paths.h>
#include <pty.h>
#include <pwd.h>
-#include <sys/select.h>
#include <termios.h>
#include <memory>
@@ -141,6 +140,20 @@
return true;
}
+struct SubprocessPollfds {
+ adb_pollfd pfds[3];
+
+ adb_pollfd* data() { return pfds; }
+ size_t size() { return 3; }
+
+ adb_pollfd* begin() { return pfds; }
+ adb_pollfd* end() { return pfds + size(); }
+
+ adb_pollfd& stdinout_pfd() { return pfds[0]; }
+ adb_pollfd& stderr_pfd() { return pfds[1]; }
+ adb_pollfd& protocol_pfd() { return pfds[2]; }
+};
+
class Subprocess {
public:
Subprocess(std::string command, const char* terminal_type, SubprocessType type,
@@ -176,8 +189,7 @@
void PassDataStreams();
void WaitForExit();
- unique_fd* SelectLoop(fd_set* master_read_set_ptr,
- fd_set* master_write_set_ptr);
+ unique_fd* PollLoop(SubprocessPollfds* pfds);
// Input/output stream handlers. Success returns nullptr, failure returns
// a pointer to the failed FD.
@@ -545,23 +557,23 @@
}
// Start by trying to read from the protocol FD, stdout, and stderr.
- fd_set master_read_set, master_write_set;
- FD_ZERO(&master_read_set);
- FD_ZERO(&master_write_set);
- for (unique_fd* sfd : {&protocol_sfd_, &stdinout_sfd_, &stderr_sfd_}) {
- if (*sfd != -1) {
- FD_SET(sfd->get(), &master_read_set);
- }
- }
+ SubprocessPollfds pfds;
+ pfds.stdinout_pfd() = {.fd = stdinout_sfd_.get(), .events = POLLIN};
+ pfds.stderr_pfd() = {.fd = stderr_sfd_.get(), .events = POLLIN};
+ pfds.protocol_pfd() = {.fd = protocol_sfd_.get(), .events = POLLIN};
// Pass data until the protocol FD or both the subprocess pipes die, at
// which point we can't pass any more data.
while (protocol_sfd_ != -1 && (stdinout_sfd_ != -1 || stderr_sfd_ != -1)) {
- unique_fd* dead_sfd = SelectLoop(&master_read_set, &master_write_set);
+ unique_fd* dead_sfd = PollLoop(&pfds);
if (dead_sfd) {
D("closing FD %d", dead_sfd->get());
- FD_CLR(dead_sfd->get(), &master_read_set);
- FD_CLR(dead_sfd->get(), &master_write_set);
+ auto it = std::find_if(pfds.begin(), pfds.end(), [=](const adb_pollfd& pfd) {
+ return pfd.fd == dead_sfd->get();
+ });
+ CHECK(it != pfds.end());
+ it->fd = -1;
+ it->events = 0;
if (dead_sfd == &protocol_sfd_) {
// Using SIGHUP is a decent general way to indicate that the
// controlling process is going away. If specific signals are
@@ -583,30 +595,19 @@
}
}
-namespace {
-
-inline bool ValidAndInSet(const unique_fd& sfd, fd_set* set) {
- return sfd != -1 && FD_ISSET(sfd.get(), set);
-}
-
-} // namespace
-
-unique_fd* Subprocess::SelectLoop(fd_set* master_read_set_ptr,
- fd_set* master_write_set_ptr) {
- fd_set read_set, write_set;
- int select_n =
- std::max(std::max(protocol_sfd_.get(), stdinout_sfd_.get()), stderr_sfd_.get()) + 1;
+unique_fd* Subprocess::PollLoop(SubprocessPollfds* pfds) {
unique_fd* dead_sfd = nullptr;
+ adb_pollfd& stdinout_pfd = pfds->stdinout_pfd();
+ adb_pollfd& stderr_pfd = pfds->stderr_pfd();
+ adb_pollfd& protocol_pfd = pfds->protocol_pfd();
- // Keep calling select() and passing data until an FD closes/errors.
+ // Keep calling poll() and passing data until an FD closes/errors.
while (!dead_sfd) {
- memcpy(&read_set, master_read_set_ptr, sizeof(read_set));
- memcpy(&write_set, master_write_set_ptr, sizeof(write_set));
- if (select(select_n, &read_set, &write_set, nullptr, nullptr) < 0) {
+ if (adb_poll(pfds->data(), pfds->size(), -1) < 0) {
if (errno == EINTR) {
continue;
} else {
- PLOG(ERROR) << "select failed, closing subprocess pipes";
+ PLOG(ERROR) << "poll failed, closing subprocess pipes";
stdinout_sfd_.reset(-1);
stderr_sfd_.reset(-1);
return nullptr;
@@ -614,34 +615,47 @@
}
// Read stdout, write to protocol FD.
- if (ValidAndInSet(stdinout_sfd_, &read_set)) {
+ if (stdinout_pfd.fd != -1 && (stdinout_pfd.revents & POLLIN)) {
dead_sfd = PassOutput(&stdinout_sfd_, ShellProtocol::kIdStdout);
}
// Read stderr, write to protocol FD.
- if (!dead_sfd && ValidAndInSet(stderr_sfd_, &read_set)) {
+ if (!dead_sfd && stderr_pfd.fd != 1 && (stderr_pfd.revents & POLLIN)) {
dead_sfd = PassOutput(&stderr_sfd_, ShellProtocol::kIdStderr);
}
// Read protocol FD, write to stdin.
- if (!dead_sfd && ValidAndInSet(protocol_sfd_, &read_set)) {
+ if (!dead_sfd && protocol_pfd.fd != -1 && (protocol_pfd.revents & POLLIN)) {
dead_sfd = PassInput();
// If we didn't finish writing, block on stdin write.
if (input_bytes_left_) {
- FD_CLR(protocol_sfd_.get(), master_read_set_ptr);
- FD_SET(stdinout_sfd_.get(), master_write_set_ptr);
+ protocol_pfd.events &= ~POLLIN;
+ stdinout_pfd.events |= POLLOUT;
}
}
// Continue writing to stdin; only happens if a previous write blocked.
- if (!dead_sfd && ValidAndInSet(stdinout_sfd_, &write_set)) {
+ if (!dead_sfd && stdinout_pfd.fd != -1 && (stdinout_pfd.revents & POLLOUT)) {
dead_sfd = PassInput();
// If we finished writing, go back to blocking on protocol read.
if (!input_bytes_left_) {
- FD_SET(protocol_sfd_.get(), master_read_set_ptr);
- FD_CLR(stdinout_sfd_.get(), master_write_set_ptr);
+ protocol_pfd.events |= POLLIN;
+ stdinout_pfd.events &= ~POLLOUT;
}
}
+
+ // After handling all of the events we've received, check to see if any fds have died.
+ if (stdinout_pfd.revents & (POLLHUP | POLLRDHUP | POLLERR | POLLNVAL)) {
+ return &stdinout_sfd_;
+ }
+
+ if (stderr_pfd.revents & (POLLHUP | POLLRDHUP | POLLERR | POLLNVAL)) {
+ return &stderr_sfd_;
+ }
+
+ if (protocol_pfd.revents & (POLLHUP | POLLRDHUP | POLLERR | POLLNVAL)) {
+ return &protocol_sfd_;
+ }
} // while (!dead_sfd)
return dead_sfd;
diff --git a/adb/daemon/transport_qemu.cpp b/adb/daemon/transport_qemu.cpp
index aa760bc..901efee 100644
--- a/adb/daemon/transport_qemu.cpp
+++ b/adb/daemon/transport_qemu.cpp
@@ -18,6 +18,7 @@
#include <qemu_pipe.h>
#define TRACE_TAG TRANSPORT
+#include "socket_spec.h"
#include "sysdeps.h"
#include "transport.h"
@@ -55,7 +56,7 @@
* the transport registration is completed. That's why we need to send the
* 'start' request after the transport is registered.
*/
-void qemu_socket_thread(int port) {
+void qemu_socket_thread(std::string_view addr) {
/* 'accept' request to the adb QEMUD service. */
static const char _accept_req[] = "accept";
/* 'start' request to the adb QEMUD service. */
@@ -69,6 +70,12 @@
adb_thread_setname("qemu socket");
D("transport: qemu_socket_thread() starting");
+ std::string error;
+ int port = get_host_socket_spec_port(addr, &error);
+ if (port == -1) {
+ port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
+ }
+
/* adb QEMUD service connection request. */
snprintf(con_name, sizeof(con_name), "pipe:qemud:adb:%d", port);
@@ -78,7 +85,7 @@
/* This could be an older version of the emulator, that doesn't
* implement adb QEMUD service. Fall back to the old TCP way. */
D("adb service is not available. Falling back to TCP socket.");
- std::thread(server_socket_thread, tcp_listen_inaddr_any, port).detach();
+ std::thread(server_socket_thread, adb_listen, addr).detach();
return;
}
diff --git a/adb/daemon/usb_ffs.cpp b/adb/daemon/usb_ffs.cpp
index 338d776..b19fa5d 100644
--- a/adb/daemon/usb_ffs.cpp
+++ b/adb/daemon/usb_ffs.cpp
@@ -84,7 +84,7 @@
using usb_os_desc_guid_t = usb_os_desc_ext_prop<20, 39>;
usb_os_desc_guid_t os_desc_guid = {
.bPropertyName = "DeviceInterfaceGUID",
- .bProperty = "{64379D6C-D531-4BED-BBEC-5A16FC07D6BC}",
+ .bProperty = "{F72FE0D4-CBCB-407D-8814-9ED673D0DD6B}",
};
struct usb_ext_prop_values {
diff --git a/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp b/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
index 3dc5e50..932d579 100644
--- a/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
+++ b/adb/fastdeploy/deploypatchgenerator/apk_archive.cpp
@@ -36,7 +36,7 @@
FileRegion(borrowed_fd fd, off64_t offset, size_t length)
: mapped_(android::base::MappedFile::FromOsHandle(adb_get_os_handle(fd), offset, length,
PROT_READ)) {
- if (mapped_.data() != nullptr) {
+ if (mapped_ != nullptr) {
return;
}
@@ -50,14 +50,14 @@
}
}
- const char* data() const { return mapped_.data() ? mapped_.data() : buffer_.data(); }
- size_t size() const { return mapped_.data() ? mapped_.size() : buffer_.size(); }
+ const char* data() const { return mapped_ ? mapped_->data() : buffer_.data(); }
+ size_t size() const { return mapped_ ? mapped_->size() : buffer_.size(); }
private:
FileRegion() = default;
DISALLOW_COPY_AND_ASSIGN(FileRegion);
- android::base::MappedFile mapped_;
+ std::unique_ptr<android::base::MappedFile> mapped_;
std::string buffer_;
};
} // namespace
diff --git a/adb/file_sync_protocol.h b/adb/file_sync_protocol.h
index 108639a..508c138 100644
--- a/adb/file_sync_protocol.h
+++ b/adb/file_sync_protocol.h
@@ -21,10 +21,14 @@
#define ID_LSTAT_V1 MKID('S', 'T', 'A', 'T')
#define ID_STAT_V2 MKID('S', 'T', 'A', '2')
#define ID_LSTAT_V2 MKID('L', 'S', 'T', '2')
-#define ID_LIST MKID('L', 'I', 'S', 'T')
+
+#define ID_LIST_V1 MKID('L', 'I', 'S', 'T')
+#define ID_LIST_V2 MKID('L', 'I', 'S', '2')
+#define ID_DENT_V1 MKID('D', 'E', 'N', 'T')
+#define ID_DENT_V2 MKID('D', 'N', 'T', '2')
+
#define ID_SEND MKID('S', 'E', 'N', 'D')
#define ID_RECV MKID('R', 'E', 'C', 'V')
-#define ID_DENT MKID('D', 'E', 'N', 'T')
#define ID_DONE MKID('D', 'O', 'N', 'E')
#define ID_DATA MKID('D', 'A', 'T', 'A')
#define ID_OKAY MKID('O', 'K', 'A', 'Y')
@@ -42,7 +46,7 @@
uint32_t id;
uint32_t mode;
uint32_t size;
- uint32_t time;
+ uint32_t mtime;
} stat_v1;
struct __attribute__((packed)) {
uint32_t id;
@@ -62,17 +66,32 @@
uint32_t id;
uint32_t mode;
uint32_t size;
- uint32_t time;
+ uint32_t mtime;
uint32_t namelen;
- } dent;
+ } dent_v1; // followed by `namelen` bytes of the name.
+ struct __attribute__((packed)) {
+ uint32_t id;
+ uint32_t error;
+ uint64_t dev;
+ uint64_t ino;
+ uint32_t mode;
+ uint32_t nlink;
+ uint32_t uid;
+ uint32_t gid;
+ uint64_t size;
+ int64_t atime;
+ int64_t mtime;
+ int64_t ctime;
+ uint32_t namelen;
+ } dent_v2; // followed by `namelen` bytes of the name.
struct __attribute__((packed)) {
uint32_t id;
uint32_t size;
- } data;
+ } data; // followed by `size` bytes of data.
struct __attribute__((packed)) {
uint32_t id;
uint32_t msglen;
- } status;
+ } status; // followed by `msglen` bytes of error message, if id == ID_FAIL.
};
#define SYNC_DATA_MAX (64 * 1024)
diff --git a/adb/socket_spec.cpp b/adb/socket_spec.cpp
index 9ce443e..d17036c 100644
--- a/adb/socket_spec.cpp
+++ b/adb/socket_spec.cpp
@@ -122,6 +122,41 @@
return true;
}
+int get_host_socket_spec_port(std::string_view spec, std::string* error) {
+ int port;
+ if (spec.starts_with("tcp:")) {
+ if (!parse_tcp_socket_spec(spec, nullptr, &port, nullptr, error)) {
+ return -1;
+ }
+ } else if (spec.starts_with("vsock:")) {
+#if ADB_LINUX
+ std::string spec_str(spec);
+ std::vector<std::string> fragments = android::base::Split(spec_str, ":");
+ if (fragments.size() != 2) {
+ *error = "given vsock server socket string was invalid";
+ return -1;
+ }
+ if (!android::base::ParseInt(fragments[1], &port)) {
+ *error = "could not parse vsock port";
+ errno = EINVAL;
+ return -1;
+ }
+ if (port < 0) {
+ *error = "vsock port was negative.";
+ errno = EINVAL;
+ return -1;
+ }
+#else // ADB_LINUX
+ *error = "vsock is only supported on linux";
+ return -1;
+#endif // ADB_LINUX
+ } else {
+ *error = "given socket spec string was invalid";
+ return -1;
+ }
+ return port;
+}
+
static bool tcp_host_is_local(std::string_view hostname) {
// FIXME
return hostname.empty() || hostname == "localhost";
@@ -254,6 +289,14 @@
fd->reset(network_local_client(&address[prefix.length()], it.second.socket_namespace,
SOCK_STREAM, error));
+
+ if (fd->get() < 0) {
+ *error =
+ android::base::StringPrintf("could not connect to %s address '%s'",
+ it.first.c_str(), std::string(address).c_str());
+ return false;
+ }
+
if (serial) {
*serial = address;
}
@@ -275,7 +318,11 @@
}
int result;
+#if ADB_HOST
if (hostname.empty() && gListenAll) {
+#else
+ if (hostname.empty()) {
+#endif
result = network_inaddr_any_server(port, SOCK_STREAM, error);
} else if (tcp_host_is_local(hostname)) {
result = network_loopback_server(port, SOCK_STREAM, error, true);
diff --git a/adb/socket_spec.h b/adb/socket_spec.h
index 7cc2fac..94719c8 100644
--- a/adb/socket_spec.h
+++ b/adb/socket_spec.h
@@ -31,3 +31,5 @@
bool parse_tcp_socket_spec(std::string_view spec, std::string* hostname, int* port,
std::string* serial, std::string* error);
+
+int get_host_socket_spec_port(std::string_view spec, std::string* error);
diff --git a/adb/socket_spec_test.cpp b/adb/socket_spec_test.cpp
index 3a2f60c..e9d5270 100644
--- a/adb/socket_spec_test.cpp
+++ b/adb/socket_spec_test.cpp
@@ -18,6 +18,10 @@
#include <string>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
#include <gtest/gtest.h>
TEST(socket_spec, parse_tcp_socket_spec_just_port) {
@@ -88,3 +92,63 @@
EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]:", &hostname, &port, &serial, &error));
EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]:-1", &hostname, &port, &serial, &error));
}
+
+TEST(socket_spec, get_host_socket_spec_port) {
+ std::string error;
+ EXPECT_EQ(5555, get_host_socket_spec_port("tcp:5555", &error));
+ EXPECT_EQ(5555, get_host_socket_spec_port("tcp:localhost:5555", &error));
+ EXPECT_EQ(5555, get_host_socket_spec_port("tcp:[::1]:5555", &error));
+ EXPECT_EQ(5555, get_host_socket_spec_port("vsock:5555", &error));
+}
+
+TEST(socket_spec, get_host_socket_spec_port_no_port) {
+ std::string error;
+ EXPECT_EQ(5555, get_host_socket_spec_port("tcp:localhost", &error));
+ EXPECT_EQ(-1, get_host_socket_spec_port("vsock:localhost", &error));
+}
+
+TEST(socket_spec, get_host_socket_spec_port_bad_ports) {
+ std::string error;
+ EXPECT_EQ(-1, get_host_socket_spec_port("tcp:65536", &error));
+ EXPECT_EQ(-1, get_host_socket_spec_port("tcp:-5", &error));
+ EXPECT_EQ(-1, get_host_socket_spec_port("vsock:-5", &error));
+ EXPECT_EQ(-1, get_host_socket_spec_port("vsock:5:5555", &error));
+}
+
+TEST(socket_spec, get_host_socket_spec_port_bad_string) {
+ std::string error;
+ EXPECT_EQ(-1, get_host_socket_spec_port("tcpz:5555", &error));
+ EXPECT_EQ(-1, get_host_socket_spec_port("vsockz:5555", &error));
+ EXPECT_EQ(-1, get_host_socket_spec_port("abcd:5555", &error));
+ EXPECT_EQ(-1, get_host_socket_spec_port("abcd", &error));
+}
+
+TEST(socket_spec, socket_spec_listen_connect_tcp) {
+ std::string error, serial;
+ int port;
+ unique_fd server_fd, client_fd;
+ EXPECT_FALSE(socket_spec_connect(&client_fd, "tcp:localhost:7777", &port, &serial, &error));
+ server_fd.reset(socket_spec_listen("tcp:7777", &error, &port));
+ EXPECT_NE(server_fd.get(), -1);
+ EXPECT_TRUE(socket_spec_connect(&client_fd, "tcp:localhost:7777", &port, &serial, &error));
+ EXPECT_NE(client_fd.get(), -1);
+}
+
+TEST(socket_spec, socket_spec_listen_connect_localfilesystem) {
+ std::string error, serial;
+ int port;
+ unique_fd server_fd, client_fd;
+ TemporaryDir sock_dir;
+
+ // Only run this test if the created directory is writable.
+ int result = access(sock_dir.path, W_OK);
+ if (result == 0) {
+ std::string sock_addr =
+ android::base::StringPrintf("localfilesystem:%s/af_unix_socket", sock_dir.path);
+ EXPECT_FALSE(socket_spec_connect(&client_fd, sock_addr, &port, &serial, &error));
+ server_fd.reset(socket_spec_listen(sock_addr, &error, &port));
+ EXPECT_NE(server_fd.get(), -1);
+ EXPECT_TRUE(socket_spec_connect(&client_fd, sock_addr, &port, &serial, &error));
+ EXPECT_NE(client_fd.get(), -1);
+ }
+}
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 7d5bf17..423af67 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -625,7 +625,8 @@
return true;
};
- static constexpr std::string_view prefixes[] = {"usb:", "product:", "model:", "device:"};
+ static constexpr std::string_view prefixes[] = {
+ "usb:", "product:", "model:", "device:", "localfilesystem:"};
for (std::string_view prefix : prefixes) {
if (command.starts_with(prefix)) {
consume(prefix.size());
diff --git a/adb/test_device.py b/adb/test_device.py
index 57925e8..083adce 100755
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -536,6 +536,36 @@
for i, success in result.iteritems():
self.assertTrue(success)
+ def disabled_test_parallel(self):
+ """Spawn a bunch of `adb shell` instances in parallel.
+
+ This was broken historically due to the use of select, which only works
+ for fds that are numerically less than 1024.
+
+ Bug: http://b/141955761"""
+
+ n_procs = 2048
+ procs = dict()
+ for i in xrange(0, n_procs):
+ procs[i] = subprocess.Popen(
+ ['adb', 'shell', 'read foo; echo $foo; read rc; exit $rc'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE
+ )
+
+ for i in xrange(0, n_procs):
+ procs[i].stdin.write("%d\n" % i)
+
+ for i in xrange(0, n_procs):
+ response = procs[i].stdout.readline()
+ assert(response == "%d\n" % i)
+
+ for i in xrange(0, n_procs):
+ procs[i].stdin.write("%d\n" % (i % 256))
+
+ for i in xrange(0, n_procs):
+ assert(procs[i].wait() == i % 256)
+
class ArgumentEscapingTest(DeviceTest):
def test_shell_escaping(self):
diff --git a/adb/transport.cpp b/adb/transport.cpp
index d9749ac..9dd6ec6 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -55,7 +55,7 @@
using android::base::ScopedLockAssertion;
static void remove_transport(atransport* transport);
-static void transport_unref(atransport* transport);
+static void transport_destroy(atransport* transport);
// TODO: unordered_map<TransportId, atransport*>
static auto& transport_list = *new std::list<atransport*>();
@@ -66,6 +66,7 @@
const char* const kFeatureShell2 = "shell_v2";
const char* const kFeatureCmd = "cmd";
const char* const kFeatureStat2 = "stat_v2";
+const char* const kFeatureLs2 = "ls_v2";
const char* const kFeatureLibusb = "libusb";
const char* const kFeaturePushSync = "push_sync";
const char* const kFeatureApex = "apex";
@@ -676,7 +677,6 @@
if (t->GetConnectionState() != kCsNoPerm) {
// The connection gets a reference to the atransport. It will release it
// upon a read/write error.
- t->ref_count++;
t->connection()->SetTransportName(t->serial_name());
t->connection()->SetReadCallback([t](Connection*, std::unique_ptr<apacket> p) {
if (!check_header(p.get(), t)) {
@@ -695,7 +695,7 @@
LOG(INFO) << t->serial_name() << ": connection terminated: " << error;
fdevent_run_on_main_thread([t]() {
handle_offline(t);
- transport_unref(t);
+ transport_destroy(t);
});
});
@@ -771,36 +771,27 @@
}
}
-static void transport_unref(atransport* t) {
+static void transport_destroy(atransport* t) {
check_main_thread();
CHECK(t != nullptr);
std::lock_guard<std::recursive_mutex> lock(transport_lock);
- CHECK_GT(t->ref_count, 0u);
- t->ref_count--;
- if (t->ref_count == 0) {
- LOG(INFO) << "destroying transport " << t->serial_name();
- t->connection()->Stop();
+ LOG(INFO) << "destroying transport " << t->serial_name();
+ t->connection()->Stop();
#if ADB_HOST
- if (t->IsTcpDevice() && !t->kicked()) {
- D("transport: %s unref (attempting reconnection)", t->serial.c_str());
+ if (t->IsTcpDevice() && !t->kicked()) {
+ D("transport: %s destroy (attempting reconnection)", t->serial.c_str());
- // We need to clear the transport's keys, so that on the next connection, it tries
- // again from the beginning.
- t->ResetKeys();
- reconnect_handler.TrackTransport(t);
- } else {
- D("transport: %s unref (kicking and closing)", t->serial.c_str());
- remove_transport(t);
- }
-#else
- D("transport: %s unref (kicking and closing)", t->serial.c_str());
- remove_transport(t);
+ // We need to clear the transport's keys, so that on the next connection, it tries
+ // again from the beginning.
+ t->ResetKeys();
+ reconnect_handler.TrackTransport(t);
+ return;
+ }
#endif
- } else {
- D("transport: %s unref (count=%zu)", t->serial.c_str(), t->ref_count);
- }
+ D("transport: %s destroy (kicking and closing)", t->serial.c_str());
+ remove_transport(t);
}
static int qual_match(const std::string& to_test, const char* prefix, const std::string& qual,
@@ -1045,6 +1036,7 @@
kFeatureShell2,
kFeatureCmd,
kFeatureStat2,
+ kFeatureLs2,
kFeatureFixedPushMkdir,
kFeatureApex,
kFeatureAbb,
diff --git a/adb/transport.h b/adb/transport.h
index 89d76b8..5a750ee 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -38,6 +38,7 @@
#include "adb.h"
#include "adb_unique_fd.h"
+#include "types.h"
#include "usb.h"
typedef std::unordered_set<std::string> FeatureSet;
@@ -57,6 +58,7 @@
// The 'cmd' command is available
extern const char* const kFeatureCmd;
extern const char* const kFeatureStat2;
+extern const char* const kFeatureLs2;
// The server is running with libusb enabled.
extern const char* const kFeatureLibusb;
// adbd supports `push --sync`.
@@ -222,7 +224,7 @@
Abort,
};
-class atransport {
+class atransport : public enable_weak_from_this<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
@@ -245,7 +247,7 @@
}
atransport(ConnectionState state = kCsOffline)
: atransport([](atransport*) { return ReconnectResult::Abort; }, state) {}
- virtual ~atransport();
+ ~atransport();
int Write(apacket* p);
void Reset();
@@ -266,7 +268,7 @@
usb_handle* GetUsbHandle() { return usb_handle_; }
const TransportId id;
- size_t ref_count = 0;
+
bool online = false;
TransportType type = kTransportAny;
@@ -423,11 +425,12 @@
asocket* create_device_tracker(bool long_output);
#if !ADB_HOST
-unique_fd tcp_listen_inaddr_any(int port, std::string* error);
-void server_socket_thread(std::function<unique_fd(int, std::string*)> listen_func, int port);
+unique_fd adb_listen(std::string_view addr, std::string* error);
+void server_socket_thread(std::function<unique_fd(std::string_view, std::string*)> listen_func,
+ std::string_view addr);
#if defined(__ANDROID__)
-void qemu_socket_thread(int port);
+void qemu_socket_thread(std::string_view addr);
bool use_qemu_goldfish();
#endif
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index b9f738d..c726186 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -85,22 +85,6 @@
return local_connect_arbitrary_ports(port - 1, port, &dummy) == 0;
}
-std::tuple<unique_fd, int, std::string> tcp_connect(const std::string& address,
- std::string* response) {
- unique_fd fd;
- int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
- std::string serial;
- std::string prefix_addr = address.starts_with("vsock:") ? address : "tcp:" + address;
- if (socket_spec_connect(&fd, prefix_addr, &port, &serial, response)) {
- close_on_exec(fd);
- if (!set_tcp_keepalive(fd, 1)) {
- D("warning: failed to configure TCP keepalives (%s)", strerror(errno));
- }
- return std::make_tuple(std::move(fd), port, serial);
- }
- return std::make_tuple(unique_fd(), 0, serial);
-}
-
void connect_device(const std::string& address, std::string* response) {
if (address.empty()) {
*response = "empty address";
@@ -110,17 +94,25 @@
D("connection requested to '%s'", address.c_str());
unique_fd fd;
int port;
- std::string serial;
- std::tie(fd, port, serial) = tcp_connect(address, response);
+ std::string serial, prefix_addr;
+
+ // If address does not match any socket type, it should default to TCP.
+ if (address.starts_with("vsock:") || address.starts_with("localfilesystem:")) {
+ prefix_addr = address;
+ } else {
+ prefix_addr = "tcp:" + address;
+ }
+
+ socket_spec_connect(&fd, prefix_addr, &port, &serial, response);
if (fd.get() == -1) {
return;
}
- auto reconnect = [address](atransport* t) {
+ auto reconnect = [prefix_addr](atransport* t) {
std::string response;
unique_fd fd;
int port;
std::string serial;
- std::tie(fd, port, serial) = tcp_connect(address, &response);
+ socket_spec_connect(&fd, prefix_addr, &port, &serial, &response);
if (fd == -1) {
D("reconnect failed: %s", response.c_str());
return ReconnectResult::Retry;
@@ -203,7 +195,7 @@
std::mutex &retry_ports_lock = *new std::mutex;
std::condition_variable &retry_ports_cond = *new std::condition_variable;
-static void client_socket_thread(int) {
+static void client_socket_thread(std::string_view) {
adb_thread_setname("client_socket_thread");
D("transport: client_socket_thread() starting");
PollAllLocalPortsForEmulator();
@@ -248,7 +240,8 @@
#else // !ADB_HOST
-void server_socket_thread(std::function<unique_fd(int, std::string*)> listen_func, int port) {
+void server_socket_thread(std::function<unique_fd(std::string_view, std::string*)> listen_func,
+ std::string_view addr) {
adb_thread_setname("server socket");
unique_fd serverfd;
@@ -256,7 +249,7 @@
while (serverfd == -1) {
errno = 0;
- serverfd = listen_func(port, &error);
+ serverfd = listen_func(addr, &error);
if (errno == EAFNOSUPPORT || errno == EINVAL || errno == EPROTONOSUPPORT) {
D("unrecoverable error: '%s'", error.c_str());
return;
@@ -276,7 +269,9 @@
close_on_exec(fd.get());
disable_tcp_nagle(fd.get());
std::string serial = android::base::StringPrintf("host-%d", fd.get());
- register_socket_transport(std::move(fd), std::move(serial), port, 1,
+ // We don't care about port value in "register_socket_transport" as it is used
+ // only from ADB_HOST. "server_socket_thread" is never called from ADB_HOST.
+ register_socket_transport(std::move(fd), std::move(serial), 0, 1,
[](atransport*) { return ReconnectResult::Abort; });
}
}
@@ -285,38 +280,30 @@
#endif
-unique_fd tcp_listen_inaddr_any(int port, std::string* error) {
- return unique_fd{network_inaddr_any_server(port, SOCK_STREAM, error)};
-}
-
#if !ADB_HOST
-static unique_fd vsock_listen(int port, std::string* error) {
- return unique_fd{
- socket_spec_listen(android::base::StringPrintf("vsock:%d", port), error, nullptr)
- };
+unique_fd adb_listen(std::string_view addr, std::string* error) {
+ return unique_fd{socket_spec_listen(addr, error, nullptr)};
}
#endif
-void local_init(int port) {
+void local_init(const std::string& addr) {
#if ADB_HOST
D("transport: local client init");
- std::thread(client_socket_thread, port).detach();
+ std::thread(client_socket_thread, addr).detach();
adb_local_transport_max_port_env_override();
#elif !defined(__ANDROID__)
// Host adbd.
D("transport: local server init");
- std::thread(server_socket_thread, tcp_listen_inaddr_any, port).detach();
- std::thread(server_socket_thread, vsock_listen, port).detach();
+ std::thread(server_socket_thread, adb_listen, addr).detach();
#else
D("transport: local server init");
// For the adbd daemon in the system image we need to distinguish
// between the device, and the emulator.
- if (use_qemu_goldfish()) {
- std::thread(qemu_socket_thread, port).detach();
+ if (addr.starts_with("tcp:") && use_qemu_goldfish()) {
+ std::thread(qemu_socket_thread, addr).detach();
} else {
- std::thread(server_socket_thread, tcp_listen_inaddr_any, port).detach();
+ std::thread(server_socket_thread, adb_listen, addr).detach();
}
- std::thread(server_socket_thread, vsock_listen, port).detach();
#endif // !ADB_HOST
}
diff --git a/adb/types.h b/adb/types.h
index 6b00224..c619fff 100644
--- a/adb/types.h
+++ b/adb/types.h
@@ -25,6 +25,7 @@
#include <android-base/logging.h>
+#include "fdevent/fdevent.h"
#include "sysdeps/uio.h"
// Essentially std::vector<char>, except without zero initialization or reallocation.
@@ -245,3 +246,97 @@
size_t start_index_ = 0;
std::vector<block_type> chain_;
};
+
+// An implementation of weak pointers tied to the fdevent run loop.
+//
+// This allows for code to submit a request for an object, and upon receiving
+// a response, know whether the object is still alive, or has been destroyed
+// because of other reasons. We keep a list of living weak_ptrs in each object,
+// and clear the weak_ptrs when the object is destroyed. This is safe, because
+// we require that both the destructor of the referent and the get method on
+// the weak_ptr are executed on the main thread.
+template <typename T>
+struct enable_weak_from_this;
+
+template <typename T>
+struct weak_ptr {
+ weak_ptr() = default;
+ explicit weak_ptr(T* ptr) { reset(ptr); }
+ weak_ptr(const weak_ptr& copy) { reset(copy.get()); }
+
+ weak_ptr(weak_ptr&& move) {
+ reset(move.get());
+ move.reset();
+ }
+
+ ~weak_ptr() { reset(); }
+
+ weak_ptr& operator=(const weak_ptr& copy) {
+ if (© == this) {
+ return *this;
+ }
+
+ reset(copy.get());
+ return *this;
+ }
+
+ weak_ptr& operator=(weak_ptr&& move) {
+ if (&move == this) {
+ return *this;
+ }
+
+ reset(move.get());
+ move.reset();
+ return *this;
+ }
+
+ T* get() {
+ check_main_thread();
+ return ptr_;
+ }
+
+ void reset(T* ptr = nullptr) {
+ check_main_thread();
+
+ if (ptr == ptr_) {
+ return;
+ }
+
+ if (ptr_) {
+ ptr_->weak_ptrs_.erase(
+ std::remove(ptr_->weak_ptrs_.begin(), ptr_->weak_ptrs_.end(), this));
+ }
+
+ ptr_ = ptr;
+ if (ptr_) {
+ ptr_->weak_ptrs_.push_back(this);
+ }
+ }
+
+ private:
+ friend struct enable_weak_from_this<T>;
+ T* ptr_ = nullptr;
+};
+
+template <typename T>
+struct enable_weak_from_this {
+ ~enable_weak_from_this() {
+ if (!weak_ptrs_.empty()) {
+ check_main_thread();
+ for (auto& weak : weak_ptrs_) {
+ weak->ptr_ = nullptr;
+ }
+ weak_ptrs_.clear();
+ }
+ }
+
+ weak_ptr<T> weak() { return weak_ptr<T>(static_cast<T*>(this)); }
+
+ void schedule_deletion() {
+ fdevent_run_on_main_thread([this]() { delete this; });
+ }
+
+ private:
+ friend struct weak_ptr<T>;
+ std::vector<weak_ptr<T>*> weak_ptrs_;
+};
diff --git a/base/include/android-base/mapped_file.h b/base/include/android-base/mapped_file.h
index 6a19f1b..8c37f43 100644
--- a/base/include/android-base/mapped_file.h
+++ b/base/include/android-base/mapped_file.h
@@ -53,7 +53,8 @@
/**
* Same thing, but using the raw OS file handle instead of a CRT wrapper.
*/
- static MappedFile FromOsHandle(os_handle h, off64_t offset, size_t length, int prot);
+ static std::unique_ptr<MappedFile> FromOsHandle(os_handle h, off64_t offset, size_t length,
+ int prot);
/**
* Removes the mapping.
@@ -69,10 +70,6 @@
char* data() const { return base_ + offset_; }
size_t size() const { return size_; }
- bool isValid() const { return base_ != nullptr; }
-
- explicit operator bool() const { return isValid(); }
-
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(MappedFile);
diff --git a/base/include/android-base/test_utils.h b/base/include/android-base/test_utils.h
index b20f278..f3d7cb0 100644
--- a/base/include/android-base/test_utils.h
+++ b/base/include/android-base/test_utils.h
@@ -53,30 +53,34 @@
CapturedStdout() : CapturedStdFd(STDOUT_FILENO) {}
};
-#define ASSERT_MATCH(str, pattern) \
- do { \
- if (!std::regex_search((str), std::regex((pattern)))) { \
- FAIL() << "regex mismatch: expected " << (pattern) << " in:\n" << (str); \
- } \
+#define ASSERT_MATCH(str, pattern) \
+ do { \
+ auto __s = (str); \
+ if (!std::regex_search(__s, std::regex((pattern)))) { \
+ FAIL() << "regex mismatch: expected " << (pattern) << " in:\n" << __s; \
+ } \
} while (0)
-#define ASSERT_NOT_MATCH(str, pattern) \
- do { \
- if (std::regex_search((str), std::regex((pattern)))) { \
- FAIL() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << (str); \
- } \
+#define ASSERT_NOT_MATCH(str, pattern) \
+ do { \
+ auto __s = (str); \
+ if (std::regex_search(__s, std::regex((pattern)))) { \
+ FAIL() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << __s; \
+ } \
} while (0)
-#define EXPECT_MATCH(str, pattern) \
- do { \
- if (!std::regex_search((str), std::regex((pattern)))) { \
- ADD_FAILURE() << "regex mismatch: expected " << (pattern) << " in:\n" << (str); \
- } \
+#define EXPECT_MATCH(str, pattern) \
+ do { \
+ auto __s = (str); \
+ if (!std::regex_search(__s, std::regex((pattern)))) { \
+ ADD_FAILURE() << "regex mismatch: expected " << (pattern) << " in:\n" << __s; \
+ } \
} while (0)
-#define EXPECT_NOT_MATCH(str, pattern) \
- do { \
- if (std::regex_search((str), std::regex((pattern)))) { \
- ADD_FAILURE() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << (str); \
- } \
+#define EXPECT_NOT_MATCH(str, pattern) \
+ do { \
+ auto __s = (str); \
+ if (std::regex_search(__s, std::regex((pattern)))) { \
+ ADD_FAILURE() << "regex mismatch: expected to not find " << (pattern) << " in:\n" << __s; \
+ } \
} while (0)
diff --git a/base/mapped_file.cpp b/base/mapped_file.cpp
index 862b73b..fff3453 100644
--- a/base/mapped_file.cpp
+++ b/base/mapped_file.cpp
@@ -38,15 +38,14 @@
std::unique_ptr<MappedFile> MappedFile::FromFd(borrowed_fd fd, off64_t offset, size_t length,
int prot) {
#if defined(_WIN32)
- auto file =
- FromOsHandle(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), offset, length, prot);
+ return FromOsHandle(reinterpret_cast<HANDLE>(_get_osfhandle(fd.get())), offset, length, prot);
#else
- auto file = FromOsHandle(fd.get(), offset, length, prot);
+ return FromOsHandle(fd.get(), offset, length, prot);
#endif
- return file ? std::make_unique<MappedFile>(std::move(file)) : std::unique_ptr<MappedFile>{};
}
-MappedFile MappedFile::FromOsHandle(os_handle h, off64_t offset, size_t length, int prot) {
+std::unique_ptr<MappedFile> MappedFile::FromOsHandle(os_handle h, off64_t offset, size_t length,
+ int prot) {
static const off64_t page_size = InitPageSize();
size_t slop = offset % page_size;
off64_t file_offset = offset - slop;
@@ -59,28 +58,30 @@
// http://b/119818070 "app crashes when reading asset of zero length".
// Return a MappedFile that's only valid for reading the size.
if (length == 0 && ::GetLastError() == ERROR_FILE_INVALID) {
- return MappedFile{const_cast<char*>(kEmptyBuffer), 0, 0, nullptr};
+ return std::unique_ptr<MappedFile>(
+ new MappedFile(const_cast<char*>(kEmptyBuffer), 0, 0, nullptr));
}
- return MappedFile(nullptr, 0, 0, nullptr);
+ return nullptr;
}
void* base = MapViewOfFile(handle, (prot & PROT_WRITE) ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ, 0,
file_offset, file_length);
if (base == nullptr) {
CloseHandle(handle);
- return MappedFile(nullptr, 0, 0, nullptr);
+ return nullptr;
}
- return MappedFile{static_cast<char*>(base), length, slop, handle};
+ return std::unique_ptr<MappedFile>(
+ new MappedFile(static_cast<char*>(base), length, slop, handle));
#else
void* base = mmap(nullptr, file_length, prot, MAP_SHARED, h, file_offset);
if (base == MAP_FAILED) {
// http://b/119818070 "app crashes when reading asset of zero length".
// mmap fails with EINVAL for a zero length region.
if (errno == EINVAL && length == 0) {
- return MappedFile{const_cast<char*>(kEmptyBuffer), 0, 0};
+ return std::unique_ptr<MappedFile>(new MappedFile(const_cast<char*>(kEmptyBuffer), 0, 0));
}
- return MappedFile(nullptr, 0, 0);
+ return nullptr;
}
- return MappedFile{static_cast<char*>(base), length, slop};
+ return std::unique_ptr<MappedFile>(new MappedFile(static_cast<char*>(base), length, slop));
#endif
}
diff --git a/base/mapped_file_test.cpp b/base/mapped_file_test.cpp
index 3629108..d21703c 100644
--- a/base/mapped_file_test.cpp
+++ b/base/mapped_file_test.cpp
@@ -44,8 +44,6 @@
ASSERT_TRUE(tf.fd != -1);
auto m = android::base::MappedFile::FromFd(tf.fd, 4096, 0, PROT_READ);
- ASSERT_NE(nullptr, m);
- EXPECT_TRUE((bool)*m);
EXPECT_EQ(0u, m->size());
EXPECT_NE(nullptr, m->data());
}
diff --git a/cli-test/.clang-format b/cli-test/.clang-format
new file mode 120000
index 0000000..fd0645f
--- /dev/null
+++ b/cli-test/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2
\ No newline at end of file
diff --git a/cli-test/Android.bp b/cli-test/Android.bp
new file mode 100644
index 0000000..37a1d1b
--- /dev/null
+++ b/cli-test/Android.bp
@@ -0,0 +1,7 @@
+cc_binary {
+ name: "cli-test",
+ host_supported: true,
+ srcs: ["cli-test.cpp"],
+ cflags: ["-Wall", "-Werror"],
+ shared_libs: ["libbase"],
+}
diff --git a/cli-test/README.md b/cli-test/README.md
new file mode 100644
index 0000000..643eb74
--- /dev/null
+++ b/cli-test/README.md
@@ -0,0 +1,90 @@
+# cli-test
+
+## What?
+
+`cli-test` makes integration testing of command-line tools easier.
+
+## Goals
+
+* Readable syntax. Common cases should be concise, and pretty much anyone
+ should be able to read tests even if they've never seen this tool before.
+
+* Minimal issues with quoting. The toybox tests -- being shell scripts --
+ quickly become a nightmare of quoting. Using a non ad hoc format (such as
+ JSON) would have introduced similar but different quoting issues. A custom
+ format, while annoying, side-steps this.
+
+* Sensible defaults. We expect your exit status to be 0 unless you say
+ otherwise. We expect nothing on stderr unless you say otherwise. And so on.
+
+* Convention over configuration. Related to sensible defaults, we don't let you
+ configure things that aren't absolutely necessary. So you can't keep your test
+ data anywhere except in the `files/` subdirectory of the directory containing
+ your test, for example.
+
+## Non Goals
+
+* Portability. Just being able to run on Linux (host and device) is sufficient
+ for our needs. macOS is probably easy enough if we ever need it, but Windows
+ probably doesn't make sense.
+
+## Syntax
+
+Any all-whitespace line, or line starting with `#` is ignored.
+
+A test looks like this:
+```
+name: unzip -l
+command: unzip -l $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/x.txt ]
+expected-stdout:
+ Archive: $FILES/example.zip
+ Length Date Time Name
+ --------- ---------- ----- ----
+ 1024 2017-06-04 08:45 d1/d2/x.txt
+ --------- -------
+ 1024 1 file
+---
+```
+
+The `name:` line names the test, and is only for human consumption.
+
+The `command:` line is the command to be run. Additional commands can be
+supplied as zero or more `before:` lines (run before `command:`) and zero or
+more `after:` lines (run after `command:`). These are useful for both
+setup/teardown but also for testing post conditions (as in the example above).
+
+Any `command:`, `before:`, or `after:` line is expected to exit with status 0.
+Anything else is considered a test failure.
+
+The `expected-stdout:` line is followed by zero or more tab-prefixed lines that
+are otherwise the exact output expected from the command. (There's magic behind
+the scenes to rewrite the test files directory to `$FILES` because otherwise any
+path in the output would depend on the temporary directory used to run the test.)
+
+There is currently no `expected-stderr:` line. Standard error is implicitly
+expected to be empty, and any output will cause a test failure. (The support is
+there, but not wired up because we haven't needed it yet.)
+
+The fields can appear in any order, but every test must contain at least a
+`name:` line and a `command:` line.
+
+## Output
+
+The output is intended to resemble gtest.
+
+## Future Directions
+
+* It's often useful to be able to *match* against stdout/stderr/a file rather
+ than give exact expected output. We might want to add explicit support for
+ this. In the meantime, it's possible to use an `after:` with `grep -q` if
+ you redirect in your `command:`.
+
+* In addition to using a `before:` (which will fail a test), it can be useful
+ to be able to specify tests that would cause us to *skip* a test. An example
+ would be "am I running as root?".
+
+* It might be useful to be able to make exit status assertions other than 0?
+
+* There's currently no way (other than the `files/` directory) to share repeated
+ setup between tests.
diff --git a/cli-test/cli-test.cpp b/cli-test/cli-test.cpp
new file mode 100644
index 0000000..d6e27ee
--- /dev/null
+++ b/cli-test/cli-test.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+
+// Example:
+
+// name: unzip -n
+// before: mkdir -p d1/d2
+// before: echo b > d1/d2/a.txt
+// command: unzip -q -n $FILES/zip/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+// expected-stdout:
+// b
+
+struct Test {
+ std::string test_filename;
+ std::string name;
+ std::string command;
+ std::vector<std::string> befores;
+ std::vector<std::string> afters;
+ std::string expected_stdout;
+ std::string expected_stderr;
+ int exit_status = 0;
+};
+
+static const char* g_progname;
+static bool g_verbose;
+
+static const char* g_file;
+static size_t g_line;
+
+enum Color { kRed, kGreen };
+
+static void Print(Color c, const char* lhs, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ if (isatty(0)) printf("%s", (c == kRed) ? "\e[31m" : "\e[32m");
+ printf("%s%s", lhs, isatty(0) ? "\e[0m" : "");
+ vfprintf(stdout, fmt, ap);
+ putchar('\n');
+ va_end(ap);
+}
+
+static void Die(int error, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s: ", g_progname);
+ vfprintf(stderr, fmt, ap);
+ if (error != 0) fprintf(stderr, ": %s", strerror(error));
+ fprintf(stderr, "\n");
+ va_end(ap);
+ _exit(1);
+}
+
+static void V(const char* fmt, ...) {
+ if (!g_verbose) return;
+
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(stderr, " - ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+}
+
+static void SetField(const char* what, std::string* field, std::string_view value) {
+ if (!field->empty()) {
+ Die(0, "%s:%zu: %s already set to '%s'", g_file, g_line, what, field->c_str());
+ }
+ field->assign(value);
+}
+
+// Similar to ConsumePrefix, but also trims, so "key:value" and "key: value"
+// are equivalent.
+static bool Match(std::string* s, const std::string& prefix) {
+ if (!android::base::StartsWith(*s, prefix)) return false;
+ s->assign(android::base::Trim(s->substr(prefix.length())));
+ return true;
+}
+
+static void CollectTests(std::vector<Test>* tests, const char* test_filename) {
+ std::string absolute_test_filename;
+ if (!android::base::Realpath(test_filename, &absolute_test_filename)) {
+ Die(errno, "realpath '%s'", test_filename);
+ }
+
+ std::string content;
+ if (!android::base::ReadFileToString(test_filename, &content)) {
+ Die(errno, "couldn't read '%s'", test_filename);
+ }
+
+ size_t count = 0;
+ g_file = test_filename;
+ g_line = 0;
+ auto lines = android::base::Split(content, "\n");
+ std::unique_ptr<Test> test(new Test);
+ while (g_line < lines.size()) {
+ auto line = lines[g_line++];
+ if (line.empty() || line[0] == '#') continue;
+
+ if (line[0] == '-') {
+ if (test->name.empty() || test->command.empty()) {
+ Die(0, "%s:%zu: each test requires both a name and a command", g_file, g_line);
+ }
+ test->test_filename = absolute_test_filename;
+ tests->push_back(*test.release());
+ test.reset(new Test);
+ ++count;
+ } else if (Match(&line, "name:")) {
+ SetField("name", &test->name, line);
+ } else if (Match(&line, "command:")) {
+ SetField("command", &test->command, line);
+ } else if (Match(&line, "before:")) {
+ test->befores.push_back(line);
+ } else if (Match(&line, "after:")) {
+ test->afters.push_back(line);
+ } else if (Match(&line, "expected-stdout:")) {
+ // Collect tab-indented lines.
+ std::string text;
+ while (g_line < lines.size() && !lines[g_line].empty() && lines[g_line][0] == '\t') {
+ text += lines[g_line++].substr(1) + "\n";
+ }
+ SetField("expected stdout", &test->expected_stdout, text);
+ } else {
+ Die(0, "%s:%zu: syntax error: \"%s\"", g_file, g_line, line.c_str());
+ }
+ }
+ if (count == 0) Die(0, "no tests found in '%s'", g_file);
+}
+
+static const char* Plural(size_t n) {
+ return (n == 1) ? "" : "s";
+}
+
+static std::string ExitStatusToString(int status) {
+ if (WIFSIGNALED(status)) {
+ return android::base::StringPrintf("was killed by signal %d (%s)", WTERMSIG(status),
+ strsignal(WTERMSIG(status)));
+ }
+ if (WIFSTOPPED(status)) {
+ return android::base::StringPrintf("was stopped by signal %d (%s)", WSTOPSIG(status),
+ strsignal(WSTOPSIG(status)));
+ }
+ return android::base::StringPrintf("exited with status %d", WEXITSTATUS(status));
+}
+
+static bool RunCommands(const char* what, const std::vector<std::string>& commands) {
+ bool result = true;
+ for (auto& command : commands) {
+ V("running %s \"%s\"", what, command.c_str());
+ int exit_status = system(command.c_str());
+ if (exit_status != 0) {
+ result = false;
+ fprintf(stderr, "Command (%s) \"%s\" %s\n", what, command.c_str(),
+ ExitStatusToString(exit_status).c_str());
+ }
+ }
+ return result;
+}
+
+static bool CheckOutput(const char* what, std::string actual_output,
+ const std::string& expected_output, const std::string& FILES) {
+ // Rewrite the output to reverse any expansion of $FILES.
+ actual_output = android::base::StringReplace(actual_output, FILES, "$FILES", true);
+
+ bool result = (actual_output == expected_output);
+ if (!result) {
+ fprintf(stderr, "Incorrect %s.\nExpected:\n%s\nActual:\n%s\n", what, expected_output.c_str(),
+ actual_output.c_str());
+ }
+ return result;
+}
+
+static int RunTests(const std::vector<Test>& tests) {
+ std::vector<std::string> failures;
+
+ Print(kGreen, "[==========]", " Running %zu tests.", tests.size());
+ android::base::Timer total_timer;
+ for (const auto& test : tests) {
+ bool failed = false;
+
+ Print(kGreen, "[ RUN ]", " %s", test.name.c_str());
+ android::base::Timer test_timer;
+
+ // Set $FILES for this test.
+ std::string FILES = android::base::Dirname(test.test_filename) + "/files";
+ V("setenv(\"FILES\", \"%s\")", FILES.c_str());
+ setenv("FILES", FILES.c_str(), 1);
+
+ // Make a safe space to run the test.
+ TemporaryDir td;
+ V("chdir(\"%s\")", td.path);
+ if (chdir(td.path)) Die(errno, "chdir(\"%s\")", td.path);
+
+ // Perform any setup specified for this test.
+ if (!RunCommands("before", test.befores)) failed = true;
+
+ if (!failed) {
+ V("running command \"%s\"", test.command.c_str());
+ CapturedStdout test_stdout;
+ CapturedStderr test_stderr;
+ int exit_status = system(test.command.c_str());
+ test_stdout.Stop();
+ test_stderr.Stop();
+
+ V("exit status %d", exit_status);
+ if (exit_status != test.exit_status) {
+ failed = true;
+ fprintf(stderr, "Incorrect exit status: expected %d but %s\n", test.exit_status,
+ ExitStatusToString(exit_status).c_str());
+ }
+
+ if (!CheckOutput("stdout", test_stdout.str(), test.expected_stdout, FILES)) failed = true;
+ if (!CheckOutput("stderr", test_stderr.str(), test.expected_stderr, FILES)) failed = true;
+
+ if (!RunCommands("after", test.afters)) failed = true;
+ }
+
+ std::stringstream duration;
+ duration << test_timer;
+ if (failed) {
+ failures.push_back(test.name);
+ Print(kRed, "[ FAILED ]", " %s (%s)", test.name.c_str(), duration.str().c_str());
+ } else {
+ Print(kGreen, "[ OK ]", " %s (%s)", test.name.c_str(), duration.str().c_str());
+ }
+ }
+
+ // Summarize the whole run and explicitly list all the failures.
+
+ std::stringstream duration;
+ duration << total_timer;
+ Print(kGreen, "[==========]", " %zu tests ran. (%s total)", tests.size(), duration.str().c_str());
+
+ size_t fail_count = failures.size();
+ size_t pass_count = tests.size() - fail_count;
+ Print(kGreen, "[ PASSED ]", " %zu test%s.", pass_count, Plural(pass_count));
+ if (!failures.empty()) {
+ Print(kRed, "[ FAILED ]", " %zu test%s.", fail_count, Plural(fail_count));
+ for (auto& failure : failures) {
+ Print(kRed, "[ FAILED ]", " %s", failure.c_str());
+ }
+ }
+ return (fail_count == 0) ? 0 : 1;
+}
+
+static void ShowHelp(bool full) {
+ fprintf(full ? stdout : stderr, "usage: %s [-v] FILE...\n", g_progname);
+ if (!full) exit(EXIT_FAILURE);
+
+ printf(
+ "\n"
+ "Run tests.\n"
+ "\n"
+ "-v\tVerbose (show workings)\n");
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char* argv[]) {
+ g_progname = basename(argv[0]);
+
+ static const struct option opts[] = {
+ {"help", no_argument, 0, 'h'},
+ {"verbose", no_argument, 0, 'v'},
+ {},
+ };
+
+ int opt;
+ while ((opt = getopt_long(argc, argv, "hv", opts, nullptr)) != -1) {
+ switch (opt) {
+ case 'h':
+ ShowHelp(true);
+ break;
+ case 'v':
+ g_verbose = true;
+ break;
+ default:
+ ShowHelp(false);
+ break;
+ }
+ }
+
+ argv += optind;
+ if (!*argv) Die(0, "no test files provided");
+ std::vector<Test> tests;
+ for (; *argv; ++argv) CollectTests(&tests, *argv);
+ return RunTests(tests);
+}
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 0602e0a..780e48d 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -74,6 +74,7 @@
header_libs: [
"libbase_headers",
"libdebuggerd_common_headers",
+ "bionic_libc_platform_headers",
],
whole_static_libs: [
@@ -92,6 +93,9 @@
defaults: ["debuggerd_defaults"],
srcs: ["handler/debuggerd_fallback_nop.cpp"],
+ header_libs: ["bionic_libc_platform_headers"],
+ export_header_lib_headers: ["bionic_libc_platform_headers"],
+
whole_static_libs: [
"libdebuggerd_handler_core",
],
@@ -119,6 +123,10 @@
"liblzma",
"libcutils",
],
+
+ header_libs: ["bionic_libc_platform_headers"],
+ export_header_lib_headers: ["bionic_libc_platform_headers"],
+
target: {
recovery: {
exclude_static_libs: [
@@ -138,15 +146,21 @@
"util.cpp",
],
- header_libs: ["libdebuggerd_common_headers"],
-
shared_libs: [
"libbase",
"libcutils",
"libprocinfo",
],
- export_header_lib_headers: ["libdebuggerd_common_headers"],
+ header_libs: [
+ "libdebuggerd_common_headers",
+ "bionic_libc_platform_headers",
+ ],
+ export_header_lib_headers: [
+ "libdebuggerd_common_headers",
+ "bionic_libc_platform_headers",
+ ],
+
export_include_dirs: ["include"],
}
@@ -167,6 +181,7 @@
// Needed for private/bionic_fdsan.h
include_dirs: ["bionic/libc"],
+ header_libs: ["bionic_libc_platform_headers"],
static_libs: [
"libdexfile_support_static", // libunwindstack dependency
@@ -176,6 +191,7 @@
"libcutils",
"liblog",
],
+
target: {
recovery: {
exclude_static_libs: [
@@ -232,6 +248,10 @@
"libdebuggerd",
],
+ header_libs: [
+ "bionic_libc_platform_headers",
+ ],
+
local_include_dirs: [
"libdebuggerd",
],
@@ -277,6 +297,10 @@
},
},
+ header_libs: [
+ "bionic_libc_platform_headers",
+ ],
+
static_libs: [
"libtombstoned_client_static",
"libdebuggerd",
@@ -317,7 +341,10 @@
],
defaults: ["debuggerd_defaults"],
- header_libs: ["libdebuggerd_common_headers"],
+ header_libs: [
+ "bionic_libc_platform_headers",
+ "libdebuggerd_common_headers"
+ ],
static_libs: [
"libbase",
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 7e35a2f..5c02738 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -35,6 +35,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <bionic/reserved_signals.h>
#include <cutils/sockets.h>
#include <procinfo/process.h>
@@ -50,7 +51,7 @@
using android::base::WriteStringToFd;
static bool send_signal(pid_t pid, const DebuggerdDumpType dump_type) {
- const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : DEBUGGER_SIGNAL;
+ const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
sigval val;
val.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0;
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index cb55745..e8f366f 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -40,6 +40,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <bionic/reserved_signals.h>
#include <cutils/sockets.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
@@ -511,13 +512,13 @@
// Defer the message until later, for readability.
bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false);
- if (siginfo.si_signo == DEBUGGER_SIGNAL) {
+ if (siginfo.si_signo == BIONIC_SIGNAL_DEBUGGER) {
wait_for_gdb = false;
}
// Detach from all of our attached threads before resuming.
for (const auto& [tid, thread] : thread_info) {
- int resume_signal = thread.signo == DEBUGGER_SIGNAL ? 0 : thread.signo;
+ int resume_signal = thread.signo == BIONIC_SIGNAL_DEBUGGER ? 0 : thread.signo;
if (wait_for_gdb) {
resume_signal = 0;
if (tgkill(target_process, tid, SIGSTOP) != 0) {
@@ -555,10 +556,10 @@
<< " (target tid = " << g_target_thread << ")";
int signo = siginfo.si_signo;
- bool fatal_signal = signo != DEBUGGER_SIGNAL;
+ bool fatal_signal = signo != BIONIC_SIGNAL_DEBUGGER;
bool backtrace = false;
- // si_value is special when used with DEBUGGER_SIGNAL.
+ // si_value is special when used with BIONIC_SIGNAL_DEBUGGER.
// 0: dump tombstone
// 1: dump backtrace
if (!fatal_signal) {
diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp
index 7bec470..e86f499 100644
--- a/debuggerd/crasher/Android.bp
+++ b/debuggerd/crasher/Android.bp
@@ -44,6 +44,7 @@
name: "crasher",
defaults: ["crasher-defaults"],
+ header_libs: ["bionic_libc_platform_headers"],
shared_libs: [
"libbase",
"liblog",
@@ -65,6 +66,7 @@
defaults: ["crasher-defaults"],
cppflags: ["-DSTATIC_CRASHER"],
static_executable: true,
+ header_libs: ["bionic_libc_platform_headers"],
static_libs: [
"libdebuggerd_handler",
"libbase",
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 99729dc..6a8cc56 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -31,6 +31,7 @@
#include <android/fdsan.h>
#include <android/set_abort_message.h>
+#include <bionic/reserved_signals.h>
#include <android-base/cmsg.h>
#include <android-base/file.h>
@@ -398,7 +399,7 @@
unique_fd output_fd;
StartProcess([]() {
android_set_abort_message("not actually aborting");
- raise(DEBUGGER_SIGNAL);
+ raise(BIONIC_SIGNAL_DEBUGGER);
exit(0);
});
StartIntercept(&output_fd);
@@ -466,7 +467,7 @@
sigval val;
val.sival_int = 1;
- ASSERT_EQ(0, sigqueue(crasher_pid, DEBUGGER_SIGNAL, val)) << strerror(errno);
+ ASSERT_EQ(0, sigqueue(crasher_pid, BIONIC_SIGNAL_DEBUGGER, val)) << strerror(errno);
FinishIntercept(&intercept_result);
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
ConsumeFd(std::move(output_fd), &result);
@@ -734,7 +735,7 @@
siginfo.si_value.sival_int = dump_type == kDebuggerdNativeBacktrace;
- if (syscall(__NR_rt_tgsigqueueinfo, getpid(), gettid(), DEBUGGER_SIGNAL, &siginfo) != 0) {
+ if (syscall(__NR_rt_tgsigqueueinfo, getpid(), gettid(), BIONIC_SIGNAL_DEBUGGER, &siginfo) != 0) {
PLOG(ERROR) << "libdebuggerd_client: failed to send signal to self";
return false;
}
@@ -887,7 +888,7 @@
errx(2, "first waitpid returned %d (%s), expected failure with ECHILD", rc, strerror(errno));
}
- raise(DEBUGGER_SIGNAL);
+ raise(BIONIC_SIGNAL_DEBUGGER);
errno = 0;
rc = TEMP_FAILURE_RETRY(waitpid(-1, &status, __WALL | __WNOTHREAD));
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp
index bbec612..9bcbdb3 100644
--- a/debuggerd/handler/debuggerd_fallback.cpp
+++ b/debuggerd/handler/debuggerd_fallback.cpp
@@ -42,6 +42,7 @@
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <async_safe/log.h>
+#include <bionic/reserved_signals.h>
#include <unwindstack/DexFiles.h>
#include <unwindstack/JitDebug.h>
#include <unwindstack/Maps.h>
@@ -272,7 +273,7 @@
siginfo.si_pid = getpid();
siginfo.si_uid = getuid();
- if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, DEBUGGER_SIGNAL, &siginfo) != 0) {
+ if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, BIONIC_SIGNAL_DEBUGGER, &siginfo) != 0) {
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s",
tid, strerror(errno));
return false;
@@ -340,7 +341,7 @@
extern "C" void debuggerd_fallback_handler(siginfo_t* info, ucontext_t* ucontext,
void* abort_message) {
- if (info->si_signo == DEBUGGER_SIGNAL && info->si_value.sival_ptr != nullptr) {
+ if (info->si_signo == BIONIC_SIGNAL_DEBUGGER && info->si_value.sival_ptr != nullptr) {
return trace_handler(info, ucontext);
} else {
return crash_handler(info, ucontext, abort_message);
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index b90ca80..6e01289 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -51,6 +51,7 @@
#include <android-base/unique_fd.h>
#include <async_safe/log.h>
+#include <bionic/reserved_signals.h>
#include <cutils/properties.h>
#include <libdebuggerd/utility.h>
@@ -175,7 +176,7 @@
thread_name[MAX_TASK_NAME_LEN] = 0;
}
- if (info->si_signo == DEBUGGER_SIGNAL) {
+ if (info->si_signo == BIONIC_SIGNAL_DEBUGGER) {
async_safe_format_log(ANDROID_LOG_INFO, "libc", "Requested dump for tid %d (%s)", __gettid(),
thread_name);
return;
@@ -307,7 +308,7 @@
static void* pseudothread_stack;
static DebuggerdDumpType get_dump_type(const debugger_thread_info* thread_info) {
- if (thread_info->siginfo->si_signo == DEBUGGER_SIGNAL &&
+ if (thread_info->siginfo->si_signo == BIONIC_SIGNAL_DEBUGGER &&
thread_info->siginfo->si_value.sival_int) {
return kDebuggerdNativeBacktrace;
}
@@ -429,7 +430,7 @@
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper crashed or stopped");
}
- if (thread_info->siginfo->si_signo != DEBUGGER_SIGNAL) {
+ if (thread_info->siginfo->si_signo != BIONIC_SIGNAL_DEBUGGER) {
// For crashes, we don't need to minimize pause latency.
// Wait for the dump to complete before having the process exit, to avoid being murdered by
// ActivityManager or init.
@@ -446,7 +447,7 @@
// exited with the correct exit status (e.g. so that sh will report
// "Segmentation fault" instead of "Killed"). For this to work, we need
// to deregister our signal handler for that signal before continuing.
- if (info->si_signo != DEBUGGER_SIGNAL) {
+ if (info->si_signo != BIONIC_SIGNAL_DEBUGGER) {
signal(info->si_signo, SIG_DFL);
int rc = syscall(SYS_rt_tgsigqueueinfo, __getpid(), __gettid(), info->si_signo, info);
if (rc != 0) {
@@ -485,7 +486,7 @@
void* abort_message = nullptr;
uintptr_t si_val = reinterpret_cast<uintptr_t>(info->si_ptr);
- if (signal_number == DEBUGGER_SIGNAL) {
+ if (signal_number == BIONIC_SIGNAL_DEBUGGER) {
if (info->si_code == SI_QUEUE && info->si_pid == __getpid()) {
// Allow for the abort message to be explicitly specified via the sigqueue value.
// Keep the bottom bit intact for representing whether we want a backtrace or a tombstone.
@@ -576,7 +577,7 @@
fatal_errno("failed to restore traceable");
}
- if (info->si_signo == DEBUGGER_SIGNAL) {
+ if (info->si_signo == BIONIC_SIGNAL_DEBUGGER) {
// If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
// starting to dump right before our death.
pthread_mutex_unlock(&crash_mutex);
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 7196e0a..cd6fc05 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -16,6 +16,7 @@
#pragma once
+#include <bionic/reserved_signals.h>
#include <signal.h>
#include <stdint.h>
#include <sys/cdefs.h>
@@ -33,11 +34,11 @@
void debuggerd_init(debuggerd_callbacks_t* callbacks);
// DEBUGGER_ACTION_DUMP_TOMBSTONE and DEBUGGER_ACTION_DUMP_BACKTRACE are both
-// triggered via DEBUGGER_SIGNAL. The debugger_action_t is sent via si_value
+// triggered via BIONIC_SIGNAL_DEBUGGER. The debugger_action_t is sent via si_value
// using sigqueue(2) or equivalent. If no si_value is specified (e.g. if the
// signal is sent by kill(2)), the default behavior is to print the backtrace
// to the log.
-#define DEBUGGER_SIGNAL (__SIGRTMIN + 3)
+#define DEBUGGER_SIGNAL BIONIC_SIGNAL_DEBUGGER
static void __attribute__((__unused__)) debuggerd_register_handlers(struct sigaction* action) {
sigaction(SIGABRT, action, nullptr);
@@ -50,7 +51,7 @@
#endif
sigaction(SIGSYS, action, nullptr);
sigaction(SIGTRAP, action, nullptr);
- sigaction(DEBUGGER_SIGNAL, action, nullptr);
+ sigaction(BIONIC_SIGNAL_DEBUGGER, action, nullptr);
}
__END_DECLS
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 236fcf7..b64e260 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -52,9 +52,6 @@
#include <unwindstack/Regs.h>
#include <unwindstack/Unwinder.h>
-// Needed to get DEBUGGER_SIGNAL.
-#include "debuggerd/handler.h"
-
#include "libdebuggerd/backtrace.h"
#include "libdebuggerd/open_files_list.h"
#include "libdebuggerd/utility.h"
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 5ce26fc..0a1d2a4 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -35,6 +35,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <bionic/reserved_signals.h>
#include <debuggerd/handler.h>
#include <log/log.h>
#include <unwindstack/Memory.h>
@@ -296,7 +297,8 @@
case SIGSTOP: return "SIGSTOP";
case SIGSYS: return "SIGSYS";
case SIGTRAP: return "SIGTRAP";
- case DEBUGGER_SIGNAL: return "<debuggerd signal>";
+ case BIONIC_SIGNAL_DEBUGGER:
+ return "<debuggerd signal>";
default: return "?";
}
}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index cbd42b1..7fdc28b 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1226,7 +1226,7 @@
std::string merge_status = "none";
if (fb->GetVar(FB_VAR_SNAPSHOT_UPDATE_STATUS, &merge_status) == fastboot::SUCCESS &&
merge_status != "none") {
- fb->SnapshotUpdateCommand("Cancel");
+ fb->SnapshotUpdateCommand("cancel");
}
}
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index eb737bb..34c64d2 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -26,14 +26,14 @@
],
}
-cc_library {
- // Do not ever allow this library to be vendor_available as a shared library.
- // It does not have a stable interface.
- name: "libfs_mgr",
+cc_defaults {
+ name: "libfs_mgr_defaults",
defaults: ["fs_mgr_defaults"],
- recovery_available: true,
export_include_dirs: ["include"],
include_dirs: ["system/vold"],
+ cflags: [
+ "-D_FILE_OFFSET_BITS=64",
+ ],
srcs: [
"file_wait.cpp",
"fs_mgr.cpp",
@@ -43,6 +43,7 @@
"fs_mgr_overlayfs.cpp",
"fs_mgr_roots.cpp",
"fs_mgr_vendor_overlay.cpp",
+ ":libfiemap_srcs",
],
shared_libs: [
"libbase",
@@ -88,6 +89,42 @@
],
},
},
+ header_libs: [
+ "libfiemap_headers",
+ ],
+ export_header_lib_headers: [
+ "libfiemap_headers",
+ ],
+}
+
+// Two variants of libfs_mgr are provided: libfs_mgr and libfs_mgr_binder.
+// Use libfs_mgr in recovery, first-stage-init, or when libfiemap or overlayfs
+// is not used.
+//
+// Use libfs_mgr_binder when not in recovery/first-stage init, or when overlayfs
+// or libfiemap is needed. In this case, libfiemap will proxy over binder to
+// gsid.
+cc_library {
+ // Do not ever allow this library to be vendor_available as a shared library.
+ // It does not have a stable interface.
+ name: "libfs_mgr",
+ recovery_available: true,
+ defaults: [
+ "libfs_mgr_defaults",
+ ],
+ srcs: [
+ ":libfiemap_passthrough_srcs",
+ ],
+}
+
+cc_library {
+ // Do not ever allow this library to be vendor_available as a shared library.
+ // It does not have a stable interface.
+ name: "libfs_mgr_binder",
+ defaults: [
+ "libfs_mgr_defaults",
+ "libfiemap_binder_defaults",
+ ],
}
cc_library_static {
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 7b686f0..15c9dfb 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -34,6 +34,7 @@
#include <time.h>
#include <unistd.h>
+#include <chrono>
#include <functional>
#include <map>
#include <memory>
@@ -42,6 +43,7 @@
#include <utility>
#include <vector>
+#include <android-base/chrono_utils.h>
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
@@ -94,6 +96,7 @@
using android::base::GetBoolProperty;
using android::base::Realpath;
using android::base::StartsWith;
+using android::base::Timer;
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
@@ -1358,24 +1361,47 @@
return ret;
}
-static std::string GetBlockDeviceForMountPoint(const std::string& mount_point) {
- Fstab mounts;
- if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
- LERROR << "Can't read /proc/mounts";
- return "";
+static bool fs_mgr_unmount_all_data_mounts(const std::string& block_device) {
+ Timer t;
+ // TODO(b/135984674): should be configured via a read-only property.
+ std::chrono::milliseconds timeout = 5s;
+ while (true) {
+ bool umount_done = true;
+ Fstab proc_mounts;
+ if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) {
+ LERROR << "Can't read /proc/mounts";
+ return false;
+ }
+ // Now proceed with other bind mounts on top of /data.
+ for (const auto& entry : proc_mounts) {
+ if (entry.blk_device == block_device) {
+ if (umount2(entry.mount_point.c_str(), 0) != 0) {
+ umount_done = false;
+ }
+ }
+ }
+ if (umount_done) {
+ LINFO << "Unmounting /data took " << t;
+ return true;
+ }
+ if (t.duration() > timeout) {
+ return false;
+ }
+ std::this_thread::sleep_for(50ms);
}
- auto entry = GetEntryForMountPoint(&mounts, mount_point);
- if (entry == nullptr) {
- LWARNING << mount_point << " is not mounted";
- return "";
- }
- return entry->blk_device;
}
// TODO(b/143970043): return different error codes based on which step failed.
int fs_mgr_remount_userdata_into_checkpointing(Fstab* fstab) {
- std::string block_device = GetBlockDeviceForMountPoint("/data");
- if (block_device.empty()) {
+ Fstab proc_mounts;
+ if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) {
+ LERROR << "Can't read /proc/mounts";
+ return -1;
+ }
+ std::string block_device;
+ if (auto entry = GetEntryForMountPoint(&proc_mounts, "/data"); entry != nullptr) {
+ block_device = entry->blk_device;
+ } else {
LERROR << "/data is not mounted";
return -1;
}
@@ -1408,8 +1434,8 @@
}
} else {
LINFO << "Unmounting /data before remounting into checkpointing mode";
- if (umount2("/data", UMOUNT_NOFOLLOW) != 0) {
- PERROR << "Failed to umount /data";
+ if (!fs_mgr_unmount_all_data_mounts(block_device)) {
+ LERROR << "Failed to umount /data";
return -1;
}
DeviceMapper& dm = DeviceMapper::Instance();
diff --git a/fs_mgr/fs_mgr_dm_linear.cpp b/fs_mgr/fs_mgr_dm_linear.cpp
index 0dcb9fe..ea9c957 100644
--- a/fs_mgr/fs_mgr_dm_linear.cpp
+++ b/fs_mgr/fs_mgr_dm_linear.cpp
@@ -151,6 +151,10 @@
LINFO << "Skipping zero-length logical partition: " << GetPartitionName(partition);
continue;
}
+ if (partition.attributes & LP_PARTITION_ATTR_DISABLED) {
+ LINFO << "Skipping disabled partition: " << GetPartitionName(partition);
+ continue;
+ }
params.partition = &partition;
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 05f3311..c043754 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -440,13 +440,9 @@
rmdir(kScratchMountPoint.c_str());
}
-// reduce 'DM_DEV_STATUS failed for scratch: No such device or address' noise
-std::string scratch_device_cache;
-
bool fs_mgr_overlayfs_teardown_scratch(const std::string& overlay, bool* change) {
// umount and delete kScratchMountPoint storage if we have logical partitions
if (overlay != kScratchMountPoint) return true;
- scratch_device_cache.erase();
auto slot_number = fs_mgr_overlayfs_slot_number();
auto super_device = fs_mgr_overlayfs_super_device(slot_number);
if (!fs_mgr_rw_access(super_device)) return true;
@@ -815,26 +811,48 @@
return "auto";
}
-std::string fs_mgr_overlayfs_scratch_device() {
- if (!scratch_device_cache.empty()) return scratch_device_cache;
-
- // Is this a multiple super device (retrofit)?
+// Note: we do not check access() here except for the super partition, since
+// in first-stage init we wouldn't have registed by-name symlinks for "other"
+// partitions that won't be mounted.
+static std::string GetPhysicalScratchDevice() {
auto slot_number = fs_mgr_overlayfs_slot_number();
auto super_device = fs_mgr_overlayfs_super_device(slot_number);
auto path = fs_mgr_overlayfs_super_device(slot_number == 0);
- if (super_device == path) {
- // Create from within single super device;
- auto& dm = DeviceMapper::Instance();
- const auto partition_name = android::base::Basename(kScratchMountPoint);
- if (!dm.GetDmDevicePathByName(partition_name, &path)) {
- // non-DAP A/B device?
- if (fs_mgr_access(super_device)) return "";
- auto other_slot = fs_mgr_get_other_slot_suffix();
- if (other_slot.empty()) return "";
- path = kPhysicalDevice + "system" + other_slot;
- }
+ if (super_device != path) {
+ return path;
}
- return scratch_device_cache = path;
+ if (fs_mgr_access(super_device)) {
+ // Do not try to use system_other on a DAP device.
+ return "";
+ }
+
+ auto other_slot = fs_mgr_get_other_slot_suffix();
+ if (!other_slot.empty()) {
+ return kPhysicalDevice + "system" + other_slot;
+ }
+ return "";
+}
+
+// This returns the scratch device that was detected during early boot (first-
+// stage init). If the device was created later, for example during setup for
+// the adb remount command, it can return an empty string since it does not
+// query ImageManager.
+static std::string GetBootScratchDevice() {
+ auto& dm = DeviceMapper::Instance();
+
+ // If there is a scratch partition allocated in /data or on super, we
+ // automatically prioritize that over super_other or system_other.
+ // Some devices, for example, have a write-protected eMMC and the
+ // super partition cannot be used even if it exists.
+ std::string device;
+ auto partition_name = android::base::Basename(kScratchMountPoint);
+ if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
+ dm.GetDmDevicePathByName(partition_name, &device)) {
+ return device;
+ }
+
+ // There is no dynamic scratch, so try and find a physical one.
+ return GetPhysicalScratchDevice();
}
bool fs_mgr_overlayfs_make_scratch(const std::string& scratch_device, const std::string& mnt_type) {
@@ -878,26 +896,22 @@
}
}
-// This is where we find and steal backing storage from the system.
-bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
- bool* partition_exists, bool* change) {
- *scratch_device = fs_mgr_overlayfs_scratch_device();
- *partition_exists = fs_mgr_rw_access(*scratch_device);
+// Create or update a scratch partition within super.
+static bool CreateDynamicScratch(std::string* scratch_device, bool* partition_exists,
+ bool* change) {
+ const auto partition_name = android::base::Basename(kScratchMountPoint);
+
+ auto& dm = DeviceMapper::Instance();
+ *partition_exists = dm.GetState(partition_name) != DmDeviceState::INVALID;
+
auto partition_create = !*partition_exists;
- // Do we need to create a logical "scratch" partition?
- if (!partition_create && android::base::StartsWith(*scratch_device, kPhysicalDevice)) {
- return true;
- }
auto slot_number = fs_mgr_overlayfs_slot_number();
auto super_device = fs_mgr_overlayfs_super_device(slot_number);
- if (!fs_mgr_rw_access(super_device)) return false;
- if (!fs_mgr_overlayfs_has_logical(fstab)) return false;
auto builder = MetadataBuilder::New(super_device, slot_number);
if (!builder) {
LERROR << "open " << super_device << " metadata";
return false;
}
- const auto partition_name = android::base::Basename(kScratchMountPoint);
auto partition = builder->FindPartition(partition_name);
*partition_exists = partition != nullptr;
auto changed = false;
@@ -978,6 +992,33 @@
return true;
}
+static bool CanUseSuperPartition(const Fstab& fstab) {
+ auto slot_number = fs_mgr_overlayfs_slot_number();
+ auto super_device = fs_mgr_overlayfs_super_device(slot_number);
+ if (!fs_mgr_rw_access(super_device) || !fs_mgr_overlayfs_has_logical(fstab)) {
+ return false;
+ }
+ return true;
+}
+
+bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
+ bool* partition_exists, bool* change) {
+ // Try a physical partition first.
+ *scratch_device = GetPhysicalScratchDevice();
+ if (!scratch_device->empty() && fs_mgr_rw_access(*scratch_device)) {
+ *partition_exists = true;
+ return true;
+ }
+
+ // If that fails, see if we can land on super.
+ if (CanUseSuperPartition(fstab)) {
+ return CreateDynamicScratch(scratch_device, partition_exists, change);
+ }
+
+ errno = ENXIO;
+ return false;
+}
+
// Create and mount kScratchMountPoint storage if we have logical partitions
bool fs_mgr_overlayfs_setup_scratch(const Fstab& fstab, bool* change) {
if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) return true;
@@ -1067,7 +1108,12 @@
}
static void TryMountScratch() {
- auto scratch_device = fs_mgr_overlayfs_scratch_device();
+ // Note we get the boot scratch device here, which means if scratch was
+ // just created through ImageManager, this could fail. In practice this
+ // should not happen because "remount" detects this scenario (by checking
+ // if verity is still disabled, i.e. no reboot occurred), and skips calling
+ // fs_mgr_overlayfs_mount_all().
+ auto scratch_device = GetBootScratchDevice();
if (!fs_mgr_overlayfs_scratch_can_be_mounted(scratch_device)) {
return;
}
@@ -1113,11 +1159,23 @@
return {};
}
+ bool want_scratch = false;
for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
- if (fs_mgr_is_verity_enabled(entry)) continue;
- if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) continue;
- auto device = fs_mgr_overlayfs_scratch_device();
- if (!fs_mgr_overlayfs_scratch_can_be_mounted(device)) break;
+ if (fs_mgr_is_verity_enabled(entry)) {
+ continue;
+ }
+ if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) {
+ continue;
+ }
+ want_scratch = true;
+ break;
+ }
+ if (!want_scratch) {
+ return {};
+ }
+
+ auto device = GetBootScratchDevice();
+ if (!device.empty()) {
return {device};
}
return {};
@@ -1188,29 +1246,57 @@
return ret;
}
+static bool GetAndMapScratchDeviceIfNeeded(std::string* device, bool* mapped) {
+ *mapped = false;
+ *device = GetBootScratchDevice();
+ if (!device->empty()) {
+ return true;
+ }
+
+ // Avoid uart spam by first checking for a scratch partition.
+ auto metadata_slot = fs_mgr_overlayfs_slot_number();
+ auto super_device = fs_mgr_overlayfs_super_device(metadata_slot);
+ auto metadata = ReadCurrentMetadata(super_device);
+ if (!metadata) {
+ return false;
+ }
+
+ auto partition_name = android::base::Basename(kScratchMountPoint);
+ auto partition = FindPartition(*metadata.get(), partition_name);
+ if (!partition) {
+ return false;
+ }
+
+ CreateLogicalPartitionParams params = {
+ .block_device = super_device,
+ .metadata = metadata.get(),
+ .partition = partition,
+ .force_writable = true,
+ .timeout_ms = 10s,
+ };
+ if (!CreateLogicalPartition(params, device)) {
+ return false;
+ }
+ *mapped = true;
+ return true;
+}
+
// Returns false if teardown not permitted, errno set to last error.
// If something is altered, set *change.
bool fs_mgr_overlayfs_teardown(const char* mount_point, bool* change) {
if (change) *change = false;
auto ret = true;
+
// If scratch exists, but is not mounted, lets gain access to clean
// specific override entries.
auto mount_scratch = false;
+ bool unmap = false;
if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
- auto scratch_device = fs_mgr_overlayfs_scratch_device();
- if (scratch_device.empty()) {
- auto metadata_slot = fs_mgr_overlayfs_slot_number();
- CreateLogicalPartitionParams params = {
- .block_device = fs_mgr_overlayfs_super_device(metadata_slot),
- .metadata_slot = metadata_slot,
- .partition_name = android::base::Basename(kScratchMountPoint),
- .force_writable = true,
- .timeout_ms = 10s,
- };
- CreateLogicalPartition(params, &scratch_device);
+ std::string scratch_device;
+ if (GetAndMapScratchDeviceIfNeeded(&scratch_device, &unmap)) {
+ mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
+ fs_mgr_overlayfs_scratch_mount_type());
}
- mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
- fs_mgr_overlayfs_scratch_mount_type());
}
for (const auto& overlay_mount_point : kOverlayMountPoints) {
ret &= fs_mgr_overlayfs_teardown_one(
@@ -1229,8 +1315,12 @@
PERROR << "teardown";
ret = false;
}
- if (mount_scratch) fs_mgr_overlayfs_umount_scratch();
-
+ if (mount_scratch) {
+ fs_mgr_overlayfs_umount_scratch();
+ }
+ if (unmap) {
+ DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
+ }
return ret;
}
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 809318c..254fbed 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -575,5 +575,9 @@
return "/dev/block/" + sub_device_name;
}
+bool DeviceMapper::TargetInfo::IsOverflowSnapshot() const {
+ return spec.target_type == "snapshot"s && data == "Overflow"s;
+}
+
} // namespace dm
} // namespace android
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 418210c..abe9c4c 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -205,6 +205,8 @@
TargetInfo() {}
TargetInfo(const struct dm_target_spec& spec, const std::string& data)
: spec(spec), data(data) {}
+
+ bool IsOverflowSnapshot() const;
};
bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table);
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
new file mode 100644
index 0000000..fdc1583
--- /dev/null
+++ b/fs_mgr/libfiemap/Android.bp
@@ -0,0 +1,105 @@
+//
+// 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.
+//
+
+cc_library_headers {
+ name: "libfiemap_headers",
+ recovery_available: true,
+ export_include_dirs: ["include"],
+}
+
+filegroup {
+ name: "libfiemap_srcs",
+ srcs: [
+ "fiemap_writer.cpp",
+ "image_manager.cpp",
+ "metadata.cpp",
+ "split_fiemap_writer.cpp",
+ "utility.cpp",
+ ],
+}
+
+filegroup {
+ name: "libfiemap_binder_srcs",
+ srcs: [
+ "binder.cpp",
+ ],
+}
+
+cc_defaults {
+ name: "libfiemap_binder_defaults",
+ srcs: [":libfiemap_binder_srcs"],
+ whole_static_libs: [
+ "gsi_aidl_interface-cpp",
+ "libgsi",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
+}
+
+// Open up a passthrough IImageManager interface. Use libfiemap_binder whenever
+// possible. This should only be used when binder is not available.
+filegroup {
+ name: "libfiemap_passthrough_srcs",
+ srcs: [
+ "passthrough.cpp",
+ ],
+}
+
+cc_test {
+ name: "fiemap_writer_test",
+ static_libs: [
+ "libbase",
+ "libdm",
+ "libfs_mgr",
+ "liblog",
+ ],
+
+ data: [
+ "testdata/unaligned_file",
+ "testdata/file_4k",
+ "testdata/file_32k",
+ ],
+
+ srcs: [
+ "fiemap_writer_test.cpp",
+ ],
+}
+
+cc_test {
+ name: "fiemap_image_test",
+ static_libs: [
+ "libdm",
+ "libext4_utils",
+ "libfs_mgr",
+ "liblp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ "libcrypto_utils",
+ "libcutils",
+ "liblog",
+ ],
+ srcs: [
+ "image_test.cpp",
+ ],
+}
+
+vts_config {
+ name: "VtsFiemapWriterTest",
+}
diff --git a/fs_mgr/libfiemap/AndroidTest.xml b/fs_mgr/libfiemap/AndroidTest.xml
new file mode 100644
index 0000000..44c80fc
--- /dev/null
+++ b/fs_mgr/libfiemap/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for VTS VtsFiemapWriterTest">
+ <option name="config-descriptor:metadata" key="plan" value="vts-kernel" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+ <option name="abort-on-push-failure" value="false"/>
+ <option name="push-group" value="HostDrivenTest.push"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+ <option name="test-module-name" value="VtsFiemapWriterTest"/>
+ <option name="binary-test-source" value="_32bit::DATA/nativetest/fiemap_writer_test/fiemap_writer_test" />
+ <option name="binary-test-source" value="_64bit::DATA/nativetest64/fiemap_writer_test/fiemap_writer_test" />
+ <option name="binary-test-type" value="gtest"/>
+ <option name="precondition-first-api-level" value="29" />
+ <option name="test-timeout" value="1m"/>
+ </test>
+</configuration>
diff --git a/fs_mgr/libfiemap/README.md b/fs_mgr/libfiemap/README.md
new file mode 100644
index 0000000..62d610a
--- /dev/null
+++ b/fs_mgr/libfiemap/README.md
@@ -0,0 +1,75 @@
+libfiemap
+=============
+
+`libfiemap` is a library for creating block-devices that are backed by
+storage in read-write partitions. It exists primary for gsid. Generally, the
+library works by using `libfiemap_writer` to allocate large files within
+filesystem, and then tracks their extents.
+
+There are three main uses for `libfiemap`:
+ - Creating images that will act as block devices. For example, gsid needs to
+ create a `system_gsi` image to store Dynamic System Updates.
+ - Mapping the image as a block device while /data is mounted. This is fairly
+ tricky and is described in more detail below.
+ - Mapping the image as a block device during first-stage init. This is simple
+ because it uses the same logic from dynamic partitions.
+
+Image creation is done through `SplitFiemap`. Depending on the file system,
+a large image may have to be split into multiple files. On Ext4 the limit is
+16GiB and on FAT32 it's 4GiB. Images are saved into `/data/gsi/<name>/`
+where `<name>` is chosen by the process requesting the image.
+
+At the same time, a file called `/metadata/gsi/<name>/lp_metadata` is created.
+This is a super partition header that allows first-stage init to create dynamic
+partitions from the image files. It also tracks the canonical size of the image,
+since the file size may be larger due to alignment.
+
+Mapping
+-------
+
+It is easy to make block devices out of blocks on `/data` when it is not
+mounted, so first-stage init has no issues mapping dynamic partitions from
+images. After `/data` is mounted however, there are two problems:
+ - `/data` is encrypted.
+ - `/dev/block/by-name/data` may be marked as in-use.
+
+We break the problem down into three scenarios.
+
+### FDE and Metadata Encrypted Devices
+
+When FDE or metadata encryption is used, `/data` is not mounted from
+`/dev/block/by-name/data`. Instead, it is mounted from an intermediate
+`dm-crypt` or `dm-default-key` device. This means the underlying device is
+not marked in use, and we can create new dm-linear devices on top of it.
+
+On these devices, a block device for an image will consist of a single
+device-mapper device with a `dm-linear` table entry for each extent in the
+backing file.
+
+### Unencrypted and FBE-encrypted Devices
+
+When a device is unencrypted, or is encrypted with FBE but not metadata
+encryption, we instead use a loop device with `LOOP_SET_DIRECT_IO` enabled.
+Since `/data/gsi` has encryption disabled, this means the raw blocks will be
+unencrypted as well.
+
+### Split Images
+
+If an image was too large to store a single file on the underlying filesystem,
+on an FBE/unencrypted device we will have multiple loop devices. In this case,
+we create a device-mapper device as well. For each loop device it will have one
+`dm-linear` table entry spanning the length of the device.
+
+State Tracking
+--------------
+
+It's important that we know whether or not an image is currently in-use by a
+block device. It could be catastrophic to write to a dm-linear device if the
+underlying blocks are no longer owned by the original file. Thus, when mapping
+an image, we create a property called `gsid.mapped_image.<name>` and set it to
+the path of the block device.
+
+Additionally, we create a `/metadata/gsi/<subdir>/<name>.status` file. Each
+line in this file denotes a dependency on either a device-mapper node or a loop
+device. When deleting a block device, this file is used to release all
+resources.
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
new file mode 100644
index 0000000..f99055a
--- /dev/null
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -0,0 +1,258 @@
+//
+// Copyright (C) 2019 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.
+//
+
+#if !defined(__ANDROID_RECOVERY__)
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/gsi/IGsiService.h>
+#include <android/gsi/IGsid.h>
+#include <binder/IServiceManager.h>
+#include <libfiemap/image_manager.h>
+#include <libgsi/libgsi.h>
+
+namespace android {
+namespace fiemap {
+
+using namespace android::gsi;
+using namespace std::chrono_literals;
+
+class ImageManagerBinder final : public IImageManager {
+ public:
+ ImageManagerBinder(android::sp<IGsiService>&& service, android::sp<IImageService>&& manager);
+ bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
+ bool DeleteBackingImage(const std::string& name) override;
+ bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+ std::string* path) override;
+ bool UnmapImageDevice(const std::string& name) override;
+ bool BackingImageExists(const std::string& name) override;
+ bool IsImageMapped(const std::string& name) override;
+ bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+ std::string* dev) override;
+ bool ZeroFillNewImage(const std::string& name, uint64_t bytes) override;
+ bool RemoveAllImages() override;
+ bool DisableImage(const std::string& name) override;
+ bool RemoveDisabledImages() override;
+ bool GetMappedImageDevice(const std::string& name, std::string* device) override;
+ bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override;
+
+ std::vector<std::string> GetAllBackingImages() override;
+
+ private:
+ android::sp<IGsiService> service_;
+ android::sp<IImageService> manager_;
+};
+
+ImageManagerBinder::ImageManagerBinder(android::sp<IGsiService>&& service,
+ android::sp<IImageService>&& manager)
+ : service_(std::move(service)), manager_(std::move(manager)) {}
+
+bool ImageManagerBinder::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
+ auto status = manager_->createBackingImage(name, size, flags);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return true;
+}
+
+bool ImageManagerBinder::DeleteBackingImage(const std::string& name) {
+ auto status = manager_->deleteBackingImage(name);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return true;
+}
+
+bool ImageManagerBinder::MapImageDevice(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms,
+ std::string* path) {
+ int32_t timeout_ms_count =
+ static_cast<int32_t>(std::clamp<typename std::chrono::milliseconds::rep>(
+ timeout_ms.count(), INT32_MIN, INT32_MAX));
+ MappedImage map;
+ auto status = manager_->mapImageDevice(name, timeout_ms_count, &map);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ *path = map.path;
+ return true;
+}
+
+bool ImageManagerBinder::UnmapImageDevice(const std::string& name) {
+ auto status = manager_->unmapImageDevice(name);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return true;
+}
+
+bool ImageManagerBinder::BackingImageExists(const std::string& name) {
+ bool retval;
+ auto status = manager_->backingImageExists(name, &retval);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return retval;
+}
+
+bool ImageManagerBinder::IsImageMapped(const std::string& name) {
+ bool retval;
+ auto status = manager_->isImageMapped(name, &retval);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return retval;
+}
+
+bool ImageManagerBinder::MapImageWithDeviceMapper(const IPartitionOpener& opener,
+ const std::string& name, std::string* dev) {
+ (void)opener;
+ (void)name;
+ (void)dev;
+ LOG(ERROR) << "MapImageWithDeviceMapper is not available over binder.";
+ return false;
+}
+
+std::vector<std::string> ImageManagerBinder::GetAllBackingImages() {
+ std::vector<std::string> retval;
+ auto status = manager_->getAllBackingImages(&retval);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ }
+ return retval;
+}
+
+bool ImageManagerBinder::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
+ auto status = manager_->zeroFillNewImage(name, bytes);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return true;
+}
+
+bool ImageManagerBinder::RemoveAllImages() {
+ auto status = manager_->removeAllImages();
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return true;
+}
+
+bool ImageManagerBinder::DisableImage(const std::string&) {
+ LOG(ERROR) << __PRETTY_FUNCTION__ << " is not available over binder";
+ return false;
+}
+
+bool ImageManagerBinder::RemoveDisabledImages() {
+ auto status = manager_->removeDisabledImages();
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return true;
+}
+
+bool ImageManagerBinder::GetMappedImageDevice(const std::string& name, std::string* device) {
+ auto status = manager_->getMappedImageDevice(name, device);
+ if (!status.isOk()) {
+ LOG(ERROR) << __PRETTY_FUNCTION__
+ << " binder returned: " << status.exceptionMessage().string();
+ return false;
+ }
+ return !device->empty();
+}
+
+bool ImageManagerBinder::MapAllImages(const std::function<bool(std::set<std::string>)>&) {
+ LOG(ERROR) << __PRETTY_FUNCTION__ << " not available over binder";
+ return false;
+}
+
+static android::sp<IGsid> AcquireIGsid(const std::chrono::milliseconds& timeout_ms) {
+ if (android::base::GetProperty("init.svc.gsid", "") != "running") {
+ if (!android::base::SetProperty("ctl.start", "gsid") ||
+ !android::base::WaitForProperty("init.svc.gsid", "running", timeout_ms)) {
+ LOG(ERROR) << "Could not start the gsid service";
+ return nullptr;
+ }
+ // Sleep for 250ms to give the service time to register.
+ usleep(250 * 1000);
+ }
+ auto sm = android::defaultServiceManager();
+ auto name = android::String16(kGsiServiceName);
+ auto service = sm->checkService(name);
+ return android::interface_cast<IGsid>(service);
+}
+
+static android::sp<IGsid> GetGsiService(const std::chrono::milliseconds& timeout_ms) {
+ auto start_time = std::chrono::steady_clock::now();
+
+ std::chrono::milliseconds elapsed = std::chrono::milliseconds::zero();
+ do {
+ if (auto gsid = AcquireIGsid(timeout_ms - elapsed); gsid != nullptr) {
+ return gsid;
+ }
+ auto now = std::chrono::steady_clock::now();
+ elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
+ } while (elapsed <= timeout_ms);
+
+ LOG(ERROR) << "Timed out trying to acquire IGsid interface";
+ return nullptr;
+}
+
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir,
+ const std::chrono::milliseconds& timeout_ms) {
+ auto gsid = GetGsiService(timeout_ms);
+ if (!gsid) {
+ return nullptr;
+ }
+
+ android::sp<IGsiService> service;
+ auto status = gsid->getClient(&service);
+ if (!status.isOk() || !service) {
+ LOG(ERROR) << "Could not acquire IGsiService";
+ return nullptr;
+ }
+
+ android::sp<IImageService> manager;
+ status = service->openImageService(dir, &manager);
+ if (!status.isOk() || !manager) {
+ LOG(ERROR) << "Could not acquire IImageManager: " << status.exceptionMessage().string();
+ return nullptr;
+ }
+ return std::make_unique<ImageManagerBinder>(std::move(service), std::move(manager));
+}
+
+} // namespace fiemap
+} // namespace android
+
+#endif // __ANDROID_RECOVERY__
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
new file mode 100644
index 0000000..d34e0b8
--- /dev/null
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -0,0 +1,788 @@
+/*
+ * 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 <libfiemap/fiemap_writer.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <libdm/dm.h>
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace android::dm;
+
+// We cap the maximum number of extents as a sanity measure.
+static constexpr uint32_t kMaxExtents = 50000;
+
+// TODO: Fallback to using fibmap if FIEMAP_EXTENT_MERGED is set.
+static constexpr const uint32_t kUnsupportedExtentFlags =
+ FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_DELALLOC |
+ FIEMAP_EXTENT_NOT_ALIGNED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_DATA_TAIL |
+ FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED | FIEMAP_EXTENT_MERGED;
+
+// Large file support must be enabled.
+static_assert(sizeof(off_t) == sizeof(uint64_t));
+
+static inline void cleanup(const std::string& file_path, bool created) {
+ if (created) {
+ unlink(file_path.c_str());
+ }
+}
+
+static bool ValidateDmTarget(const DeviceMapper::TargetInfo& target) {
+ const auto& entry = target.spec;
+ if (entry.sector_start != 0) {
+ LOG(INFO) << "Stopping at target with non-zero starting sector";
+ return false;
+ }
+
+ auto target_type = DeviceMapper::GetTargetType(entry);
+ if (target_type == "bow" || target_type == "default-key" || target_type == "crypt") {
+ return true;
+ }
+ if (target_type == "linear") {
+ auto pieces = android::base::Split(target.data, " ");
+ if (pieces[1] != "0") {
+ LOG(INFO) << "Stopping at complex linear target with non-zero starting sector: "
+ << pieces[1];
+ return false;
+ }
+ return true;
+ }
+
+ LOG(INFO) << "Stopping at complex target type " << target_type;
+ return false;
+}
+
+static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) {
+ *bdev_raw = bdev;
+
+ if (!::android::base::StartsWith(bdev, "dm-")) {
+ // We are at the bottom of the device mapper stack.
+ return true;
+ }
+
+ // Get the device name.
+ auto dm_name_file = "/sys/block/" + bdev + "/dm/name";
+ std::string dm_name;
+ if (!android::base::ReadFileToString(dm_name_file, &dm_name)) {
+ PLOG(ERROR) << "Could not read file: " << dm_name_file;
+ return false;
+ }
+ dm_name = android::base::Trim(dm_name);
+
+ auto& dm = DeviceMapper::Instance();
+ std::vector<DeviceMapper::TargetInfo> table;
+ if (!dm.GetTableInfo(dm_name, &table)) {
+ LOG(ERROR) << "Could not read device-mapper table for " << dm_name << " at " << bdev;
+ return false;
+ }
+
+ // The purpose of libfiemap is to provide an extent-based view into
+ // a file. This is difficult if devices are not layered in a 1:1 manner;
+ // we would have to translate and break up extents based on the actual
+ // block mapping. Since this is too complex, we simply stop processing
+ // the device-mapper stack if we encounter a complex case.
+ //
+ // It is up to the caller to decide whether stopping at a virtual block
+ // device is allowable. In most cases it is not, because we want either
+ // "userdata" or an external volume. It is useful for tests however.
+ // Callers can check by comparing the device number to that of userdata,
+ // or by checking whether is a device-mapper node.
+ if (table.size() > 1) {
+ LOG(INFO) << "Stopping at complex table for " << dm_name << " at " << bdev;
+ return true;
+ }
+ if (!ValidateDmTarget(table[0])) {
+ return true;
+ }
+
+ auto dm_leaf_dir = "/sys/block/" + bdev + "/slaves";
+ auto d = std::unique_ptr<DIR, decltype(&closedir)>(opendir(dm_leaf_dir.c_str()), closedir);
+ if (d == nullptr) {
+ PLOG(ERROR) << "Failed to open: " << dm_leaf_dir;
+ return false;
+ }
+
+ struct dirent* de;
+ uint32_t num_leaves = 0;
+ std::string bdev_next = "";
+ while ((de = readdir(d.get())) != nullptr) {
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
+ continue;
+ }
+
+ // We set the first name we find here
+ if (bdev_next.empty()) {
+ bdev_next = de->d_name;
+ }
+ num_leaves++;
+ }
+
+ // if we have more than one leaves, we return immediately. We can't continue to create the
+ // file since we don't know how to write it out using fiemap, so it will be readable via the
+ // underlying block devices later. The reader will also have to construct the same device mapper
+ // target in order read the file out.
+ if (num_leaves > 1) {
+ LOG(ERROR) << "Found " << num_leaves << " leaf block devices under device mapper device "
+ << bdev;
+ return false;
+ }
+
+ // recursively call with the block device we found in order to pop the device mapper stack.
+ return DeviceMapperStackPop(bdev_next, bdev_raw);
+}
+
+bool FiemapWriter::GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path,
+ bool* uses_dm) {
+ struct stat sb;
+ if (stat(file_path.c_str(), &sb)) {
+ PLOG(ERROR) << "Failed to get stat for: " << file_path;
+ return false;
+ }
+
+ std::string bdev;
+ if (!BlockDeviceToName(major(sb.st_dev), minor(sb.st_dev), &bdev)) {
+ LOG(ERROR) << "Failed to get block device name for " << major(sb.st_dev) << ":"
+ << minor(sb.st_dev);
+ return false;
+ }
+
+ std::string bdev_raw;
+ if (!DeviceMapperStackPop(bdev, &bdev_raw)) {
+ LOG(ERROR) << "Failed to get the bottom of the device mapper stack for device: " << bdev;
+ return false;
+ }
+
+ if (uses_dm) {
+ *uses_dm = (bdev_raw != bdev);
+ }
+
+ LOG(DEBUG) << "Popped device (" << bdev_raw << ") from device mapper stack starting with ("
+ << bdev << ")";
+
+ *bdev_path = ::android::base::StringPrintf("/dev/block/%s", bdev_raw.c_str());
+
+ // Make sure we are talking to a block device before calling it a success.
+ if (stat(bdev_path->c_str(), &sb)) {
+ PLOG(ERROR) << "Failed to get stat for block device: " << *bdev_path;
+ return false;
+ }
+
+ if ((sb.st_mode & S_IFMT) != S_IFBLK) {
+ PLOG(ERROR) << "File: " << *bdev_path << " is not a block device";
+ return false;
+ }
+
+ return true;
+}
+
+static bool GetBlockDeviceSize(int bdev_fd, const std::string& bdev_path, uint64_t* bdev_size) {
+ uint64_t size_in_bytes = 0;
+ if (ioctl(bdev_fd, BLKGETSIZE64, &size_in_bytes)) {
+ PLOG(ERROR) << "Failed to get total size for: " << bdev_path;
+ return false;
+ }
+
+ *bdev_size = size_in_bytes;
+
+ return true;
+}
+
+static uint64_t GetFileSize(const std::string& file_path) {
+ struct stat sb;
+ if (stat(file_path.c_str(), &sb)) {
+ PLOG(ERROR) << "Failed to get size for file: " << file_path;
+ return 0;
+ }
+
+ return sb.st_size;
+}
+
+static bool PerformFileChecks(const std::string& file_path, uint64_t* blocksz, uint32_t* fs_type) {
+ struct statfs64 sfs;
+ if (statfs64(file_path.c_str(), &sfs)) {
+ PLOG(ERROR) << "Failed to read file system status at: " << file_path;
+ return false;
+ }
+
+ if (!sfs.f_bsize) {
+ LOG(ERROR) << "Unsupported block size: " << sfs.f_bsize;
+ return false;
+ }
+
+ // Check if the filesystem is of supported types.
+ // Only ext4, f2fs, and vfat are tested and supported.
+ switch (sfs.f_type) {
+ case EXT4_SUPER_MAGIC:
+ case F2FS_SUPER_MAGIC:
+ case MSDOS_SUPER_MAGIC:
+ break;
+ default:
+ LOG(ERROR) << "Unsupported file system type: 0x" << std::hex << sfs.f_type;
+ return false;
+ }
+
+ *blocksz = sfs.f_bsize;
+ *fs_type = sfs.f_type;
+ return true;
+}
+
+static bool FallocateFallback(int file_fd, uint64_t block_size, uint64_t file_size,
+ const std::string& file_path,
+ const std::function<bool(uint64_t, uint64_t)>& on_progress) {
+ // Even though this is much faster than writing zeroes, it is still slow
+ // enough that we need to fire the progress callback periodically. To
+ // easily achieve this, we seek in chunks. We use 1000 chunks since
+ // normally we only fire the callback on 1/1000th increments.
+ uint64_t bytes_per_chunk = std::max(file_size / 1000, block_size);
+
+ // Seek just to the end of each chunk and write a single byte, causing
+ // the filesystem to allocate blocks.
+ off_t cursor = 0;
+ off_t end = static_cast<off_t>(file_size);
+ while (cursor < end) {
+ cursor = std::min(static_cast<off_t>(cursor + bytes_per_chunk), end);
+ auto rv = TEMP_FAILURE_RETRY(lseek(file_fd, cursor - 1, SEEK_SET));
+ if (rv < 0) {
+ PLOG(ERROR) << "Failed to lseek " << file_path;
+ return false;
+ }
+ if (rv != cursor - 1) {
+ LOG(ERROR) << "Seek returned wrong offset " << rv << " for file " << file_path;
+ return false;
+ }
+ char buffer[] = {0};
+ if (!android::base::WriteFully(file_fd, buffer, 1)) {
+ PLOG(ERROR) << "Write failed: " << file_path;
+ return false;
+ }
+ if (on_progress && !on_progress(cursor, file_size)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// F2FS-specific ioctl
+// It requires the below kernel commit merged in v4.16-rc1.
+// 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file")
+// In android-4.4,
+// 56ee1e817908 ("f2fs: updates on v4.16-rc1")
+// In android-4.9,
+// 2f17e34672a8 ("f2fs: updates on v4.16-rc1")
+// In android-4.14,
+// ce767d9a55bc ("f2fs: updates on v4.16-rc1")
+#ifndef F2FS_IOC_SET_PIN_FILE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#endif
+
+static bool IsFilePinned(int file_fd, const std::string& file_path, uint32_t fs_type) {
+ if (fs_type != F2FS_SUPER_MAGIC) {
+ // No pinning necessary for ext4 or vfat. The blocks, once allocated,
+ // are expected to be fixed.
+ return true;
+ }
+
+ // f2fs: export FS_NOCOW_FL flag to user
+ uint32_t flags;
+ int error = ioctl(file_fd, FS_IOC_GETFLAGS, &flags);
+ if (error < 0) {
+ if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+ PLOG(ERROR) << "Failed to get flags, not supported by kernel: " << file_path;
+ } else {
+ PLOG(ERROR) << "Failed to get flags: " << file_path;
+ }
+ return false;
+ }
+ if (!(flags & FS_NOCOW_FL)) {
+ return false;
+ }
+
+ // F2FS_IOC_GET_PIN_FILE returns the number of blocks moved.
+ uint32_t moved_blocks_nr;
+ error = ioctl(file_fd, F2FS_IOC_GET_PIN_FILE, &moved_blocks_nr);
+ if (error < 0) {
+ if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+ PLOG(ERROR) << "Failed to get file pin status, not supported by kernel: " << file_path;
+ } else {
+ PLOG(ERROR) << "Failed to get file pin status: " << file_path;
+ }
+ return false;
+ }
+
+ if (moved_blocks_nr) {
+ LOG(WARNING) << moved_blocks_nr << " blocks moved in file " << file_path;
+ }
+ return moved_blocks_nr == 0;
+}
+
+static bool PinFile(int file_fd, const std::string& file_path, uint32_t fs_type) {
+ if (IsFilePinned(file_fd, file_path, fs_type)) {
+ return true;
+ }
+ if (fs_type != F2FS_SUPER_MAGIC) {
+ // No pinning necessary for ext4/msdos. The blocks, once allocated, are
+ // expected to be fixed.
+ return true;
+ }
+
+ uint32_t pin_status = 1;
+ int error = ioctl(file_fd, F2FS_IOC_SET_PIN_FILE, &pin_status);
+ if (error < 0) {
+ if ((errno == ENOTTY) || (errno == ENOTSUP)) {
+ PLOG(ERROR) << "Failed to pin file, not supported by kernel: " << file_path;
+ } else {
+ PLOG(ERROR) << "Failed to pin file: " << file_path;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+// write zeroes in 'blocksz' byte increments until we reach file_size to make sure the data
+// blocks are actually written to by the file system and thus getting rid of the holes in the
+// file.
+static bool WriteZeroes(int file_fd, const std::string& file_path, size_t blocksz,
+ uint64_t file_size,
+ const std::function<bool(uint64_t, uint64_t)>& on_progress) {
+ auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksz), free);
+ if (buffer == nullptr) {
+ LOG(ERROR) << "failed to allocate memory for writing file";
+ return false;
+ }
+
+ off64_t offset = lseek64(file_fd, 0, SEEK_SET);
+ if (offset < 0) {
+ PLOG(ERROR) << "Failed to seek at the beginning of : " << file_path;
+ return false;
+ }
+
+ int permille = -1;
+ while (offset < file_size) {
+ if (!::android::base::WriteFully(file_fd, buffer.get(), blocksz)) {
+ PLOG(ERROR) << "Failed to write" << blocksz << " bytes at offset" << offset
+ << " in file " << file_path;
+ return false;
+ }
+
+ offset += blocksz;
+
+ // Don't invoke the callback every iteration - wait until a significant
+ // chunk (here, 1/1000th) of the data has been processed.
+ int new_permille = (static_cast<uint64_t>(offset) * 1000) / file_size;
+ if (new_permille != permille && static_cast<uint64_t>(offset) != file_size) {
+ if (on_progress && !on_progress(offset, file_size)) {
+ return false;
+ }
+ permille = new_permille;
+ }
+ }
+
+ if (lseek64(file_fd, 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "Failed to reset offset at the beginning of : " << file_path;
+ return false;
+ }
+ return true;
+}
+
+// Reserve space for the file on the file system and write it out to make sure the extents
+// don't come back unwritten. Return from this function with the kernel file offset set to 0.
+// If the filesystem is f2fs, then we also PIN the file on disk to make sure the blocks
+// aren't moved around.
+static bool AllocateFile(int file_fd, const std::string& file_path, uint64_t blocksz,
+ uint64_t file_size, unsigned int fs_type,
+ std::function<bool(uint64_t, uint64_t)> on_progress) {
+ bool need_explicit_writes = true;
+ switch (fs_type) {
+ case EXT4_SUPER_MAGIC:
+ break;
+ case F2FS_SUPER_MAGIC: {
+ bool supported;
+ if (!F2fsPinBeforeAllocate(file_fd, &supported)) {
+ return false;
+ }
+ if (supported) {
+ if (!PinFile(file_fd, file_path, fs_type)) {
+ return false;
+ }
+ need_explicit_writes = false;
+ }
+ break;
+ }
+ case MSDOS_SUPER_MAGIC:
+ // fallocate() is not supported, and not needed, since VFAT does not support holes.
+ // Instead we can perform a much faster allocation.
+ return FallocateFallback(file_fd, blocksz, file_size, file_path, on_progress);
+ default:
+ LOG(ERROR) << "Missing fallocate() support for file system " << fs_type;
+ return false;
+ }
+
+ if (fallocate(file_fd, 0, 0, file_size)) {
+ PLOG(ERROR) << "Failed to allocate space for file: " << file_path << " size: " << file_size;
+ return false;
+ }
+
+ if (need_explicit_writes && !WriteZeroes(file_fd, file_path, blocksz, file_size, on_progress)) {
+ return false;
+ }
+
+ // flush all writes here ..
+ if (fsync(file_fd)) {
+ PLOG(ERROR) << "Failed to synchronize written file:" << file_path;
+ return false;
+ }
+
+ // Send one last progress notification.
+ if (on_progress && !on_progress(file_size, file_size)) {
+ return false;
+ }
+ return true;
+}
+
+bool FiemapWriter::HasPinnedExtents(const std::string& file_path) {
+ android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY));
+ if (fd < 0) {
+ PLOG(ERROR) << "open: " << file_path;
+ return false;
+ }
+
+ struct statfs64 sfs;
+ if (fstatfs64(fd, &sfs)) {
+ PLOG(ERROR) << "fstatfs64: " << file_path;
+ return false;
+ }
+ return IsFilePinned(fd, file_path, sfs.f_type);
+}
+
+static bool CountFiemapExtents(int file_fd, const std::string& file_path, uint32_t* num_extents) {
+ struct fiemap fiemap = {};
+ fiemap.fm_start = 0;
+ fiemap.fm_length = UINT64_MAX;
+ fiemap.fm_flags = FIEMAP_FLAG_SYNC;
+ fiemap.fm_extent_count = 0;
+
+ if (ioctl(file_fd, FS_IOC_FIEMAP, &fiemap)) {
+ PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
+ return false;
+ }
+
+ if (num_extents) {
+ *num_extents = fiemap.fm_mapped_extents;
+ }
+ return true;
+}
+
+static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
+ if (extent->fe_flags & kUnsupportedExtentFlags) {
+ LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
+ << " has unsupported flags";
+ return false;
+ }
+ return true;
+}
+
+static bool IsLastExtent(const fiemap_extent* extent) {
+ if (!(extent->fe_flags & FIEMAP_EXTENT_LAST)) {
+ LOG(ERROR) << "Extents are being received out-of-order";
+ return false;
+ }
+ return true;
+}
+
+static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
+ uint32_t num_extents, std::string_view file_path) {
+ if (num_extents == 0) return false;
+
+ const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
+ if (!IsLastExtent(last_extent)) {
+ LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path;
+ return false;
+ }
+
+ // Iterate through each extent, read and make sure its valid before adding it to the vector
+ // merging contiguous extents.
+ fiemap_extent* prev = &fiemap->fm_extents[0];
+ if (!IsValidExtent(prev, file_path)) return false;
+
+ for (uint32_t i = 1; i < num_extents; i++) {
+ fiemap_extent* next = &fiemap->fm_extents[i];
+
+ // Make sure extents are returned in order
+ if (next != last_extent && IsLastExtent(next)) return false;
+
+ // Check if extent's flags are valid
+ if (!IsValidExtent(next, file_path)) return false;
+
+ // Check if the current extent is contiguous with the previous one.
+ // An extent can be combined with its predecessor only if:
+ // 1. There is no physical space between the previous and the current
+ // extent, and
+ // 2. The physical distance between the previous and current extent
+ // corresponds to their logical distance (contiguous mapping).
+ if (prev->fe_physical + prev->fe_length == next->fe_physical &&
+ next->fe_physical - prev->fe_physical == next->fe_logical - prev->fe_logical) {
+ prev->fe_length += next->fe_length;
+ } else {
+ extents->emplace_back(*prev);
+ prev = next;
+ }
+ }
+ extents->emplace_back(*prev);
+
+ return true;
+}
+
+static bool ReadFiemap(int file_fd, const std::string& file_path,
+ std::vector<struct fiemap_extent>* extents) {
+ uint32_t num_extents;
+ if (!CountFiemapExtents(file_fd, file_path, &num_extents)) {
+ return false;
+ }
+ if (num_extents == 0) {
+ LOG(ERROR) << "File " << file_path << " has zero extents";
+ return false;
+ }
+ if (num_extents > kMaxExtents) {
+ LOG(ERROR) << "File has " << num_extents << ", maximum is " << kMaxExtents << ": "
+ << file_path;
+ return false;
+ }
+
+ uint64_t fiemap_size =
+ sizeof(struct fiemap_extent) + num_extents * sizeof(struct fiemap_extent);
+ auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
+ if (buffer == nullptr) {
+ LOG(ERROR) << "Failed to allocate memory for fiemap";
+ return false;
+ }
+
+ struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(buffer.get());
+ fiemap->fm_start = 0;
+ fiemap->fm_length = UINT64_MAX;
+ // make sure file is synced to disk before we read the fiemap
+ fiemap->fm_flags = FIEMAP_FLAG_SYNC;
+ fiemap->fm_extent_count = num_extents;
+
+ if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
+ PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
+ return false;
+ }
+ if (fiemap->fm_mapped_extents != num_extents) {
+ LOG(ERROR) << "FIEMAP returned unexpected extent count (" << num_extents
+ << " expected, got " << fiemap->fm_mapped_extents << ") for file: " << file_path;
+ return false;
+ }
+
+ return FiemapToExtents(fiemap, extents, num_extents, file_path);
+}
+
+static bool ReadFibmap(int file_fd, const std::string& file_path,
+ std::vector<struct fiemap_extent>* extents) {
+ struct stat s;
+ if (fstat(file_fd, &s)) {
+ PLOG(ERROR) << "Failed to stat " << file_path;
+ return false;
+ }
+
+ unsigned int blksize;
+ if (ioctl(file_fd, FIGETBSZ, &blksize) < 0) {
+ PLOG(ERROR) << "Failed to get FIGETBSZ for " << file_path;
+ return false;
+ }
+ if (!blksize) {
+ LOG(ERROR) << "Invalid filesystem block size: " << blksize;
+ return false;
+ }
+
+ uint64_t num_blocks = (s.st_size + blksize - 1) / blksize;
+ if (num_blocks > std::numeric_limits<uint32_t>::max()) {
+ LOG(ERROR) << "Too many blocks for FIBMAP (" << num_blocks << ")";
+ return false;
+ }
+
+ for (uint32_t last_block, block_number = 0; block_number < num_blocks; block_number++) {
+ uint32_t block = block_number;
+ if (ioctl(file_fd, FIBMAP, &block)) {
+ PLOG(ERROR) << "Failed to get FIBMAP for file " << file_path;
+ return false;
+ }
+ if (!block) {
+ LOG(ERROR) << "Logical block " << block_number << " is a hole, which is not supported";
+ return false;
+ }
+
+ if (!extents->empty() && block == last_block + 1) {
+ extents->back().fe_length += blksize;
+ } else {
+ extents->push_back(fiemap_extent{.fe_logical = block_number,
+ .fe_physical = static_cast<uint64_t>(block) * blksize,
+ .fe_length = static_cast<uint64_t>(blksize),
+ .fe_flags = 0});
+ if (extents->size() > kMaxExtents) {
+ LOG(ERROR) << "File has more than " << kMaxExtents << "extents: " << file_path;
+ return false;
+ }
+ }
+ last_block = block;
+ }
+ return true;
+}
+
+FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create,
+ std::function<bool(uint64_t, uint64_t)> progress) {
+ // if 'create' is false, open an existing file and do not truncate.
+ int open_flags = O_RDWR | O_CLOEXEC;
+ if (create) {
+ if (access(file_path.c_str(), F_OK) == 0) {
+ LOG(WARNING) << "File " << file_path << " already exists, truncating";
+ }
+ open_flags |= O_CREAT | O_TRUNC;
+ }
+ ::android::base::unique_fd file_fd(
+ TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags, S_IRUSR | S_IWUSR)));
+ if (file_fd < 0) {
+ PLOG(ERROR) << "Failed to create file at: " << file_path;
+ return nullptr;
+ }
+
+ std::string abs_path;
+ if (!::android::base::Realpath(file_path, &abs_path)) {
+ PLOG(ERROR) << "Invalid file path: " << file_path;
+ cleanup(file_path, create);
+ return nullptr;
+ }
+
+ std::string bdev_path;
+ if (!GetBlockDeviceForFile(abs_path, &bdev_path)) {
+ LOG(ERROR) << "Failed to get block dev path for file: " << file_path;
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+
+ ::android::base::unique_fd bdev_fd(
+ TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (bdev_fd < 0) {
+ PLOG(ERROR) << "Failed to open block device: " << bdev_path;
+ cleanup(file_path, create);
+ return nullptr;
+ }
+
+ uint64_t bdevsz;
+ if (!GetBlockDeviceSize(bdev_fd, bdev_path, &bdevsz)) {
+ LOG(ERROR) << "Failed to get block device size for : " << bdev_path;
+ cleanup(file_path, create);
+ return nullptr;
+ }
+
+ if (!create) {
+ file_size = GetFileSize(abs_path);
+ if (file_size == 0) {
+ LOG(ERROR) << "Invalid file size of zero bytes for file: " << abs_path;
+ return nullptr;
+ }
+ }
+
+ uint64_t blocksz;
+ uint32_t fs_type;
+ if (!PerformFileChecks(abs_path, &blocksz, &fs_type)) {
+ LOG(ERROR) << "Failed to validate file or file system for file:" << abs_path;
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+
+ // Align up to the nearest block size.
+ if (file_size % blocksz) {
+ file_size += blocksz - (file_size % blocksz);
+ }
+
+ if (create) {
+ if (!AllocateFile(file_fd, abs_path, blocksz, file_size, fs_type, std::move(progress))) {
+ LOG(ERROR) << "Failed to allocate file: " << abs_path << " of size: " << file_size
+ << " bytes";
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+ }
+
+ // f2fs may move the file blocks around.
+ if (!PinFile(file_fd, abs_path, fs_type)) {
+ cleanup(abs_path, create);
+ LOG(ERROR) << "Failed to pin the file in storage";
+ return nullptr;
+ }
+
+ // now allocate the FiemapWriter and start setting it up
+ FiemapUniquePtr fmap(new FiemapWriter());
+ switch (fs_type) {
+ case EXT4_SUPER_MAGIC:
+ case F2FS_SUPER_MAGIC:
+ if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) {
+ LOG(ERROR) << "Failed to read fiemap of file: " << abs_path;
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+ break;
+ case MSDOS_SUPER_MAGIC:
+ if (!ReadFibmap(file_fd, abs_path, &fmap->extents_)) {
+ LOG(ERROR) << "Failed to read fibmap of file: " << abs_path;
+ cleanup(abs_path, create);
+ return nullptr;
+ }
+ break;
+ }
+
+ fmap->file_path_ = abs_path;
+ fmap->bdev_path_ = bdev_path;
+ fmap->file_size_ = file_size;
+ fmap->bdev_size_ = bdevsz;
+ fmap->fs_type_ = fs_type;
+ fmap->block_size_ = blocksz;
+
+ LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
+ << bdev_path;
+ return fmap;
+}
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/fiemap_writer_test.cpp b/fs_mgr/libfiemap/fiemap_writer_test.cpp
new file mode 100644
index 0000000..4ac7161
--- /dev/null
+++ b/fs_mgr/libfiemap/fiemap_writer_test.cpp
@@ -0,0 +1,541 @@
+/*
+ * 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 <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <libdm/loop_control.h>
+#include <libfiemap/fiemap_writer.h>
+#include <libfiemap/split_fiemap_writer.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace std;
+using namespace std::string_literals;
+using namespace android::fiemap;
+using unique_fd = android::base::unique_fd;
+using LoopDevice = android::dm::LoopDevice;
+
+std::string gTestDir;
+uint64_t testfile_size = 536870912; // default of 512MiB
+size_t gBlockSize = 0;
+
+class FiemapWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+ testfile = gTestDir + "/"s + tinfo->name();
+ }
+
+ void TearDown() override { unlink(testfile.c_str()); }
+
+ // name of the file we use for testing
+ std::string testfile;
+};
+
+class SplitFiemapTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+ testfile = gTestDir + "/"s + tinfo->name();
+ }
+
+ void TearDown() override {
+ std::string message;
+ if (!SplitFiemap::RemoveSplitFiles(testfile, &message)) {
+ cerr << "Could not remove all split files: " << message;
+ }
+ }
+
+ // name of the file we use for testing
+ std::string testfile;
+};
+
+TEST_F(FiemapWriterTest, CreateImpossiblyLargeFile) {
+ // Try creating a file of size ~100TB but aligned to
+ // 512 byte to make sure block alignment tests don't
+ // fail.
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1099511627997184);
+ EXPECT_EQ(fptr, nullptr);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+ EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(FiemapWriterTest, CreateUnalignedFile) {
+ // Try creating a file of size 4097 bytes which is guaranteed
+ // to be unaligned to all known block sizes.
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize + 1);
+ ASSERT_NE(fptr, nullptr);
+ ASSERT_EQ(fptr->size(), gBlockSize * 2);
+}
+
+TEST_F(FiemapWriterTest, CheckFilePath) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize);
+ ASSERT_NE(fptr, nullptr);
+ EXPECT_EQ(fptr->size(), gBlockSize);
+ EXPECT_EQ(fptr->file_path(), testfile);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), 0);
+}
+
+TEST_F(FiemapWriterTest, CheckFileSize) {
+ // Create a large-ish file and test that the expected size matches.
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1024 * 1024 * 16);
+ ASSERT_NE(fptr, nullptr);
+
+ struct stat s;
+ ASSERT_EQ(stat(testfile.c_str(), &s), 0);
+ EXPECT_EQ(static_cast<uint64_t>(s.st_size), fptr->size());
+}
+
+TEST_F(FiemapWriterTest, CheckProgress) {
+ std::vector<uint64_t> expected;
+ size_t invocations = 0;
+ auto callback = [&](uint64_t done, uint64_t total) -> bool {
+ if (invocations >= expected.size()) {
+ return false;
+ }
+ EXPECT_EQ(done, expected[invocations]);
+ EXPECT_EQ(total, gBlockSize);
+ invocations++;
+ return true;
+ };
+
+ expected.push_back(gBlockSize);
+
+ auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback));
+ EXPECT_NE(ptr, nullptr);
+ EXPECT_EQ(invocations, expected.size());
+}
+
+TEST_F(FiemapWriterTest, CheckPinning) {
+ auto ptr = FiemapWriter::Open(testfile, 4096);
+ ASSERT_NE(ptr, nullptr);
+ EXPECT_TRUE(FiemapWriter::HasPinnedExtents(testfile));
+}
+
+TEST_F(FiemapWriterTest, CheckBlockDevicePath) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize);
+ EXPECT_EQ(fptr->size(), gBlockSize);
+ EXPECT_EQ(fptr->bdev_path().find("/dev/block/"), size_t(0));
+ EXPECT_EQ(fptr->bdev_path().find("/dev/block/dm-"), string::npos);
+}
+
+TEST_F(FiemapWriterTest, CheckFileCreated) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 32768);
+ ASSERT_NE(fptr, nullptr);
+ unique_fd fd(open(testfile.c_str(), O_RDONLY));
+ EXPECT_GT(fd, -1);
+}
+
+TEST_F(FiemapWriterTest, CheckFileSizeActual) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
+ ASSERT_NE(fptr, nullptr);
+
+ struct stat sb;
+ ASSERT_EQ(stat(testfile.c_str(), &sb), 0);
+ EXPECT_GE(sb.st_size, testfile_size);
+}
+
+TEST_F(FiemapWriterTest, CheckFileExtents) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size);
+ ASSERT_NE(fptr, nullptr);
+ EXPECT_GT(fptr->extents().size(), 0);
+}
+
+TEST_F(FiemapWriterTest, ExistingFile) {
+ // Create the file.
+ { ASSERT_NE(FiemapWriter::Open(testfile, gBlockSize), nullptr); }
+ // Test that we can still open it.
+ {
+ auto ptr = FiemapWriter::Open(testfile, 0, false);
+ ASSERT_NE(ptr, nullptr);
+ EXPECT_GT(ptr->extents().size(), 0);
+ }
+}
+
+TEST_F(FiemapWriterTest, FileDeletedOnError) {
+ auto callback = [](uint64_t, uint64_t) -> bool { return false; };
+ auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback));
+ EXPECT_EQ(ptr, nullptr);
+ EXPECT_EQ(access(testfile.c_str(), F_OK), -1);
+ EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(FiemapWriterTest, MaxBlockSize) {
+ ASSERT_GT(DetermineMaximumFileSize(testfile), 0);
+}
+
+TEST_F(FiemapWriterTest, FibmapBlockAddressing) {
+ FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize);
+ ASSERT_NE(fptr, nullptr);
+
+ switch (fptr->fs_type()) {
+ case F2FS_SUPER_MAGIC:
+ case EXT4_SUPER_MAGIC:
+ // Skip the test for FIEMAP supported filesystems. This is really
+ // because f2fs/ext4 have caches that seem to defeat reading back
+ // directly from the block device, and writing directly is too
+ // dangerous.
+ std::cout << "Skipping test, filesystem does not use FIBMAP\n";
+ return;
+ }
+
+ bool uses_dm;
+ std::string bdev_path;
+ ASSERT_TRUE(FiemapWriter::GetBlockDeviceForFile(testfile, &bdev_path, &uses_dm));
+
+ if (uses_dm) {
+ // We could use a device-mapper wrapper here to bypass encryption, but
+ // really this test is for FIBMAP correctness on VFAT (where encryption
+ // is never used), so we don't bother.
+ std::cout << "Skipping test, block device is metadata encrypted\n";
+ return;
+ }
+
+ std::string data(fptr->size(), '\0');
+ for (size_t i = 0; i < data.size(); i++) {
+ data[i] = 'A' + static_cast<char>(data.size() % 26);
+ }
+
+ {
+ unique_fd fd(open(testfile.c_str(), O_WRONLY | O_CLOEXEC));
+ ASSERT_GE(fd, 0);
+ ASSERT_TRUE(android::base::WriteFully(fd, data.data(), data.size()));
+ ASSERT_EQ(fsync(fd), 0);
+ }
+
+ ASSERT_FALSE(fptr->extents().empty());
+ const auto& first_extent = fptr->extents()[0];
+
+ unique_fd bdev(open(fptr->bdev_path().c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_GE(bdev, 0);
+
+ off_t where = first_extent.fe_physical;
+ ASSERT_EQ(lseek(bdev, where, SEEK_SET), where);
+
+ // Note: this will fail on encrypted folders.
+ std::string actual(data.size(), '\0');
+ ASSERT_GE(first_extent.fe_length, data.size());
+ ASSERT_TRUE(android::base::ReadFully(bdev, actual.data(), actual.size()));
+ EXPECT_EQ(memcmp(actual.data(), data.data(), data.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, Create) {
+ auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
+ ASSERT_NE(ptr, nullptr);
+
+ auto extents = ptr->extents();
+
+ // Destroy the fiemap, closing file handles. This should not delete them.
+ ptr = nullptr;
+
+ std::vector<std::string> files;
+ ASSERT_TRUE(SplitFiemap::GetSplitFileList(testfile, &files));
+ for (const auto& path : files) {
+ EXPECT_EQ(access(path.c_str(), F_OK), 0);
+ }
+
+ ASSERT_GE(extents.size(), files.size());
+}
+
+TEST_F(SplitFiemapTest, Open) {
+ {
+ auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32);
+ ASSERT_NE(ptr, nullptr);
+ }
+
+ auto ptr = SplitFiemap::Open(testfile);
+ ASSERT_NE(ptr, nullptr);
+
+ auto extents = ptr->extents();
+ ASSERT_GE(extents.size(), 24);
+}
+
+TEST_F(SplitFiemapTest, DeleteOnFail) {
+ auto ptr = SplitFiemap::Create(testfile, 1024 * 1024 * 100, 1);
+ ASSERT_EQ(ptr, nullptr);
+
+ std::string first_file = testfile + ".0001";
+ ASSERT_NE(access(first_file.c_str(), F_OK), 0);
+ ASSERT_EQ(errno, ENOENT);
+ ASSERT_NE(access(testfile.c_str(), F_OK), 0);
+ ASSERT_EQ(errno, ENOENT);
+}
+
+static string ReadSplitFiles(const std::string& base_path, size_t num_files) {
+ std::string result;
+ for (int i = 0; i < num_files; i++) {
+ std::string path = base_path + android::base::StringPrintf(".%04d", i);
+ std::string data;
+ if (!android::base::ReadFileToString(path, &data)) {
+ return {};
+ }
+ result += data;
+ }
+ return result;
+}
+
+TEST_F(SplitFiemapTest, WriteWholeFile) {
+ static constexpr size_t kChunkSize = 32768;
+ static constexpr size_t kSize = kChunkSize * 3;
+ auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+ ASSERT_NE(ptr, nullptr);
+
+ auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+ for (size_t i = 0; i < kSize / sizeof(int); i++) {
+ buffer[i] = i;
+ }
+ ASSERT_TRUE(ptr->Write(buffer.get(), kSize));
+
+ std::string expected(reinterpret_cast<char*>(buffer.get()), kSize);
+ auto actual = ReadSplitFiles(testfile, 3);
+ ASSERT_EQ(expected.size(), actual.size());
+ EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, WriteFileInChunks1) {
+ static constexpr size_t kChunkSize = 32768;
+ static constexpr size_t kSize = kChunkSize * 3;
+ auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+ ASSERT_NE(ptr, nullptr);
+
+ auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+ for (size_t i = 0; i < kSize / sizeof(int); i++) {
+ buffer[i] = i;
+ }
+
+ // Write in chunks of 1000 (so some writes straddle the boundary of two
+ // files).
+ size_t bytes_written = 0;
+ while (bytes_written < kSize) {
+ size_t to_write = std::min(kSize - bytes_written, (size_t)1000);
+ char* data = reinterpret_cast<char*>(buffer.get()) + bytes_written;
+ ASSERT_TRUE(ptr->Write(data, to_write));
+ bytes_written += to_write;
+ }
+
+ std::string expected(reinterpret_cast<char*>(buffer.get()), kSize);
+ auto actual = ReadSplitFiles(testfile, 3);
+ ASSERT_EQ(expected.size(), actual.size());
+ EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, WriteFileInChunks2) {
+ static constexpr size_t kChunkSize = 32768;
+ static constexpr size_t kSize = kChunkSize * 3;
+ auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+ ASSERT_NE(ptr, nullptr);
+
+ auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+ for (size_t i = 0; i < kSize / sizeof(int); i++) {
+ buffer[i] = i;
+ }
+
+ // Write in chunks of 32KiB so every write is exactly at the end of the
+ // current file.
+ size_t bytes_written = 0;
+ while (bytes_written < kSize) {
+ size_t to_write = std::min(kSize - bytes_written, kChunkSize);
+ char* data = reinterpret_cast<char*>(buffer.get()) + bytes_written;
+ ASSERT_TRUE(ptr->Write(data, to_write));
+ bytes_written += to_write;
+ }
+
+ std::string expected(reinterpret_cast<char*>(buffer.get()), kSize);
+ auto actual = ReadSplitFiles(testfile, 3);
+ ASSERT_EQ(expected.size(), actual.size());
+ EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0);
+}
+
+TEST_F(SplitFiemapTest, WritePastEnd) {
+ static constexpr size_t kChunkSize = 32768;
+ static constexpr size_t kSize = kChunkSize * 3;
+ auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize);
+ ASSERT_NE(ptr, nullptr);
+
+ auto buffer = std::make_unique<int[]>(kSize / sizeof(int));
+ for (size_t i = 0; i < kSize / sizeof(int); i++) {
+ buffer[i] = i;
+ }
+ ASSERT_TRUE(ptr->Write(buffer.get(), kSize));
+ ASSERT_FALSE(ptr->Write(buffer.get(), kSize));
+}
+
+class VerifyBlockWritesExt4 : public ::testing::Test {
+ // 2GB Filesystem and 4k block size by default
+ static constexpr uint64_t block_size = 4096;
+ static constexpr uint64_t fs_size = 2147483648;
+
+ protected:
+ void SetUp() override {
+ fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img";
+ uint64_t count = fs_size / block_size;
+ std::string dd_cmd =
+ ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
+ " count=%" PRIu64 " > /dev/null 2>&1",
+ fs_path.c_str(), block_size, count);
+ std::string mkfs_cmd =
+ ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str());
+ // create mount point
+ mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
+ ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+ // create file for the file system
+ int ret = system(dd_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+ // Get and attach a loop device to the filesystem we created
+ LoopDevice loop_dev(fs_path, 10s);
+ ASSERT_TRUE(loop_dev.valid());
+ // create file system
+ ret = system(mkfs_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+
+ // mount the file system
+ ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0);
+ }
+
+ void TearDown() override {
+ umount(mntpoint.c_str());
+ rmdir(mntpoint.c_str());
+ unlink(fs_path.c_str());
+ }
+
+ std::string mntpoint;
+ std::string fs_path;
+};
+
+class VerifyBlockWritesF2fs : public ::testing::Test {
+ // 2GB Filesystem and 4k block size by default
+ static constexpr uint64_t block_size = 4096;
+ static constexpr uint64_t fs_size = 2147483648;
+
+ protected:
+ void SetUp() override {
+ fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img";
+ uint64_t count = fs_size / block_size;
+ std::string dd_cmd =
+ ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
+ " count=%" PRIu64 " > /dev/null 2>&1",
+ fs_path.c_str(), block_size, count);
+ std::string mkfs_cmd =
+ ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str());
+ // create mount point
+ mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
+ ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+ // create file for the file system
+ int ret = system(dd_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+ // Get and attach a loop device to the filesystem we created
+ LoopDevice loop_dev(fs_path, 10s);
+ ASSERT_TRUE(loop_dev.valid());
+ // create file system
+ ret = system(mkfs_cmd.c_str());
+ ASSERT_EQ(ret, 0);
+
+ // mount the file system
+ ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0);
+ }
+
+ void TearDown() override {
+ umount(mntpoint.c_str());
+ rmdir(mntpoint.c_str());
+ unlink(fs_path.c_str());
+ }
+
+ std::string mntpoint;
+ std::string fs_path;
+};
+
+bool DetermineBlockSize() {
+ struct statfs s;
+ if (statfs(gTestDir.c_str(), &s)) {
+ std::cerr << "Could not call statfs: " << strerror(errno) << "\n";
+ return false;
+ }
+ if (!s.f_bsize) {
+ std::cerr << "Invalid block size: " << s.f_bsize << "\n";
+ return false;
+ }
+
+ gBlockSize = s.f_bsize;
+ return true;
+}
+
+} // namespace fiemap
+} // namespace android
+
+using namespace android::fiemap;
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ if (argc > 1 && argv[1] == "-h"s) {
+ cerr << "Usage: [test_dir] [file_size]\n";
+ cerr << "\n";
+ cerr << "Note: test_dir must be a writable, unencrypted directory.\n";
+ exit(EXIT_FAILURE);
+ }
+ ::android::base::InitLogging(argv, ::android::base::StderrLogger);
+
+ std::string root_dir = "/data/local/unencrypted";
+ if (access(root_dir.c_str(), F_OK)) {
+ root_dir = "/data";
+ }
+
+ std::string tempdir = root_dir + "/XXXXXX"s;
+ if (!mkdtemp(tempdir.data())) {
+ cerr << "unable to create tempdir on " << root_dir << "\n";
+ exit(EXIT_FAILURE);
+ }
+ if (!android::base::Realpath(tempdir, &gTestDir)) {
+ cerr << "unable to find realpath for " << tempdir;
+ exit(EXIT_FAILURE);
+ }
+
+ if (argc > 2) {
+ testfile_size = strtoull(argv[2], NULL, 0);
+ if (testfile_size == ULLONG_MAX) {
+ testfile_size = 512 * 1024 * 1024;
+ }
+ }
+
+ if (!DetermineBlockSize()) {
+ exit(EXIT_FAILURE);
+ }
+
+ auto result = RUN_ALL_TESTS();
+
+ std::string cmd = "rm -rf " + gTestDir;
+ system(cmd.c_str());
+
+ return result;
+}
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
new file mode 100644
index 0000000..baa5de4
--- /dev/null
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -0,0 +1,737 @@
+//
+// Copyright (C) 2019 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 <libfiemap/image_manager.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <fs_mgr/file_wait.h>
+#include <fs_mgr_dm_linear.h>
+#include <libdm/loop_control.h>
+#include <libfiemap/split_fiemap_writer.h>
+
+#include "metadata.h"
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace std::literals;
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::dm::DmTable;
+using android::dm::DmTargetLinear;
+using android::dm::LoopControl;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::CreateLogicalPartitions;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::GetBlockDevicePartitionName;
+using android::fs_mgr::GetBlockDevicePartitionNames;
+using android::fs_mgr::GetPartitionName;
+
+static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
+
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) {
+ auto metadata_dir = "/metadata/gsi/" + dir_prefix;
+ auto data_dir = "/data/gsi/" + dir_prefix;
+ return Open(metadata_dir, data_dir);
+}
+
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
+ const std::string& data_dir) {
+ return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir));
+}
+
+ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir)
+ : metadata_dir_(metadata_dir), data_dir_(data_dir) {
+ partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
+}
+
+std::string ImageManager::GetImageHeaderPath(const std::string& name) {
+ return JoinPaths(data_dir_, name) + ".img";
+}
+
+// The status file has one entry per line, with each entry formatted as one of:
+// dm:<name>
+// loop:<path>
+//
+// This simplifies the process of tearing down a mapping, since we can simply
+// unmap each entry in the order it appears.
+std::string ImageManager::GetStatusFilePath(const std::string& image_name) {
+ return JoinPaths(metadata_dir_, image_name) + ".status";
+}
+
+static std::string GetStatusPropertyName(const std::string& image_name) {
+ // Note: we don't prefix |image_name|, because CreateLogicalPartition won't
+ // prefix the name either. There are no plans to change this at the moment,
+ // consumers of the image API must take care to use globally-unique image
+ // names.
+ return "gsid.mapped_image." + image_name;
+}
+
+void ImageManager::set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener) {
+ partition_opener_ = std::move(opener);
+}
+
+bool ImageManager::IsImageMapped(const std::string& image_name) {
+ auto prop_name = GetStatusPropertyName(image_name);
+ if (android::base::GetProperty(prop_name, "").empty()) {
+ // If mapped in first-stage init, the dm-device will exist but not the
+ // property.
+ auto& dm = DeviceMapper::Instance();
+ return dm.GetState(image_name) != DmDeviceState::INVALID;
+ }
+ return true;
+}
+
+std::vector<std::string> ImageManager::GetAllBackingImages() {
+ std::vector<std::string> images;
+ if (!MetadataExists(metadata_dir_)) {
+ return images;
+ }
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (metadata) {
+ for (auto&& partition : metadata->partitions) {
+ images.push_back(partition.name);
+ }
+ }
+ return images;
+}
+
+bool ImageManager::PartitionExists(const std::string& name) {
+ if (!MetadataExists(metadata_dir_)) {
+ return false;
+ }
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (!metadata) {
+ return false;
+ }
+ return !!FindPartition(*metadata.get(), name);
+}
+
+bool ImageManager::BackingImageExists(const std::string& name) {
+ auto header_file = GetImageHeaderPath(name);
+ return access(header_file.c_str(), F_OK) == 0;
+}
+
+bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags) {
+ return CreateBackingImage(name, size, flags, nullptr);
+}
+
+static bool IsUnreliablePinningAllowed(const std::string& path) {
+ return android::base::StartsWith(path, "/data/gsi/dsu/") ||
+ android::base::StartsWith(path, "/data/gsi/test/") ||
+ android::base::StartsWith(path, "/data/gsi/ota/test/");
+}
+
+bool ImageManager::CreateBackingImage(const std::string& name, uint64_t size, int flags,
+ std::function<bool(uint64_t, uint64_t)>&& on_progress) {
+ auto data_path = GetImageHeaderPath(name);
+ auto fw = SplitFiemap::Create(data_path, size, 0, on_progress);
+ if (!fw) {
+ return false;
+ }
+
+ bool reliable_pinning;
+ if (!FilesystemHasReliablePinning(data_path, &reliable_pinning)) {
+ return false;
+ }
+ if (!reliable_pinning && !IsUnreliablePinningAllowed(data_path)) {
+ // For historical reasons, we allow unreliable pinning for certain use
+ // cases (DSUs, testing) because the ultimate use case is either
+ // developer-oriented or ephemeral (the intent is to boot immediately
+ // into DSUs). For everything else - such as snapshots/OTAs or adb
+ // remount, we have a higher bar, and require the filesystem to support
+ // proper pinning.
+ LOG(ERROR) << "File system does not have reliable block pinning";
+ SplitFiemap::RemoveSplitFiles(data_path);
+ return false;
+ }
+
+ // Except for testing, we do not allow persisting metadata that references
+ // device-mapper devices. It just doesn't make sense, because the device
+ // numbering may change on reboot. We allow it for testing since the images
+ // are not meant to survive reboot. Outside of tests, this can only happen
+ // if device-mapper is stacked in some complex way not supported by
+ // FiemapWriter.
+ auto device_path = GetDevicePathForFile(fw.get());
+ if (android::base::StartsWith(device_path, "/dev/block/dm-") &&
+ !android::base::StartsWith(metadata_dir_, kTestImageMetadataDir)) {
+ LOG(ERROR) << "Cannot persist images against device-mapper device: " << device_path;
+
+ fw = {};
+ SplitFiemap::RemoveSplitFiles(data_path);
+ return false;
+ }
+
+ bool readonly = !!(flags & CREATE_IMAGE_READONLY);
+ if (!UpdateMetadata(metadata_dir_, name, fw.get(), size, readonly)) {
+ return false;
+ }
+
+ if (flags & CREATE_IMAGE_ZERO_FILL) {
+ if (!ZeroFillNewImage(name, 0)) {
+ DeleteBackingImage(name);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
+ auto data_path = GetImageHeaderPath(name);
+
+ // See the comment in MapImageDevice() about how this works.
+ std::string block_device;
+ bool can_use_devicemapper;
+ if (!FiemapWriter::GetBlockDeviceForFile(data_path, &block_device, &can_use_devicemapper)) {
+ LOG(ERROR) << "Could not determine block device for " << data_path;
+ return false;
+ }
+
+ if (!can_use_devicemapper) {
+ // We've backed with loop devices, and since we store files in an
+ // unencrypted folder, the initial zeroes we wrote will suffice.
+ return true;
+ }
+
+ // data is dm-crypt, or FBE + dm-default-key. This means the zeroes written
+ // by libfiemap were encrypted, so we need to map the image in and correct
+ // this.
+ auto device = MappedDevice::Open(this, 10s, name);
+ if (!device) {
+ return false;
+ }
+
+ static constexpr size_t kChunkSize = 4096;
+ std::string zeroes(kChunkSize, '\0');
+
+ uint64_t remaining;
+ if (bytes) {
+ remaining = bytes;
+ } else {
+ remaining = get_block_device_size(device->fd());
+ if (!remaining) {
+ PLOG(ERROR) << "Could not get block device size for " << device->path();
+ return false;
+ }
+ }
+ while (remaining) {
+ uint64_t to_write = std::min(static_cast<uint64_t>(zeroes.size()), remaining);
+ if (!android::base::WriteFully(device->fd(), zeroes.data(),
+ static_cast<size_t>(to_write))) {
+ PLOG(ERROR) << "write failed: " << device->path();
+ return false;
+ }
+ remaining -= to_write;
+ }
+ return true;
+}
+
+bool ImageManager::DeleteBackingImage(const std::string& name) {
+ // For dm-linear devices sitting on top of /data, we cannot risk deleting
+ // the file. The underlying blocks could be reallocated by the filesystem.
+ if (IsImageMapped(name)) {
+ LOG(ERROR) << "Backing image " << name << " is currently mapped to a block device";
+ return false;
+ }
+
+ std::string message;
+ auto header_file = GetImageHeaderPath(name);
+ if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
+ // This is fatal, because we don't want to leave these files dangling.
+ LOG(ERROR) << "Error removing image " << name << ": " << message;
+ return false;
+ }
+
+ auto status_file = GetStatusFilePath(name);
+ if (!android::base::RemoveFileIfExists(status_file)) {
+ LOG(ERROR) << "Error removing " << status_file << ": " << message;
+ }
+ return RemoveImageMetadata(metadata_dir_, name);
+}
+
+// Create a block device for an image file, using its extents in its
+// lp_metadata.
+bool ImageManager::MapWithDmLinear(const IPartitionOpener& opener, const std::string& name,
+ const std::chrono::milliseconds& timeout_ms, std::string* path) {
+ // :TODO: refresh extents in metadata file until f2fs is fixed.
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (!metadata) {
+ return false;
+ }
+
+ auto super = android::fs_mgr::GetMetadataSuperBlockDevice(*metadata.get());
+ auto block_device = android::fs_mgr::GetBlockDevicePartitionName(*super);
+
+ CreateLogicalPartitionParams params = {
+ .block_device = block_device,
+ .metadata = metadata.get(),
+ .partition_name = name,
+ .force_writable = true,
+ .timeout_ms = timeout_ms,
+ .partition_opener = &opener,
+ };
+ if (!CreateLogicalPartition(params, path)) {
+ LOG(ERROR) << "Error creating device-mapper node for image " << name;
+ return false;
+ }
+
+ auto status_string = "dm:" + name;
+ auto status_file = GetStatusFilePath(name);
+ if (!android::base::WriteStringToFile(status_string, status_file)) {
+ PLOG(ERROR) << "Could not write status file: " << status_file;
+ DestroyLogicalPartition(name);
+ return false;
+ }
+ return true;
+}
+
+// Helper to create a loop device for a file.
+static bool CreateLoopDevice(LoopControl& control, const std::string& file,
+ const std::chrono::milliseconds& timeout_ms, std::string* path) {
+ static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+ android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
+ if (file_fd < 0) {
+ PLOG(ERROR) << "Could not open file: " << file;
+ return false;
+ }
+ if (!control.Attach(file_fd, timeout_ms, path)) {
+ LOG(ERROR) << "Could not create loop device for: " << file;
+ return false;
+ }
+ LOG(INFO) << "Created loop device " << *path << " for file " << file;
+ return true;
+}
+
+class AutoDetachLoopDevices final {
+ public:
+ AutoDetachLoopDevices(LoopControl& control, const std::vector<std::string>& devices)
+ : control_(control), devices_(devices), commit_(false) {}
+
+ ~AutoDetachLoopDevices() {
+ if (commit_) return;
+ for (const auto& device : devices_) {
+ control_.Detach(device);
+ }
+ }
+
+ void Commit() { commit_ = true; }
+
+ private:
+ LoopControl& control_;
+ const std::vector<std::string>& devices_;
+ bool commit_;
+};
+
+// If an image is stored across multiple files, this takes a list of loop
+// devices and joins them together using device-mapper.
+bool ImageManager::MapWithLoopDeviceList(const std::vector<std::string>& device_list,
+ const std::string& name,
+ const std::chrono::milliseconds& timeout_ms,
+ std::string* path) {
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (!metadata) {
+ return false;
+ }
+ auto partition = FindPartition(*metadata.get(), name);
+ if (!partition) {
+ LOG(ERROR) << "Could not find image in metadata: " << name;
+ return false;
+ }
+
+ // Since extent lengths are in sector units, the size should be a multiple
+ // of the sector size.
+ uint64_t partition_size = GetPartitionSize(*metadata.get(), *partition);
+ if (partition_size % LP_SECTOR_SIZE != 0) {
+ LOG(ERROR) << "Partition size not sector aligned: " << name << ", " << partition_size
+ << " bytes";
+ return false;
+ }
+
+ DmTable table;
+
+ uint64_t start_sector = 0;
+ uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE;
+ for (const auto& block_device : device_list) {
+ // The final block device must be == partition_size, otherwise we
+ // can't find the AVB footer on verified partitions.
+ static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+ unique_fd fd(open(block_device.c_str(), kOpenFlags));
+ if (fd < 0) {
+ PLOG(ERROR) << "Open failed: " << block_device;
+ return false;
+ }
+
+ uint64_t file_size = get_block_device_size(fd);
+ uint64_t file_sectors = file_size / LP_SECTOR_SIZE;
+ uint64_t segment_size = std::min(file_sectors, sectors_needed);
+
+ table.Emplace<DmTargetLinear>(start_sector, segment_size, block_device, 0);
+
+ start_sector += segment_size;
+ sectors_needed -= segment_size;
+ if (sectors_needed == 0) {
+ break;
+ }
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ if (!dm.CreateDevice(name, table, path, timeout_ms)) {
+ LOG(ERROR) << "Could not create device-mapper device over loop set";
+ return false;
+ }
+
+ // Build the status file.
+ std::vector<std::string> lines;
+ lines.emplace_back("dm:" + name);
+ for (const auto& block_device : device_list) {
+ lines.emplace_back("loop:" + block_device);
+ }
+ auto status_message = android::base::Join(lines, "\n");
+ auto status_file = GetStatusFilePath(name);
+ if (!android::base::WriteStringToFile(status_message, status_file)) {
+ PLOG(ERROR) << "Write failed: " << status_file;
+ dm.DeleteDevice(name);
+ return false;
+ }
+ return true;
+}
+
+static bool OptimizeLoopDevices(const std::vector<std::string>& device_list) {
+ for (const auto& device : device_list) {
+ unique_fd fd(open(device.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW));
+ if (fd < 0) {
+ PLOG(ERROR) << "Open failed: " << device;
+ return false;
+ }
+ if (!LoopControl::EnableDirectIo(fd)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Helper to use one or more loop devices around image files.
+bool ImageManager::MapWithLoopDevice(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms,
+ std::string* path) {
+ auto image_header = GetImageHeaderPath(name);
+
+ std::vector<std::string> file_list;
+ if (!SplitFiemap::GetSplitFileList(image_header, &file_list)) {
+ LOG(ERROR) << "Could not get image file list";
+ return false;
+ }
+
+ // Map each image file as a loopback device.
+ LoopControl control;
+ std::vector<std::string> loop_devices;
+ AutoDetachLoopDevices auto_detach(control, loop_devices);
+
+ auto start_time = std::chrono::steady_clock::now();
+ for (const auto& file : file_list) {
+ auto now = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
+
+ std::string loop_device;
+ if (!CreateLoopDevice(control, file, timeout_ms - elapsed, &loop_device)) {
+ break;
+ }
+ loop_devices.emplace_back(loop_device);
+ }
+ if (loop_devices.size() != file_list.size()) {
+ // The number of devices will mismatch if CreateLoopDevice() failed.
+ return false;
+ }
+
+ // If OptimizeLoopDevices fails, we'd use double the memory.
+ if (!OptimizeLoopDevices(loop_devices)) {
+ return false;
+ }
+
+ // If there's only one loop device (by far the most common case, splits
+ // will normally only happen on sdcards with FAT32), then just return that
+ // as the block device. Otherwise, we need to use dm-linear to stitch
+ // together all the loop devices we just created.
+ if (loop_devices.size() > 1) {
+ if (!MapWithLoopDeviceList(loop_devices, name, timeout_ms, path)) {
+ return false;
+ }
+ }
+
+ auto status_message = "loop:" + loop_devices.back();
+ auto status_file = GetStatusFilePath(name);
+ if (!android::base::WriteStringToFile(status_message, status_file)) {
+ PLOG(ERROR) << "Write failed: " << status_file;
+ return false;
+ }
+
+ auto_detach.Commit();
+
+ *path = loop_devices.back();
+ return true;
+}
+
+bool ImageManager::MapImageDevice(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms, std::string* path) {
+ if (IsImageMapped(name)) {
+ LOG(ERROR) << "Backing image " << name << " is already mapped";
+ return false;
+ }
+
+ auto image_header = GetImageHeaderPath(name);
+
+ // If there is a device-mapper node wrapping the block device, then we're
+ // able to create another node around it; the dm layer does not carry the
+ // exclusion lock down the stack when a mount occurs.
+ //
+ // If there is no intermediate device-mapper node, then partitions cannot be
+ // opened writable due to sepolicy and exclusivity of having a mounted
+ // filesystem. This should only happen on devices with no encryption, or
+ // devices with FBE and no metadata encryption. For these cases it suffices
+ // to perform normal file writes to /data/gsi (which is unencrypted).
+ std::string block_device;
+ bool can_use_devicemapper;
+ if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
+ LOG(ERROR) << "Could not determine block device for " << image_header;
+ return false;
+ }
+
+ if (can_use_devicemapper) {
+ if (!MapWithDmLinear(*partition_opener_.get(), name, timeout_ms, path)) {
+ return false;
+ }
+ } else if (!MapWithLoopDevice(name, timeout_ms, path)) {
+ return false;
+ }
+
+ // Set a property so we remember this is mapped.
+ auto prop_name = GetStatusPropertyName(name);
+ if (!android::base::SetProperty(prop_name, *path)) {
+ UnmapImageDevice(name, true);
+ return false;
+ }
+ return true;
+}
+
+bool ImageManager::MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+ std::string* dev) {
+ std::string ignore_path;
+ if (!MapWithDmLinear(opener, name, {}, &ignore_path)) {
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ if (!dm.GetDeviceString(name, dev)) {
+ return false;
+ }
+ return true;
+}
+
+bool ImageManager::UnmapImageDevice(const std::string& name) {
+ return UnmapImageDevice(name, false);
+}
+
+bool ImageManager::UnmapImageDevice(const std::string& name, bool force) {
+ if (!force && !IsImageMapped(name)) {
+ LOG(ERROR) << "Backing image " << name << " is not mapped";
+ return false;
+ }
+ auto& dm = DeviceMapper::Instance();
+ LoopControl loop;
+
+ std::string status;
+ auto status_file = GetStatusFilePath(name);
+ if (!android::base::ReadFileToString(status_file, &status)) {
+ PLOG(ERROR) << "Read failed: " << status_file;
+ return false;
+ }
+
+ auto lines = android::base::Split(status, "\n");
+ for (const auto& line : lines) {
+ auto pieces = android::base::Split(line, ":");
+ if (pieces.size() != 2) {
+ LOG(ERROR) << "Unknown status line";
+ continue;
+ }
+ if (pieces[0] == "dm") {
+ // Failure to remove a dm node is fatal, since we can't safely
+ // remove the file or loop devices.
+ const auto& name = pieces[1];
+ if (!dm.DeleteDeviceIfExists(name)) {
+ return false;
+ }
+ } else if (pieces[0] == "loop") {
+ // Failure to remove a loop device is not fatal, since we can still
+ // remove the backing file if we want.
+ loop.Detach(pieces[1]);
+ } else {
+ LOG(ERROR) << "Unknown status: " << pieces[0];
+ }
+ }
+
+ std::string message;
+ if (!android::base::RemoveFileIfExists(status_file, &message)) {
+ LOG(ERROR) << "Could not remove " << status_file << ": " << message;
+ }
+
+ auto status_prop = GetStatusPropertyName(name);
+ android::base::SetProperty(status_prop, "");
+ return true;
+}
+
+bool ImageManager::RemoveAllImages() {
+ if (!MetadataExists(metadata_dir_)) {
+ return true;
+ }
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (!metadata) {
+ return RemoveAllMetadata(metadata_dir_);
+ }
+
+ bool ok = true;
+ for (const auto& partition : metadata->partitions) {
+ auto partition_name = GetPartitionName(partition);
+ ok &= DeleteBackingImage(partition_name);
+ }
+ return ok && RemoveAllMetadata(metadata_dir_);
+}
+
+bool ImageManager::Validate() {
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (!metadata) {
+ return false;
+ }
+
+ for (const auto& partition : metadata->partitions) {
+ auto name = GetPartitionName(partition);
+ auto image_path = GetImageHeaderPath(name);
+ auto fiemap = SplitFiemap::Open(image_path);
+ if (!fiemap || !fiemap->HasPinnedExtents()) {
+ LOG(ERROR) << "Image is missing or was moved: " << image_path;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ImageManager::DisableImage(const std::string& name) {
+ return AddAttributes(metadata_dir_, name, LP_PARTITION_ATTR_DISABLED);
+}
+
+bool ImageManager::RemoveDisabledImages() {
+ if (!MetadataExists(metadata_dir_)) {
+ return true;
+ }
+
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (!metadata) {
+ return false;
+ }
+
+ bool ok = true;
+ for (const auto& partition : metadata->partitions) {
+ if (partition.attributes & LP_PARTITION_ATTR_DISABLED) {
+ ok &= DeleteBackingImage(GetPartitionName(partition));
+ }
+ }
+ return ok;
+}
+
+bool ImageManager::GetMappedImageDevice(const std::string& name, std::string* device) {
+ auto prop_name = GetStatusPropertyName(name);
+ *device = android::base::GetProperty(prop_name, "");
+ if (!device->empty()) {
+ return true;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ if (dm.GetState(name) == DmDeviceState::INVALID) {
+ return false;
+ }
+ return dm.GetDmDevicePathByName(name, device);
+}
+
+bool ImageManager::MapAllImages(const std::function<bool(std::set<std::string>)>& init) {
+ if (!MetadataExists(metadata_dir_)) {
+ return true;
+ }
+
+ auto metadata = OpenMetadata(metadata_dir_);
+ if (!metadata) {
+ return false;
+ }
+
+ std::set<std::string> devices;
+ for (const auto& name : GetBlockDevicePartitionNames(*metadata.get())) {
+ devices.emplace(name);
+ }
+ if (!init(std::move(devices))) {
+ return false;
+ }
+
+ auto data_device = GetMetadataSuperBlockDevice(*metadata.get());
+ auto data_partition_name = GetBlockDevicePartitionName(*data_device);
+ return CreateLogicalPartitions(*metadata.get(), data_partition_name);
+}
+
+std::unique_ptr<MappedDevice> MappedDevice::Open(IImageManager* manager,
+ const std::chrono::milliseconds& timeout_ms,
+ const std::string& name) {
+ std::string path;
+ if (!manager->MapImageDevice(name, timeout_ms, &path)) {
+ return nullptr;
+ }
+
+ auto device = std::unique_ptr<MappedDevice>(new MappedDevice(manager, name, path));
+ if (device->fd() < 0) {
+ return nullptr;
+ }
+ return device;
+}
+
+MappedDevice::MappedDevice(IImageManager* manager, const std::string& name, const std::string& path)
+ : manager_(manager), name_(name), path_(path) {
+ // The device is already mapped; try and open it.
+ fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC));
+}
+
+MappedDevice::~MappedDevice() {
+ fd_ = {};
+ manager_->UnmapImageDevice(name_);
+}
+
+bool IImageManager::UnmapImageIfExists(const std::string& name) {
+ // No lock is needed even though this seems to be vulnerable to TOCTOU. If process A
+ // calls MapImageDevice() while process B calls UnmapImageIfExists(), and MapImageDevice()
+ // happens after process B checks IsImageMapped(), it would be as if MapImageDevice() is called
+ // after process B finishes calling UnmapImageIfExists(), resulting the image to be mapped,
+ // which is a reasonable sequence.
+ if (!IsImageMapped(name)) {
+ return true;
+ }
+ return UnmapImageDevice(name);
+}
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/image_test.cpp b/fs_mgr/libfiemap/image_test.cpp
new file mode 100644
index 0000000..80c340f
--- /dev/null
+++ b/fs_mgr/libfiemap/image_test.cpp
@@ -0,0 +1,280 @@
+//
+// Copyright (C) 2019 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 <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <iostream>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <fs_mgr/file_wait.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
+using namespace android::dm;
+using namespace std::literals;
+using android::base::unique_fd;
+using android::fiemap::ImageManager;
+using android::fs_mgr::BlockDeviceInfo;
+using android::fs_mgr::PartitionOpener;
+using android::fs_mgr::WaitForFile;
+
+static std::string gDataPath;
+static std::string gDataMountPath;
+static constexpr char kMetadataPath[] = "/metadata/gsi/test";
+
+static constexpr uint64_t kTestImageSize = 1024 * 1024;
+
+class TestPartitionOpener final : public PartitionOpener {
+ public:
+ android::base::unique_fd Open(const std::string& partition_name, int flags) const override {
+ return PartitionOpener::Open(GetPathForBlockDeviceName(partition_name), flags);
+ }
+ bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override {
+ return PartitionOpener::GetInfo(GetPathForBlockDeviceName(partition_name), info);
+ }
+ std::string GetDeviceString(const std::string& partition_name) const override {
+ return PartitionOpener::GetDeviceString(GetPathForBlockDeviceName(partition_name));
+ }
+
+ private:
+ static std::string GetPathForBlockDeviceName(const std::string& name) {
+ if (android::base::StartsWith(name, "loop") || android::base::StartsWith(name, "dm-")) {
+ return "/dev/block/"s + name;
+ }
+ return name;
+ }
+};
+
+// This fixture is for tests against the device's native configuration.
+class NativeTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ manager_ = ImageManager::Open(kMetadataPath, gDataPath);
+ ASSERT_NE(manager_, nullptr);
+
+ manager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
+
+ const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+ base_name_ = tinfo->name();
+ }
+
+ void TearDown() override {
+ manager_->UnmapImageDevice(base_name_);
+ manager_->DeleteBackingImage(base_name_);
+ }
+
+ std::string PropertyName() { return "gsid.mapped_image." + base_name_; }
+
+ std::unique_ptr<ImageManager> manager_;
+ std::string base_name_;
+};
+
+TEST_F(NativeTest, CreateAndMap) {
+ ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr));
+
+ std::string path;
+ ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &path));
+ ASSERT_TRUE(manager_->IsImageMapped(base_name_));
+ ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), path);
+
+ {
+ unique_fd fd(open(path.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC));
+ ASSERT_GE(fd, 0);
+ ASSERT_EQ(get_block_device_size(fd), kTestImageSize);
+ }
+
+ ASSERT_TRUE(manager_->UnmapImageDevice(base_name_));
+ ASSERT_FALSE(manager_->IsImageMapped(base_name_));
+ ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), "");
+}
+
+TEST_F(NativeTest, DisableImage) {
+ ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr));
+ ASSERT_TRUE(manager_->BackingImageExists(base_name_));
+ ASSERT_TRUE(manager_->DisableImage(base_name_));
+ ASSERT_TRUE(manager_->RemoveDisabledImages());
+ ASSERT_TRUE(!manager_->BackingImageExists(base_name_));
+}
+
+TEST_F(NativeTest, GetMappedImageDevice) {
+ ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr));
+
+ std::string path1, path2;
+ ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &path1));
+ ASSERT_TRUE(manager_->GetMappedImageDevice(base_name_, &path2));
+ EXPECT_EQ(path1, path2);
+
+ ASSERT_TRUE(manager_->UnmapImageDevice(base_name_));
+}
+
+// This fixture is for tests against a simulated device environment. Rather
+// than use /data, we create an image and then layer a new filesystem within
+// it. Each test then decides how to mount and create layered images. This
+// allows us to test FBE vs FDE configurations.
+class ImageTest : public ::testing::Test {
+ public:
+ ImageTest() : dm_(DeviceMapper::Instance()) {}
+
+ void SetUp() override {
+ manager_ = ImageManager::Open(kMetadataPath, gDataPath);
+ ASSERT_NE(manager_, nullptr);
+
+ manager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
+
+ submanager_ = ImageManager::Open(kMetadataPath + "/mnt"s, gDataPath + "/mnt"s);
+ ASSERT_NE(submanager_, nullptr);
+
+ submanager_->set_partition_opener(std::make_unique<TestPartitionOpener>());
+
+ // Ensure that metadata is cleared in between runs.
+ submanager_->RemoveAllImages();
+ manager_->RemoveAllImages();
+
+ const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info();
+ base_name_ = tinfo->name();
+ test_image_name_ = base_name_ + "-base";
+ wrapper_device_name_ = base_name_ + "-wrapper";
+
+ ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize * 16, false, nullptr));
+ ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &base_device_));
+ }
+
+ void TearDown() override {
+ submanager_->UnmapImageDevice(test_image_name_);
+ umount(gDataMountPath.c_str());
+ dm_.DeleteDeviceIfExists(wrapper_device_name_);
+ manager_->UnmapImageDevice(base_name_);
+ manager_->DeleteBackingImage(base_name_);
+ }
+
+ protected:
+ bool DoFormat(const std::string& device) {
+ // clang-format off
+ std::vector<std::string> mkfs_args = {
+ "/system/bin/mke2fs",
+ "-F",
+ "-b 4096",
+ "-t ext4",
+ "-m 0",
+ "-O has_journal",
+ device,
+ ">/dev/null",
+ "2>/dev/null",
+ "</dev/null",
+ };
+ // clang-format on
+ auto command = android::base::Join(mkfs_args, " ");
+ return system(command.c_str()) == 0;
+ }
+
+ std::unique_ptr<ImageManager> manager_;
+ std::unique_ptr<ImageManager> submanager_;
+
+ DeviceMapper& dm_;
+ std::string base_name_;
+ std::string base_device_;
+ std::string test_image_name_;
+ std::string wrapper_device_name_;
+};
+
+TEST_F(ImageTest, DirectMount) {
+ ASSERT_TRUE(DoFormat(base_device_));
+ ASSERT_EQ(mount(base_device_.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0);
+ ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
+
+ std::string path;
+ ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
+ ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/loop"));
+}
+
+TEST_F(ImageTest, IndirectMount) {
+ // Create a simple wrapper around the base device that we'll mount from
+ // instead. This will simulate the code paths for dm-crypt/default-key/bow
+ // and force us to use device-mapper rather than loop devices.
+ uint64_t device_size = 0;
+ {
+ unique_fd fd(open(base_device_.c_str(), O_RDWR | O_CLOEXEC));
+ ASSERT_GE(fd, 0);
+ device_size = get_block_device_size(fd);
+ ASSERT_EQ(device_size, kTestImageSize * 16);
+ }
+ uint64_t num_sectors = device_size / 512;
+
+ auto& dm = DeviceMapper::Instance();
+
+ DmTable table;
+ table.Emplace<DmTargetLinear>(0, num_sectors, base_device_, 0);
+ ASSERT_TRUE(dm.CreateDevice(wrapper_device_name_, table));
+
+ // Format and mount.
+ std::string wrapper_device;
+ ASSERT_TRUE(dm.GetDmDevicePathByName(wrapper_device_name_, &wrapper_device));
+ ASSERT_TRUE(WaitForFile(wrapper_device, 5s));
+ ASSERT_TRUE(DoFormat(wrapper_device));
+ ASSERT_EQ(mount(wrapper_device.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0);
+
+ ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr));
+
+ std::set<std::string> backing_devices;
+ auto init = [&](std::set<std::string> devices) -> bool {
+ backing_devices = std::move(devices);
+ return true;
+ };
+
+ std::string path;
+ ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
+ ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-"));
+ ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_));
+ ASSERT_TRUE(submanager_->MapAllImages(init));
+ ASSERT_FALSE(backing_devices.empty());
+ ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_));
+}
+
+bool Mkdir(const std::string& path) {
+ if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
+ std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
+ return false;
+ }
+ return true;
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ if (argc >= 2) {
+ gDataPath = argv[1];
+ } else {
+ gDataPath = "/data/gsi/test";
+ }
+ gDataMountPath = gDataPath + "/mnt"s;
+
+ if (!Mkdir(gDataPath) || !Mkdir(kMetadataPath) || !Mkdir(gDataMountPath) ||
+ !Mkdir(kMetadataPath + "/mnt"s)) {
+ return 1;
+ }
+ return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/libfiemap/include/libfiemap/fiemap_writer.h b/fs_mgr/libfiemap/include/libfiemap/fiemap_writer.h
new file mode 100644
index 0000000..c692265
--- /dev/null
+++ b/fs_mgr/libfiemap/include/libfiemap/fiemap_writer.h
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <linux/fiemap.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace fiemap {
+
+class FiemapWriter;
+using FiemapUniquePtr = std::unique_ptr<FiemapWriter>;
+
+class FiemapWriter final {
+ public:
+ // Factory method for FiemapWriter.
+ // The method returns FiemapUniquePtr that contains all the data necessary to be able to write
+ // to the given file directly using raw block i/o. The optional progress callback will be
+ // invoked, if create is true, while the file is being initialized. It receives the bytes
+ // written and the number of total bytes. If the callback returns false, the operation will
+ // fail.
+ //
+ // Note: when create is true, the file size will be aligned up to the nearest file system
+ // block.
+ static FiemapUniquePtr Open(const std::string& file_path, uint64_t file_size,
+ bool create = true,
+ std::function<bool(uint64_t, uint64_t)> progress = {});
+
+ // Check that a file still has the same extents since it was last opened with FiemapWriter,
+ // assuming the file was not resized outside of FiemapWriter. Returns false either on error
+ // or if the file was not pinned.
+ //
+ // This will always return true on Ext4. On F2FS, it will return true if either of the
+ // following cases are true:
+ // - The file was never pinned.
+ // - The file is pinned and has not been moved by the GC.
+ // Thus, this method should only be called for pinned files (such as those returned by
+ // FiemapWriter::Open).
+ static bool HasPinnedExtents(const std::string& file_path);
+
+ // Returns the underlying block device of a file. This will look past device-mapper layers
+ // as long as each layer would not change block mappings (i.e., dm-crypt, dm-bow, and dm-
+ // default-key tables are okay; dm-linear is not). If a mapping such as dm-linear is found,
+ // it will be returned in place of any physical block device.
+ //
+ // It is the caller's responsibility to check whether the returned block device is acceptable.
+ // Gsid, for example, will only accept /dev/block/by-name/userdata as the bottom device.
+ // Callers can check the device name (dm- or loop prefix), inspect sysfs, or compare the major
+ // number against a boot device.
+ //
+ // If device-mapper nodes were encountered, then |uses_dm| will be set to true.
+ static bool GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path,
+ bool* uses_dm = nullptr);
+
+ ~FiemapWriter() = default;
+
+ const std::string& file_path() const { return file_path_; };
+ uint64_t size() const { return file_size_; };
+ const std::string& bdev_path() const { return bdev_path_; };
+ uint64_t block_size() const { return block_size_; };
+ const std::vector<struct fiemap_extent>& extents() { return extents_; };
+ uint32_t fs_type() const { return fs_type_; }
+
+ // Non-copyable & Non-movable
+ FiemapWriter(const FiemapWriter&) = delete;
+ FiemapWriter& operator=(const FiemapWriter&) = delete;
+ FiemapWriter& operator=(FiemapWriter&&) = delete;
+ FiemapWriter(FiemapWriter&&) = delete;
+
+ private:
+ // Name of the file managed by this class.
+ std::string file_path_;
+ // Block device on which we have created the file.
+ std::string bdev_path_;
+
+ // Size in bytes of the file this class is writing
+ uint64_t file_size_;
+
+ // total size in bytes of the block device
+ uint64_t bdev_size_;
+
+ // Filesystem type where the file is being created.
+ // See: <uapi/linux/magic.h> for filesystem magic numbers
+ uint32_t fs_type_;
+
+ // block size as reported by the kernel of the underlying block device;
+ uint64_t block_size_;
+
+ // This file's fiemap
+ std::vector<struct fiemap_extent> extents_;
+
+ FiemapWriter() = default;
+};
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
new file mode 100644
index 0000000..7b907c0
--- /dev/null
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -0,0 +1,213 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+
+#include <stdint.h>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <set>
+#include <string>
+
+#include <android-base/unique_fd.h>
+#include <liblp/partition_opener.h>
+
+namespace android {
+namespace fiemap {
+
+class IImageManager {
+ public:
+ using IPartitionOpener = android::fs_mgr::IPartitionOpener;
+
+ virtual ~IImageManager() {}
+
+ // When linking to libfiemap_binder, the Open() call will use binder.
+ // Otherwise, the Open() call will use the ImageManager implementation
+ // below.
+ static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix,
+ const std::chrono::milliseconds& timeout_ms);
+
+ // Flags for CreateBackingImage().
+ static constexpr int CREATE_IMAGE_DEFAULT = 0x0;
+ static constexpr int CREATE_IMAGE_READONLY = 0x1;
+ static constexpr int CREATE_IMAGE_ZERO_FILL = 0x2;
+
+ // Create an image that can be mapped as a block-device. If |force_zero_fill|
+ // is true, the image will be zero-filled. Otherwise, the initial content
+ // of the image is undefined. If zero-fill is requested, and the operation
+ // cannot be completed, the image will be deleted and this function will
+ // return false.
+ virtual bool CreateBackingImage(const std::string& name, uint64_t size, int flags) = 0;
+
+ // Delete an image created with CreateBackingImage. Its entry will be
+ // removed from the associated lp_metadata file.
+ virtual bool DeleteBackingImage(const std::string& name) = 0;
+
+ // Create a block device for an image previously created with
+ // CreateBackingImage. This will wait for at most |timeout_ms| milliseconds
+ // for |path| to be available, and will return false if not available in
+ // the requested time. If |timeout_ms| is zero, this is NOT guaranteed to
+ // return true. A timeout of 10s is recommended.
+ //
+ // Note that snapshots created with a readonly flag are always mapped
+ // writable. The flag is persisted in the lp_metadata file however, so if
+ // fs_mgr::CreateLogicalPartition(s) is used, the flag will be respected.
+ virtual bool MapImageDevice(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms, std::string* path) = 0;
+
+ // Unmap a block device previously mapped with mapBackingImage.
+ virtual bool UnmapImageDevice(const std::string& name) = 0;
+
+ // Returns true whether the named backing image exists.
+ virtual bool BackingImageExists(const std::string& name) = 0;
+
+ // Returns true if the specified image is mapped to a device.
+ virtual bool IsImageMapped(const std::string& name) = 0;
+
+ // Map an image using device-mapper. This is not available over binder, and
+ // is intended only for first-stage init. The returned device is a major:minor
+ // device string.
+ virtual bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+ std::string* dev) = 0;
+
+ // If an image was mapped, return the path to its device. Otherwise, return
+ // false. Errors are not reported in this case, calling IsImageMapped is
+ // not necessary.
+ virtual bool GetMappedImageDevice(const std::string& name, std::string* device) = 0;
+
+ // Map all images owned by this manager. This is only intended to be used
+ // during first-stage init, and as such, it does not provide a timeout
+ // (meaning libdm races can't be resolved, as ueventd is not available),
+ // and is not available over binder.
+ //
+ // The callback provided is given the list of dependent block devices.
+ virtual bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) = 0;
+
+ // Mark an image as disabled. This is useful for marking an image as
+ // will-be-deleted in recovery, since recovery cannot mount /data.
+ //
+ // This is not available in binder, since it is intended for recovery.
+ // When binder is available, images can simply be removed.
+ virtual bool DisableImage(const std::string& name) = 0;
+
+ // Remove all images that been marked as disabled.
+ virtual bool RemoveDisabledImages() = 0;
+
+ // Get all backing image names.
+ virtual std::vector<std::string> GetAllBackingImages() = 0;
+
+ // Writes |bytes| zeros to |name| file. If |bytes| is 0, then the
+ // whole file if filled with zeros.
+ virtual bool ZeroFillNewImage(const std::string& name, uint64_t bytes) = 0;
+
+ // Find and remove all images and metadata for this manager.
+ virtual bool RemoveAllImages() = 0;
+
+ virtual bool UnmapImageIfExists(const std::string& name);
+};
+
+class ImageManager final : public IImageManager {
+ public:
+ // Return an ImageManager for the given metadata and data directories. Both
+ // directories must already exist.
+ static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir,
+ const std::string& data_dir);
+
+ // Helper function that derives the metadata and data dirs given a single
+ // prefix.
+ static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
+
+ // Methods that must be implemented from IImageManager.
+ bool CreateBackingImage(const std::string& name, uint64_t size, int flags) override;
+ bool DeleteBackingImage(const std::string& name) override;
+ bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+ std::string* path) override;
+ bool UnmapImageDevice(const std::string& name) override;
+ bool BackingImageExists(const std::string& name) override;
+ bool IsImageMapped(const std::string& name) override;
+ bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
+ std::string* dev) override;
+ bool RemoveAllImages() override;
+ bool DisableImage(const std::string& name) override;
+ bool RemoveDisabledImages() override;
+ bool GetMappedImageDevice(const std::string& name, std::string* device) override;
+ bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override;
+
+ std::vector<std::string> GetAllBackingImages();
+ // Same as CreateBackingImage, but provides a progress notification.
+ bool CreateBackingImage(const std::string& name, uint64_t size, int flags,
+ std::function<bool(uint64_t, uint64_t)>&& on_progress);
+
+ // Returns true if the named partition exists. This does not check the
+ // consistency of the backing image/data file.
+ bool PartitionExists(const std::string& name);
+
+ // Validates that all images still have pinned extents. This will be removed
+ // once b/134588268 is fixed.
+ bool Validate();
+
+ void set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener);
+
+ // Writes |bytes| zeros at the beginning of the passed image
+ bool ZeroFillNewImage(const std::string& name, uint64_t bytes);
+
+ private:
+ ImageManager(const std::string& metadata_dir, const std::string& data_dir);
+ std::string GetImageHeaderPath(const std::string& name);
+ std::string GetStatusFilePath(const std::string& image_name);
+ bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
+ std::string* path);
+ bool MapWithLoopDeviceList(const std::vector<std::string>& device_list, const std::string& name,
+ const std::chrono::milliseconds& timeout_ms, std::string* path);
+ bool MapWithDmLinear(const IPartitionOpener& opener, const std::string& name,
+ const std::chrono::milliseconds& timeout_ms, std::string* path);
+ bool UnmapImageDevice(const std::string& name, bool force);
+
+ ImageManager(const ImageManager&) = delete;
+ ImageManager& operator=(const ImageManager&) = delete;
+ ImageManager& operator=(ImageManager&&) = delete;
+ ImageManager(ImageManager&&) = delete;
+
+ std::string metadata_dir_;
+ std::string data_dir_;
+ std::unique_ptr<IPartitionOpener> partition_opener_;
+};
+
+// RAII helper class for mapping and opening devices with an ImageManager.
+class MappedDevice final {
+ public:
+ static std::unique_ptr<MappedDevice> Open(IImageManager* manager,
+ const std::chrono::milliseconds& timeout_ms,
+ const std::string& name);
+
+ ~MappedDevice();
+
+ int fd() const { return fd_; }
+ const std::string& path() const { return path_; }
+
+ protected:
+ MappedDevice(IImageManager* manager, const std::string& name, const std::string& path);
+
+ IImageManager* manager_;
+ std::string name_;
+ std::string path_;
+ android::base::unique_fd fd_;
+};
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/include/libfiemap/split_fiemap_writer.h b/fs_mgr/libfiemap/include/libfiemap/split_fiemap_writer.h
new file mode 100644
index 0000000..feffb3d
--- /dev/null
+++ b/fs_mgr/libfiemap/include/libfiemap/split_fiemap_writer.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "fiemap_writer.h"
+
+namespace android {
+namespace fiemap {
+
+// Wrapper around FiemapWriter that is able to split images across files if
+// necessary.
+class SplitFiemap final {
+ public:
+ using ProgressCallback = std::function<bool(uint64_t, uint64_t)>;
+
+ // Create a new split fiemap file. If |max_piece_size| is 0, the number of
+ // pieces will be determined automatically by detecting the filesystem.
+ // Otherwise, the file will be split evenly (with the remainder in the
+ // final file).
+ static std::unique_ptr<SplitFiemap> Create(const std::string& file_path, uint64_t file_size,
+ uint64_t max_piece_size,
+ ProgressCallback progress = {});
+
+ // Open an existing split fiemap file.
+ static std::unique_ptr<SplitFiemap> Open(const std::string& file_path);
+
+ ~SplitFiemap();
+
+ // Return a list of all files created for a split file.
+ static bool GetSplitFileList(const std::string& file_path, std::vector<std::string>* list);
+
+ // Destroy all components of a split file. If the root file does not exist,
+ // this returns true and does not report an error.
+ static bool RemoveSplitFiles(const std::string& file_path, std::string* message = nullptr);
+
+ // Return whether all components of a split file still have pinned extents.
+ bool HasPinnedExtents() const;
+
+ // Helper method for writing data that spans files. Note there is no seek
+ // method (yet); this starts at 0 and increments the position by |bytes|.
+ bool Write(const void* data, uint64_t bytes);
+
+ // Flush all writes to all split files.
+ bool Flush();
+
+ const std::vector<struct fiemap_extent>& extents();
+ uint32_t block_size() const;
+ uint64_t size() const { return total_size_; }
+ const std::string& bdev_path() const;
+
+ // Non-copyable & Non-movable
+ SplitFiemap(const SplitFiemap&) = delete;
+ SplitFiemap& operator=(const SplitFiemap&) = delete;
+ SplitFiemap& operator=(SplitFiemap&&) = delete;
+ SplitFiemap(SplitFiemap&&) = delete;
+
+ private:
+ SplitFiemap() = default;
+ void AddFile(FiemapUniquePtr&& file);
+
+ bool creating_ = false;
+ std::string list_file_;
+ std::vector<FiemapUniquePtr> files_;
+ std::vector<struct fiemap_extent> extents_;
+ uint64_t total_size_ = 0;
+
+ // Most recently open file and position for Write().
+ size_t cursor_index_ = 0;
+ uint64_t cursor_file_pos_ = 0;
+ android::base::unique_fd cursor_fd_;
+};
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/metadata.cpp b/fs_mgr/libfiemap/metadata.cpp
new file mode 100644
index 0000000..ea1f508
--- /dev/null
+++ b/fs_mgr/libfiemap/metadata.cpp
@@ -0,0 +1,214 @@
+//
+// Copyright (C) 2019 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 "metadata.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <liblp/builder.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using namespace android::fs_mgr;
+
+static constexpr uint32_t kMaxMetadataSize = 256 * 1024;
+
+std::string GetMetadataFile(const std::string& metadata_dir) {
+ return JoinPaths(metadata_dir, "lp_metadata");
+}
+
+bool MetadataExists(const std::string& metadata_dir) {
+ auto metadata_file = GetMetadataFile(metadata_dir);
+ return access(metadata_file.c_str(), F_OK) == 0;
+}
+
+std::unique_ptr<LpMetadata> OpenMetadata(const std::string& metadata_dir) {
+ auto metadata_file = GetMetadataFile(metadata_dir);
+ auto metadata = ReadFromImageFile(metadata_file);
+ if (!metadata) {
+ LOG(ERROR) << "Could not read metadata file " << metadata_file;
+ return nullptr;
+ }
+ return metadata;
+}
+
+// :TODO: overwrite on create if open fails
+std::unique_ptr<MetadataBuilder> OpenOrCreateMetadata(const std::string& metadata_dir,
+ SplitFiemap* file) {
+ auto metadata_file = GetMetadataFile(metadata_dir);
+
+ PartitionOpener opener;
+ std::unique_ptr<MetadataBuilder> builder;
+ if (access(metadata_file.c_str(), R_OK)) {
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "access " << metadata_file << " failed:";
+ return nullptr;
+ }
+
+ auto data_device = GetDevicePathForFile(file);
+
+ BlockDeviceInfo device_info;
+ if (!opener.GetInfo(data_device, &device_info)) {
+ LOG(ERROR) << "Could not read partition: " << data_device;
+ return nullptr;
+ }
+
+ std::vector<BlockDeviceInfo> block_devices = {device_info};
+ auto super_name = android::base::Basename(data_device);
+ builder = MetadataBuilder::New(block_devices, super_name, kMaxMetadataSize, 1);
+ } else {
+ auto metadata = OpenMetadata(metadata_dir);
+ if (!metadata) {
+ return nullptr;
+ }
+ builder = MetadataBuilder::New(*metadata.get(), &opener);
+ }
+
+ if (!builder) {
+ LOG(ERROR) << "Could not create metadata builder";
+ return nullptr;
+ }
+ return builder;
+}
+
+bool SaveMetadata(MetadataBuilder* builder, const std::string& metadata_dir) {
+ auto exported = builder->Export();
+ if (!exported) {
+ LOG(ERROR) << "Unable to export new metadata";
+ return false;
+ }
+
+ // If there are no more partitions in the metadata, just delete the file.
+ auto metadata_file = GetMetadataFile(metadata_dir);
+ if (exported->partitions.empty() && android::base::RemoveFileIfExists(metadata_file)) {
+ return true;
+ }
+ if (!WriteToImageFile(metadata_file, *exported.get())) {
+ LOG(ERROR) << "Unable to save new metadata";
+ return false;
+ }
+ return true;
+}
+
+bool RemoveAllMetadata(const std::string& dir) {
+ auto metadata_file = GetMetadataFile(dir);
+ return android::base::RemoveFileIfExists(metadata_file);
+}
+
+bool FillPartitionExtents(MetadataBuilder* builder, Partition* partition, SplitFiemap* file,
+ uint64_t partition_size) {
+ auto block_device = android::base::Basename(GetDevicePathForFile(file));
+
+ uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE;
+ for (const auto& extent : file->extents()) {
+ if (extent.fe_length % LP_SECTOR_SIZE != 0) {
+ LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length;
+ return false;
+ }
+ if (extent.fe_physical % LP_SECTOR_SIZE != 0) {
+ LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical;
+ return false;
+ }
+
+ uint64_t num_sectors =
+ std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed);
+ if (!num_sectors || !sectors_needed) {
+ // This should never happen, but we include it just in case. It would
+ // indicate that the last filesystem block had multiple extents.
+ LOG(WARNING) << "FiemapWriter allocated extra blocks";
+ break;
+ }
+
+ uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE;
+ if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) {
+ LOG(ERROR) << "Could not add extent to lp metadata";
+ return false;
+ }
+
+ sectors_needed -= num_sectors;
+ }
+ return true;
+}
+
+bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name) {
+ if (!MetadataExists(metadata_dir)) {
+ return true;
+ }
+ auto metadata = OpenMetadata(metadata_dir);
+ if (!metadata) {
+ return false;
+ }
+
+ PartitionOpener opener;
+ auto builder = MetadataBuilder::New(*metadata.get(), &opener);
+ if (!builder) {
+ return false;
+ }
+ builder->RemovePartition(partition_name);
+ return SaveMetadata(builder.get(), metadata_dir);
+}
+
+bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name,
+ SplitFiemap* file, uint64_t partition_size, bool readonly) {
+ auto builder = OpenOrCreateMetadata(metadata_dir, file);
+ if (!builder) {
+ return false;
+ }
+ auto partition = builder->FindPartition(partition_name);
+ if (!partition) {
+ int attrs = 0;
+ if (readonly) attrs |= LP_PARTITION_ATTR_READONLY;
+
+ if ((partition = builder->AddPartition(partition_name, attrs)) == nullptr) {
+ LOG(ERROR) << "Could not add partition " << partition_name << " to metadata";
+ return false;
+ }
+ }
+ partition->RemoveExtents();
+
+ if (!FillPartitionExtents(builder.get(), partition, file, partition_size)) {
+ return false;
+ }
+ return SaveMetadata(builder.get(), metadata_dir);
+}
+
+bool AddAttributes(const std::string& metadata_dir, const std::string& partition_name,
+ uint32_t attributes) {
+ auto metadata = OpenMetadata(metadata_dir);
+ if (!metadata) {
+ return false;
+ }
+ auto builder = MetadataBuilder::New(*metadata.get());
+ if (!builder) {
+ return false;
+ }
+ auto partition = builder->FindPartition(partition_name);
+ if (!partition) {
+ return false;
+ }
+ partition->set_attributes(partition->attributes() | attributes);
+ return SaveMetadata(builder.get(), metadata_dir);
+}
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/metadata.h b/fs_mgr/libfiemap/metadata.h
new file mode 100644
index 0000000..4eb3ad5
--- /dev/null
+++ b/fs_mgr/libfiemap/metadata.h
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2019 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <libfiemap/split_fiemap_writer.h>
+#include <liblp/liblp.h>
+
+namespace android {
+namespace fiemap {
+
+bool MetadataExists(const std::string& metadata_dir);
+std::unique_ptr<android::fs_mgr::LpMetadata> OpenMetadata(const std::string& metadata_dir);
+bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name,
+ SplitFiemap* file, uint64_t partition_size, bool readonly);
+bool AddAttributes(const std::string& metadata_dir, const std::string& partition_name,
+ uint32_t attributes);
+bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name);
+bool RemoveAllMetadata(const std::string& dir);
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/passthrough.cpp b/fs_mgr/libfiemap/passthrough.cpp
new file mode 100644
index 0000000..1ccd9a0
--- /dev/null
+++ b/fs_mgr/libfiemap/passthrough.cpp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 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 <libfiemap/image_manager.h>
+
+namespace android {
+namespace fiemap {
+
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir_prefix,
+ const std::chrono::milliseconds& timeout_ms) {
+ (void)timeout_ms;
+ return ImageManager::Open(dir_prefix);
+}
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/split_fiemap_writer.cpp b/fs_mgr/libfiemap/split_fiemap_writer.cpp
new file mode 100644
index 0000000..cc54f20
--- /dev/null
+++ b/fs_mgr/libfiemap/split_fiemap_writer.cpp
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2019 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 <libfiemap/split_fiemap_writer.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "utility.h"
+
+namespace android {
+namespace fiemap {
+
+using android::base::unique_fd;
+
+// We use a four-digit suffix at the end of filenames.
+static const size_t kMaxFilePieces = 500;
+
+std::unique_ptr<SplitFiemap> SplitFiemap::Create(const std::string& file_path, uint64_t file_size,
+ uint64_t max_piece_size,
+ ProgressCallback progress) {
+ if (!file_size) {
+ LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path;
+ return nullptr;
+ }
+
+ if (!max_piece_size) {
+ max_piece_size = DetermineMaximumFileSize(file_path);
+ if (!max_piece_size) {
+ LOG(ERROR) << "Could not determine maximum file size for " << file_path;
+ return nullptr;
+ }
+ }
+
+ // Remove any existing file.
+ RemoveSplitFiles(file_path);
+
+ // Call |progress| only when the total percentage would significantly change.
+ int permille = -1;
+ uint64_t total_bytes_written = 0;
+ auto on_progress = [&](uint64_t written, uint64_t) -> bool {
+ uint64_t actual_written = total_bytes_written + written;
+ int new_permille = (actual_written * 1000) / file_size;
+ if (new_permille != permille && actual_written < file_size) {
+ if (progress && !progress(actual_written, file_size)) {
+ return false;
+ }
+ permille = new_permille;
+ }
+ return true;
+ };
+
+ std::unique_ptr<SplitFiemap> out(new SplitFiemap());
+ out->creating_ = true;
+ out->list_file_ = file_path;
+
+ // Create the split files.
+ uint64_t remaining_bytes = file_size;
+ while (remaining_bytes) {
+ if (out->files_.size() >= kMaxFilePieces) {
+ LOG(ERROR) << "Requested size " << file_size << " created too many split files";
+ return nullptr;
+ }
+ std::string chunk_path =
+ android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size());
+ uint64_t chunk_size = std::min(max_piece_size, remaining_bytes);
+ auto writer = FiemapWriter::Open(chunk_path, chunk_size, true, on_progress);
+ if (!writer) {
+ return nullptr;
+ }
+
+ // To make sure the alignment doesn't create too much inconsistency, we
+ // account the *actual* size, not the requested size.
+ total_bytes_written += writer->size();
+
+ // writer->size() is block size aligned and could be bigger than remaining_bytes
+ // If remaining_bytes is bigger, set remaining_bytes to 0 to avoid underflow error.
+ remaining_bytes = remaining_bytes > writer->size() ? (remaining_bytes - writer->size()) : 0;
+
+ out->AddFile(std::move(writer));
+ }
+
+ // Create the split file list.
+ unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660));
+ if (fd < 0) {
+ PLOG(ERROR) << "Failed to open " << file_path;
+ return nullptr;
+ }
+
+ for (const auto& writer : out->files_) {
+ std::string line = android::base::Basename(writer->file_path()) + "\n";
+ if (!android::base::WriteFully(fd, line.data(), line.size())) {
+ PLOG(ERROR) << "Write failed " << file_path;
+ return nullptr;
+ }
+ }
+
+ // Unset this bit, so we don't unlink on destruction.
+ out->creating_ = false;
+ return out;
+}
+
+std::unique_ptr<SplitFiemap> SplitFiemap::Open(const std::string& file_path) {
+ std::vector<std::string> files;
+ if (!GetSplitFileList(file_path, &files)) {
+ return nullptr;
+ }
+
+ std::unique_ptr<SplitFiemap> out(new SplitFiemap());
+ out->list_file_ = file_path;
+
+ for (const auto& file : files) {
+ auto writer = FiemapWriter::Open(file, 0, false);
+ if (!writer) {
+ // Error was logged in Open().
+ return nullptr;
+ }
+ out->AddFile(std::move(writer));
+ }
+ return out;
+}
+
+bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector<std::string>* list) {
+ // This is not the most efficient thing, but it is simple and recovering
+ // the fiemap/fibmap is much more expensive.
+ std::string contents;
+ if (!android::base::ReadFileToString(file_path, &contents, true)) {
+ PLOG(ERROR) << "Error reading file: " << file_path;
+ return false;
+ }
+
+ std::vector<std::string> names = android::base::Split(contents, "\n");
+ std::string dir = android::base::Dirname(file_path);
+ for (const auto& name : names) {
+ if (!name.empty()) {
+ list->emplace_back(dir + "/" + name);
+ }
+ }
+ return true;
+}
+
+bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) {
+ // Early exit if this does not exist, and do not report an error.
+ if (access(file_path.c_str(), F_OK) && errno == ENOENT) {
+ return true;
+ }
+
+ bool ok = true;
+ std::vector<std::string> files;
+ if (GetSplitFileList(file_path, &files)) {
+ for (const auto& file : files) {
+ ok &= android::base::RemoveFileIfExists(file, message);
+ }
+ }
+ ok &= android::base::RemoveFileIfExists(file_path, message);
+ return ok;
+}
+
+bool SplitFiemap::HasPinnedExtents() const {
+ for (const auto& file : files_) {
+ if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+const std::vector<struct fiemap_extent>& SplitFiemap::extents() {
+ if (extents_.empty()) {
+ for (const auto& file : files_) {
+ const auto& extents = file->extents();
+ extents_.insert(extents_.end(), extents.begin(), extents.end());
+ }
+ }
+ return extents_;
+}
+
+bool SplitFiemap::Write(const void* data, uint64_t bytes) {
+ // Open the current file.
+ FiemapWriter* file = files_[cursor_index_].get();
+
+ const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(data);
+ uint64_t bytes_remaining = bytes;
+ while (bytes_remaining) {
+ // How many bytes can we write into the current file?
+ uint64_t file_bytes_left = file->size() - cursor_file_pos_;
+ if (!file_bytes_left) {
+ if (cursor_index_ == files_.size() - 1) {
+ LOG(ERROR) << "write past end of file requested";
+ return false;
+ }
+
+ // No space left in the current file, but we have more files to
+ // use, so prep the next one.
+ cursor_fd_ = {};
+ cursor_file_pos_ = 0;
+ file = files_[++cursor_index_].get();
+ file_bytes_left = file->size();
+ }
+
+ // Open the current file if it's not open.
+ if (cursor_fd_ < 0) {
+ cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY));
+ if (cursor_fd_ < 0) {
+ PLOG(ERROR) << "open failed: " << file->file_path();
+ return false;
+ }
+ CHECK(cursor_file_pos_ == 0);
+ }
+
+ if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
+ LOG(ERROR) << "file is no longer pinned: " << file->file_path();
+ return false;
+ }
+
+ uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining);
+ if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) {
+ PLOG(ERROR) << "write failed: " << file->file_path();
+ return false;
+ }
+ data_ptr += bytes_to_write;
+ bytes_remaining -= bytes_to_write;
+ cursor_file_pos_ += bytes_to_write;
+ }
+
+ // If we've reached the end of the current file, close it for sanity.
+ if (cursor_file_pos_ == file->size()) {
+ cursor_fd_ = {};
+ }
+ return true;
+}
+
+bool SplitFiemap::Flush() {
+ for (const auto& file : files_) {
+ unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ PLOG(ERROR) << "open failed: " << file->file_path();
+ return false;
+ }
+ if (fsync(fd)) {
+ PLOG(ERROR) << "fsync failed: " << file->file_path();
+ return false;
+ }
+ }
+ return true;
+}
+
+SplitFiemap::~SplitFiemap() {
+ if (!creating_) {
+ return;
+ }
+
+ // We failed to finish creating, so unlink everything.
+ unlink(list_file_.c_str());
+ for (auto&& file : files_) {
+ std::string path = file->file_path();
+ file = nullptr;
+
+ unlink(path.c_str());
+ }
+}
+
+void SplitFiemap::AddFile(FiemapUniquePtr&& file) {
+ total_size_ += file->size();
+ files_.emplace_back(std::move(file));
+}
+
+uint32_t SplitFiemap::block_size() const {
+ return files_[0]->block_size();
+}
+
+const std::string& SplitFiemap::bdev_path() const {
+ return files_[0]->bdev_path();
+}
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/testdata/file_32k b/fs_mgr/libfiemap/testdata/file_32k
new file mode 100644
index 0000000..12f3be4
--- /dev/null
+++ b/fs_mgr/libfiemap/testdata/file_32k
Binary files differ
diff --git a/fs_mgr/libfiemap/testdata/file_4k b/fs_mgr/libfiemap/testdata/file_4k
new file mode 100644
index 0000000..08e7df1
--- /dev/null
+++ b/fs_mgr/libfiemap/testdata/file_4k
Binary files differ
diff --git a/fs_mgr/libfiemap/testdata/unaligned_file b/fs_mgr/libfiemap/testdata/unaligned_file
new file mode 100644
index 0000000..c107c26
--- /dev/null
+++ b/fs_mgr/libfiemap/testdata/unaligned_file
Binary files differ
diff --git a/fs_mgr/libfiemap/utility.cpp b/fs_mgr/libfiemap/utility.cpp
new file mode 100644
index 0000000..955e544
--- /dev/null
+++ b/fs_mgr/libfiemap/utility.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 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 "utility.h"
+
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <libfiemap/fiemap_writer.h>
+
+namespace android {
+namespace fiemap {
+
+using namespace std::string_literals;
+using android::base::unique_fd;
+
+static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata";
+
+uint64_t DetermineMaximumFileSize(const std::string& file_path) {
+ // Create the smallest file possible (one block).
+ auto writer = FiemapWriter::Open(file_path, 1);
+ if (!writer) {
+ return 0;
+ }
+
+ uint64_t result = 0;
+ switch (writer->fs_type()) {
+ case EXT4_SUPER_MAGIC:
+ // The minimum is 16GiB, so just report that. If we wanted we could parse the
+ // superblock and figure out if 64-bit support is enabled.
+ result = 17179869184ULL;
+ break;
+ case F2FS_SUPER_MAGIC:
+ // Formula is from https://www.kernel.org/doc/Documentation/filesystems/f2fs.txt
+ // 4KB * (923 + 2 * 1018 + 2 * 1018 * 1018 + 1018 * 1018 * 1018) := 3.94TB.
+ result = 4329690886144ULL;
+ break;
+ case MSDOS_SUPER_MAGIC:
+ // 4GB-1, which we want aligned to the block size.
+ result = 4294967295;
+ result -= (result % writer->block_size());
+ break;
+ default:
+ LOG(ERROR) << "Unknown file system type: " << writer->fs_type();
+ break;
+ }
+
+ // Close and delete the temporary file.
+ writer = nullptr;
+ unlink(file_path.c_str());
+
+ return result;
+}
+
+// Given a SplitFiemap, this returns a device path that will work during first-
+// stage init (i.e., its path can be found by InitRequiredDevices).
+std::string GetDevicePathForFile(SplitFiemap* file) {
+ auto bdev_path = file->bdev_path();
+
+ struct stat userdata, given;
+ if (!stat(bdev_path.c_str(), &given) && !stat(kUserdataDevice, &userdata)) {
+ if (S_ISBLK(given.st_mode) && S_ISBLK(userdata.st_mode) &&
+ given.st_rdev == userdata.st_rdev) {
+ return kUserdataDevice;
+ }
+ }
+ return bdev_path;
+}
+
+std::string JoinPaths(const std::string& dir, const std::string& file) {
+ if (android::base::EndsWith(dir, "/")) {
+ return dir + file;
+ }
+ return dir + "/" + file;
+}
+
+bool F2fsPinBeforeAllocate(int file_fd, bool* supported) {
+ struct stat st;
+ if (fstat(file_fd, &st) < 0) {
+ PLOG(ERROR) << "stat failed";
+ return false;
+ }
+ std::string bdev;
+ if (!BlockDeviceToName(major(st.st_dev), minor(st.st_dev), &bdev)) {
+ LOG(ERROR) << "Failed to get block device name for " << major(st.st_dev) << ":"
+ << minor(st.st_dev);
+ return false;
+ }
+
+ std::string contents;
+ std::string feature_file = "/sys/fs/f2fs/" + bdev + "/features";
+ if (!android::base::ReadFileToString(feature_file, &contents)) {
+ PLOG(ERROR) << "read failed: " << feature_file;
+ return false;
+ }
+ contents = android::base::Trim(contents);
+
+ auto features = android::base::Split(contents, ", ");
+ auto iter = std::find(features.begin(), features.end(), "pin_file"s);
+ *supported = (iter != features.end());
+ return true;
+}
+
+bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_name) {
+ // The symlinks in /sys/dev/block point to the block device node under /sys/device/..
+ // The directory name in the target corresponds to the name of the block device. We use
+ // that to extract the block device name.
+ // e.g for block device name 'ram0', there exists a symlink named '1:0' in /sys/dev/block as
+ // follows.
+ // 1:0 -> ../../devices/virtual/block/ram0
+ std::string sysfs_path = ::android::base::StringPrintf("/sys/dev/block/%u:%u", major, minor);
+ std::string sysfs_bdev;
+
+ if (!::android::base::Readlink(sysfs_path, &sysfs_bdev)) {
+ PLOG(ERROR) << "Failed to read link at: " << sysfs_path;
+ return false;
+ }
+
+ *bdev_name = ::android::base::Basename(sysfs_bdev);
+ // Paranoid sanity check to make sure we just didn't get the
+ // input in return as-is.
+ if (sysfs_bdev == *bdev_name) {
+ LOG(ERROR) << "Malformed symlink for block device: " << sysfs_bdev;
+ return false;
+ }
+
+ return true;
+}
+
+bool FilesystemHasReliablePinning(const std::string& file, bool* supported) {
+ struct statfs64 sfs;
+ if (statfs64(file.c_str(), &sfs)) {
+ PLOG(ERROR) << "statfs failed: " << file;
+ return false;
+ }
+ if (sfs.f_type != F2FS_SUPER_MAGIC) {
+ *supported = true;
+ return true;
+ }
+
+ unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ PLOG(ERROR) << "open failed: " << file;
+ return false;
+ }
+ return F2fsPinBeforeAllocate(fd, supported);
+}
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/utility.h b/fs_mgr/libfiemap/utility.h
new file mode 100644
index 0000000..24ebc57
--- /dev/null
+++ b/fs_mgr/libfiemap/utility.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+
+#include <libfiemap/split_fiemap_writer.h>
+
+namespace android {
+namespace fiemap {
+
+// Given a file that will be created, determine the maximum size its containing
+// filesystem allows. Note this is a theoretical maximum size; free space is
+// ignored entirely.
+uint64_t DetermineMaximumFileSize(const std::string& file_path);
+
+// Given a SplitFiemap, this returns a device path that will work during first-
+// stage init (i.e., its path can be found by InitRequiredDevices).
+std::string GetDevicePathForFile(android::fiemap::SplitFiemap* file);
+
+// Combine two path components into a single path.
+std::string JoinPaths(const std::string& dir, const std::string& file);
+
+// Given a file within an F2FS filesystem, return whether or not the filesystem
+// supports the "pin_file" feature, which requires pinning before fallocation.
+bool F2fsPinBeforeAllocate(int file_fd, bool* supported);
+
+// Given a major/minor device number, return its canonical name such that
+// /dev/block/<name> resolves to the device.
+bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_name);
+
+// This is the same as F2fsPinBeforeAllocate, however, it will return true
+// (and supported = true) for non-f2fs filesystems. It is intended to be used
+// in conjunction with ImageManager to reject image requests for reliable use
+// cases (such as snapshots or adb remount).
+bool FilesystemHasReliablePinning(const std::string& file, bool* supported);
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 54350a5..d496466 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -253,7 +253,7 @@
header_.magic = LP_METADATA_HEADER_MAGIC;
header_.major_version = LP_METADATA_MAJOR_VERSION;
header_.minor_version = LP_METADATA_MINOR_VERSION_MIN;
- header_.header_size = sizeof(header_);
+ header_.header_size = sizeof(LpMetadataHeaderV1_0);
header_.partitions.entry_size = sizeof(LpMetadataPartition);
header_.extents.entry_size = sizeof(LpMetadataExtent);
header_.groups.entry_size = sizeof(LpMetadataPartitionGroup);
@@ -264,6 +264,12 @@
geometry_ = metadata.geometry;
block_devices_ = metadata.block_devices;
+ // Bump the version as necessary to copy any newer fields.
+ if (metadata.header.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
+ RequireExpandedMetadataHeader();
+ header_.flags = metadata.header.flags;
+ }
+
for (const auto& group : metadata.groups) {
std::string group_name = GetPartitionGroupName(group);
if (!AddGroup(group_name, group.maximum_size)) {
@@ -846,7 +852,7 @@
return nullptr;
}
- if (partition->attributes() & LP_PARTITION_ATTR_UPDATED) {
+ if (partition->attributes() & LP_PARTITION_ATTRIBUTE_MASK_V1) {
static const uint16_t kMinVersion = LP_METADATA_VERSION_FOR_UPDATED_ATTR;
metadata->header.minor_version = std::max(metadata->header.minor_version, kMinVersion);
}
@@ -883,6 +889,14 @@
return metadata;
}
+void MetadataBuilder::RequireExpandedMetadataHeader() {
+ if (header_.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
+ return;
+ }
+ header_.minor_version = LP_METADATA_VERSION_FOR_EXPANDED_HEADER;
+ header_.header_size = sizeof(LpMetadataHeaderV1_2);
+}
+
uint64_t MetadataBuilder::AllocatableSpace() const {
uint64_t total_size = 0;
for (const auto& block_device : block_devices_) {
@@ -1111,6 +1125,11 @@
auto_slot_suffixing_ = true;
}
+void MetadataBuilder::SetVirtualABDeviceFlag() {
+ RequireExpandedMetadataHeader();
+ header_.flags |= LP_HEADER_FLAG_VIRTUAL_AB_DEVICE;
+}
+
bool MetadataBuilder::IsABDevice() {
return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
}
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index a67ffa7..ca8df61 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -352,6 +352,7 @@
EXPECT_EQ(header.magic, LP_METADATA_HEADER_MAGIC);
EXPECT_EQ(header.major_version, LP_METADATA_MAJOR_VERSION);
EXPECT_EQ(header.minor_version, LP_METADATA_MINOR_VERSION_MIN);
+ EXPECT_EQ(header.header_size, sizeof(LpMetadataHeaderV1_0));
ASSERT_EQ(exported->partitions.size(), 2);
ASSERT_EQ(exported->extents.size(), 3);
@@ -917,3 +918,22 @@
std::vector<Interval>{Interval(0, 100, 150)})
.size());
}
+
+TEST_F(BuilderTest, ExpandedHeader) {
+ unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+ ASSERT_NE(builder, nullptr);
+
+ builder->RequireExpandedMetadataHeader();
+
+ unique_ptr<LpMetadata> exported = builder->Export();
+ ASSERT_NE(exported, nullptr);
+ EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2));
+
+ exported->header.flags = 0x5e5e5e5e;
+
+ builder = MetadataBuilder::New(*exported.get());
+ exported = builder->Export();
+ ASSERT_NE(exported, nullptr);
+ EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2));
+ EXPECT_EQ(exported->header.flags, 0x5e5e5e5e);
+}
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index 1e9d636..f7738fb 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -145,6 +145,7 @@
std::vector<std::unique_ptr<Extent>> extents_;
uint32_t attributes_;
uint64_t size_;
+ bool disabled_;
};
// An interval in the metadata. This is similar to a LinearExtent with one difference.
@@ -318,6 +319,8 @@
// Set the LP_METADATA_AUTO_SLOT_SUFFIXING flag.
void SetAutoSlotSuffixing();
+ // Set the LP_HEADER_FLAG_VIRTUAL_AB_DEVICE flag.
+ void SetVirtualABDeviceFlag();
// If set, checks for slot suffixes will be ignored internally.
void IgnoreSlotSuffixing();
@@ -325,6 +328,10 @@
bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const;
bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info);
+ // Require the expanded metadata header. This is exposed for testing, and
+ // is normally only called as needed by other methods.
+ void RequireExpandedMetadataHeader();
+
// Attempt to preserve the named partitions from an older metadata. If this
// is not possible (for example, the block device list has changed) then
// false is returned.
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index 6e928b4..41d8b0c 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -40,11 +40,14 @@
/* Current metadata version. */
#define LP_METADATA_MAJOR_VERSION 10
#define LP_METADATA_MINOR_VERSION_MIN 0
-#define LP_METADATA_MINOR_VERSION_MAX 1
+#define LP_METADATA_MINOR_VERSION_MAX 2
/* Metadata version needed to use the UPDATED partition attribute. */
#define LP_METADATA_VERSION_FOR_UPDATED_ATTR 1
+/* Metadata version needed for the new expanded header struct. */
+#define LP_METADATA_VERSION_FOR_EXPANDED_HEADER 2
+
/* Attributes for the LpMetadataPartition::attributes field.
*
* READONLY - The partition should not be considered writable. When used with
@@ -69,13 +72,17 @@
*/
#define LP_PARTITION_ATTR_UPDATED (1 << 2)
+/* This flag marks a partition as disabled. It should not be used or mapped. */
+#define LP_PARTITION_ATTR_DISABLED (1 << 3)
+
/* Mask that defines all valid attributes. When changing this, make sure to
* update ParseMetadata().
*/
#define LP_PARTITION_ATTRIBUTE_MASK_V0 \
(LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED)
-#define LP_PARTITION_ATTRIBUTE_MASK_V1 (LP_PARTITION_ATTRIBUTE_MASK_V0 | LP_PARTITION_ATTR_UPDATED)
-#define LP_PARTITION_ATTRIBUTE_MASK LP_PARTITION_ATTRIBUTE_MASK_V1
+#define LP_PARTITION_ATTRIBUTE_MASK_V1 (LP_PARTITION_ATTR_UPDATED | LP_PARTITION_ATTR_DISABLED)
+#define LP_PARTITION_ATTRIBUTE_MASK \
+ (LP_PARTITION_ATTRIBUTE_MASK_V0 | LP_PARTITION_ATTRIBUTE_MASK_V1)
/* Default name of the physical partition that holds logical partition entries.
* The layout of this partition will look like:
@@ -212,8 +219,27 @@
LpMetadataTableDescriptor groups;
/* 116: Block device table. */
LpMetadataTableDescriptor block_devices;
+
+ /* Everything past here is header version 1.2+, and is only included if
+ * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must
+ * zero these additional fields.
+ */
+
+ /* 128: See LP_HEADER_FLAG_ constants for possible values. Header flags are
+ * independent of the version number and intended to be informational only.
+ * New flags can be added without bumping the version.
+ */
+ uint32_t flags;
+
+ /* 132: Reserved (zero), pad to 256 bytes. */
+ uint8_t reserved[124];
} __attribute__((packed)) LpMetadataHeader;
+/* This device uses Virtual A/B. Note that on retrofit devices, the expanded
+ * header may not be present.
+ */
+#define LP_HEADER_FLAG_VIRTUAL_AB_DEVICE 0x1
+
/* This struct defines a logical partition entry, similar to what would be
* present in a GUID Partition Table.
*/
@@ -351,6 +377,25 @@
*/
#define LP_BLOCK_DEVICE_SLOT_SUFFIXED (1 << 0)
+/* For ease of writing compatibility checks, the original metadata header is
+ * preserved below, and typedefs are provided for the current version.
+ */
+typedef struct LpMetadataHeaderV1_0 {
+ uint32_t magic;
+ uint16_t major_version;
+ uint16_t minor_version;
+ uint32_t header_size;
+ uint8_t header_checksum[32];
+ uint32_t tables_size;
+ uint8_t tables_checksum[32];
+ LpMetadataTableDescriptor partitions;
+ LpMetadataTableDescriptor extents;
+ LpMetadataTableDescriptor groups;
+ LpMetadataTableDescriptor block_devices;
+} __attribute__((packed)) LpMetadataHeaderV1_0;
+
+typedef LpMetadataHeader LpMetadataHeaderV1_2;
+
#ifdef __cplusplus
} /* extern "C" */
#endif
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 22f6746..e67fb33 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -372,7 +372,7 @@
// Compute the maximum number of partitions we can fit in 512 bytes of
// metadata. By default there is the header, one partition group, and a
// block device entry.
- static const size_t kMaxPartitionTableSize = kMetadataSize - sizeof(LpMetadataHeader) -
+ static const size_t kMaxPartitionTableSize = kMetadataSize - sizeof(LpMetadataHeaderV1_0) -
sizeof(LpMetadataPartitionGroup) -
sizeof(LpMetadataBlockDevice);
size_t max_partitions = kMaxPartitionTableSize / sizeof(LpMetadataPartition);
@@ -742,3 +742,28 @@
ASSERT_GE(metadata->partitions.size(), 1);
ASSERT_NE(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0);
}
+
+TEST_F(LiblpTest, ReadExpandedHeader) {
+ unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
+ ASSERT_NE(builder, nullptr);
+ ASSERT_TRUE(AddDefaultPartitions(builder.get()));
+
+ builder->RequireExpandedMetadataHeader();
+
+ unique_fd fd = CreateFakeDisk();
+ ASSERT_GE(fd, 0);
+
+ DefaultPartitionOpener opener(fd);
+
+ // Export and flash.
+ unique_ptr<LpMetadata> exported = builder->Export();
+ ASSERT_NE(exported, nullptr);
+ exported->header.flags = 0x5e5e5e5e;
+ ASSERT_TRUE(FlashPartitionTable(opener, "super", *exported.get()));
+
+ unique_ptr<LpMetadata> imported = ReadMetadata(opener, "super", 0);
+ ASSERT_NE(imported, nullptr);
+ EXPECT_EQ(imported->header.header_size, sizeof(LpMetadataHeaderV1_2));
+ EXPECT_EQ(imported->header.header_size, exported->header.header_size);
+ EXPECT_EQ(imported->header.flags, exported->header.flags);
+}
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index aecf685..e6fd9f7 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -31,6 +31,9 @@
namespace android {
namespace fs_mgr {
+static_assert(sizeof(LpMetadataHeaderV1_0) == offsetof(LpMetadataHeader, flags),
+ "Incorrect LpMetadataHeader v0 size");
+
// Helper class for reading descriptors and memory buffers in the same manner.
class Reader {
public:
@@ -161,30 +164,59 @@
return true;
}
-static bool ValidateMetadataHeader(const LpMetadataHeader& header) {
- // To compute the header's checksum, we have to temporarily set its checksum
- // field to 0.
- {
- LpMetadataHeader temp = header;
- memset(&temp.header_checksum, 0, sizeof(temp.header_checksum));
- SHA256(&temp, sizeof(temp), temp.header_checksum);
- if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) != 0) {
- LERROR << "Logical partition metadata has invalid checksum.";
- return false;
- }
+static bool ReadMetadataHeader(Reader* reader, LpMetadata* metadata) {
+ // Note we zero the struct since older files will result in a partial read.
+ LpMetadataHeader& header = metadata->header;
+ memset(&header, 0, sizeof(header));
+
+ if (!reader->ReadFully(&header, sizeof(LpMetadataHeaderV1_0))) {
+ PERROR << __PRETTY_FUNCTION__ << " read failed";
+ return false;
}
- // Do basic validation of key metadata bits.
+ // Do basic sanity checks before computing the checksum.
if (header.magic != LP_METADATA_HEADER_MAGIC) {
LERROR << "Logical partition metadata has invalid magic value.";
return false;
}
- // Check that the version is compatible.
if (header.major_version != LP_METADATA_MAJOR_VERSION ||
header.minor_version > LP_METADATA_MINOR_VERSION_MAX) {
LERROR << "Logical partition metadata has incompatible version.";
return false;
}
+
+ // Validate the header struct size against the reported version.
+ uint32_t expected_struct_size = sizeof(header);
+ if (header.minor_version < LP_METADATA_VERSION_FOR_EXPANDED_HEADER) {
+ expected_struct_size = sizeof(LpMetadataHeaderV1_0);
+ }
+ if (header.header_size != expected_struct_size) {
+ LERROR << "Invalid partition metadata header struct size.";
+ return false;
+ }
+
+ // Read in any remaining fields, the last step needed before checksumming.
+ if (size_t remaining_bytes = header.header_size - sizeof(LpMetadataHeaderV1_0)) {
+ uint8_t* offset = reinterpret_cast<uint8_t*>(&header) + sizeof(LpMetadataHeaderV1_0);
+ if (!reader->ReadFully(offset, remaining_bytes)) {
+ PERROR << __PRETTY_FUNCTION__ << " read failed";
+ return false;
+ }
+ }
+
+ // To compute the header's checksum, we have to temporarily set its checksum
+ // field to 0. Note that we must only compute up to |header_size|.
+ {
+ LpMetadataHeader temp = header;
+ memset(&temp.header_checksum, 0, sizeof(temp.header_checksum));
+ SHA256(&temp, temp.header_size, temp.header_checksum);
+ if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) !=
+ 0) {
+ LERROR << "Logical partition metadata has invalid checksum.";
+ return false;
+ }
+ }
+
if (!ValidateTableBounds(header, header.partitions) ||
!ValidateTableBounds(header, header.extents) ||
!ValidateTableBounds(header, header.groups) ||
@@ -215,19 +247,22 @@
Reader* reader) {
// First read and validate the header.
std::unique_ptr<LpMetadata> metadata = std::make_unique<LpMetadata>();
- if (!reader->ReadFully(&metadata->header, sizeof(metadata->header))) {
- PERROR << __PRETTY_FUNCTION__ << " read " << sizeof(metadata->header) << "bytes failed";
- return nullptr;
- }
- if (!ValidateMetadataHeader(metadata->header)) {
- return nullptr;
- }
+
metadata->geometry = geometry;
+ if (!ReadMetadataHeader(reader, metadata.get())) {
+ return nullptr;
+ }
LpMetadataHeader& header = metadata->header;
- // Read the metadata payload. Allocation is fallible in case the metadata is
- // corrupt and has some huge value.
+ // Sanity check the table size.
+ if (header.tables_size > geometry.metadata_max_size) {
+ LERROR << "Invalid partition metadata header table size.";
+ return nullptr;
+ }
+
+ // Read the metadata payload. Allocation is fallible since the table size
+ // could be large.
std::unique_ptr<uint8_t[]> buffer(new (std::nothrow) uint8_t[header.tables_size]);
if (!buffer) {
LERROR << "Out of memory reading logical partition tables.";
@@ -245,11 +280,9 @@
return nullptr;
}
- uint32_t valid_attributes = 0;
+ uint32_t valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V0;
if (metadata->header.minor_version >= LP_METADATA_VERSION_FOR_UPDATED_ATTR) {
- valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V1;
- } else {
- valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V0;
+ valid_attributes |= LP_PARTITION_ATTRIBUTE_MASK_V1;
}
// ValidateTableSize ensured that |cursor| is valid for the number of
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index bb24069..8bf1ee9 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -74,10 +74,10 @@
// Compute header checksum.
memset(header.header_checksum, 0, sizeof(header.header_checksum));
- SHA256(&header, sizeof(header), header.header_checksum);
+ SHA256(&header, header.header_size, header.header_checksum);
std::string header_blob =
- std::string(reinterpret_cast<const char*>(&metadata.header), sizeof(metadata.header));
+ std::string(reinterpret_cast<const char*>(&header), header.header_size);
return header_blob + tables;
}
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 30d01a6..eadcecc 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -30,7 +30,6 @@
static_libs: [
"libcutils",
"libdm",
- "libfs_mgr",
"libfstab",
"liblp",
"update_metadata-protos",
@@ -93,8 +92,8 @@
"libsnapshot_hal_deps",
],
srcs: [":libsnapshot_sources"],
- whole_static_libs: [
- "libfiemap_binder",
+ static_libs: [
+ "libfs_mgr_binder"
],
}
@@ -103,8 +102,8 @@
defaults: ["libsnapshot_defaults"],
srcs: [":libsnapshot_sources"],
recovery_available: true,
- whole_static_libs: [
- "libfiemap_passthrough",
+ static_libs: [
+ "libfs_mgr",
],
}
@@ -116,8 +115,8 @@
],
srcs: [":libsnapshot_sources"],
recovery_available: true,
- whole_static_libs: [
- "libfiemap_passthrough",
+ static_libs: [
+ "libfs_mgr",
],
}
@@ -144,6 +143,7 @@
"libstorage_literals_headers",
],
static_libs: [
+ "libfs_mgr",
"libgtest",
"libgmock",
],
@@ -170,6 +170,7 @@
"android.hardware.boot@1.1",
"libfs_mgr",
"libgmock",
+ "libgsi",
"liblp",
"libsnapshot",
"libsnapshot_test_helpers",
@@ -189,7 +190,6 @@
static_libs: [
"libdm",
"libext2_uuid",
- "libfiemap_binder",
"libfstab",
"libsnapshot",
],
@@ -200,7 +200,7 @@
"libbinder",
"libbinderthreadstate",
"libext4_utils",
- "libfs_mgr",
+ "libfs_mgr_binder",
"libhidlbase",
"liblog",
"liblp",
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 7450d19..445e6db 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -155,6 +155,7 @@
// Mark snapshot writes as having completed. After this, new snapshots cannot
// be created, and the device must either cancel the OTA (either before
// rebooting or after rolling back), or merge the OTA.
+ // Before calling this function, all snapshots must be mapped.
bool FinishedSnapshotWrites();
private:
@@ -197,6 +198,13 @@
// - other states indicating an error has occurred
UpdateState InitiateMergeAndWait();
+ // Wait for the merge if rebooted into the new slot. Does NOT initiate a
+ // merge. If the merge has not been initiated (but should be), wait.
+ // Returns:
+ // - true there is no merge or merge finishes
+ // - false indicating an error has occurred
+ bool WaitForMerge();
+
// Find the status of the current update, if any.
//
// |progress| depends on the returned status:
@@ -490,6 +498,15 @@
// This should only be called in recovery.
bool UnmapAllPartitions();
+ // Sanity check no snapshot overflows. Note that this returns false negatives if the snapshot
+ // overflows, then is remapped and not written afterwards. Hence, the function may only serve
+ // as a sanity check.
+ bool EnsureNoOverflowSnapshot(LockedFile* lock);
+
+ enum class Slot { Unknown, Source, Target };
+ friend std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot);
+ Slot GetCurrentSlot();
+
std::string gsid_dir_;
std::string metadata_dir_;
std::unique_ptr<IDeviceInfo> device_;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 830495c..a0ec068 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -74,6 +74,7 @@
using namespace std::string_literals;
static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";
+static constexpr auto kUpdateStateCheckInterval = 2s;
// Note: IImageManager is an incomplete type in the header, so the default
// destructor doesn't work.
@@ -171,14 +172,9 @@
if (state == UpdateState::Unverified) {
// We completed an update, but it can still be canceled if we haven't booted into it.
- auto boot_file = GetSnapshotBootIndicatorPath();
- std::string contents;
- if (!android::base::ReadFileToString(boot_file, &contents)) {
- PLOG(WARNING) << "Cannot read " << boot_file << ", proceed to canceling the update:";
- return RemoveAllUpdateState(file.get());
- }
- if (device_->GetSlotSuffix() == contents) {
- LOG(INFO) << "Canceling a previously completed update";
+ auto slot = GetCurrentSlot();
+ if (slot != Slot::Target) {
+ LOG(INFO) << "Canceling previously completed updates (if any)";
return RemoveAllUpdateState(file.get());
}
}
@@ -186,6 +182,19 @@
return true;
}
+SnapshotManager::Slot SnapshotManager::GetCurrentSlot() {
+ auto boot_file = GetSnapshotBootIndicatorPath();
+ std::string contents;
+ if (!android::base::ReadFileToString(boot_file, &contents)) {
+ PLOG(WARNING) << "Cannot read " << boot_file;
+ return Slot::Unknown;
+ }
+ if (device_->GetSlotSuffix() == contents) {
+ return Slot::Source;
+ }
+ return Slot::Target;
+}
+
bool SnapshotManager::RemoveAllUpdateState(LockedFile* lock) {
if (!RemoveAllSnapshots(lock)) {
LOG(ERROR) << "Could not remove all snapshots";
@@ -214,6 +223,11 @@
return false;
}
+ if (!EnsureNoOverflowSnapshot(lock.get())) {
+ LOG(ERROR) << "Cannot ensure there are no overflow snapshots.";
+ return false;
+ }
+
// This file acts as both a quick indicator for init (it can use access(2)
// to decide how to do first-stage mounts), and it stores the old slot, so
// we can tell whether or not we performed a rollback.
@@ -500,15 +514,9 @@
return false;
}
- std::string old_slot;
- auto boot_file = GetSnapshotBootIndicatorPath();
- if (!android::base::ReadFileToString(boot_file, &old_slot)) {
- LOG(ERROR) << "Could not determine the previous slot; aborting merge";
- return false;
- }
- auto new_slot = device_->GetSlotSuffix();
- if (new_slot == old_slot) {
- LOG(ERROR) << "Device cannot merge while booting off old slot " << old_slot;
+ auto slot = GetCurrentSlot();
+ if (slot != Slot::Target) {
+ LOG(ERROR) << "Device cannot merge while not booting from new slot";
return false;
}
@@ -724,7 +732,7 @@
// This wait is not super time sensitive, so we have a relatively
// low polling frequency.
- std::this_thread::sleep_for(2s);
+ std::this_thread::sleep_for(kUpdateStateCheckInterval);
}
}
@@ -1092,13 +1100,11 @@
}
bool SnapshotManager::HandleCancelledUpdate(LockedFile* lock) {
- std::string old_slot;
- auto boot_file = GetSnapshotBootIndicatorPath();
- if (!android::base::ReadFileToString(boot_file, &old_slot)) {
- PLOG(ERROR) << "Unable to read the snapshot indicator file: " << boot_file;
+ auto slot = GetCurrentSlot();
+ if (slot == Slot::Unknown) {
return false;
}
- if (device_->GetSlotSuffix() != old_slot) {
+ if (slot == Slot::Target) {
// We're booted into the target slot, which means we just rebooted
// after applying the update.
if (!HandleCancelledUpdateOnNewSlot(lock)) {
@@ -1266,14 +1272,9 @@
// ultimately we'll fail to boot. Why not make it a fatal error and have
// the reason be clearer? Because the indicator file still exists, and
// if this was FATAL, reverting to the old slot would be broken.
- std::string old_slot;
- auto boot_file = GetSnapshotBootIndicatorPath();
- if (!android::base::ReadFileToString(boot_file, &old_slot)) {
- PLOG(ERROR) << "Unable to read the snapshot indicator file: " << boot_file;
- return false;
- }
- if (device_->GetSlotSuffix() == old_slot) {
- LOG(INFO) << "Detected slot rollback, will not mount snapshots.";
+ auto slot = GetCurrentSlot();
+ if (slot != Slot::Target) {
+ LOG(INFO) << "Not booting from new slot. Will not mount snapshots.";
return false;
}
@@ -2151,6 +2152,17 @@
return ok;
}
+std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot) {
+ switch (slot) {
+ case SnapshotManager::Slot::Unknown:
+ return os << "unknown";
+ case SnapshotManager::Slot::Source:
+ return os << "source";
+ case SnapshotManager::Slot::Target:
+ return os << "target";
+ }
+}
+
bool SnapshotManager::Dump(std::ostream& os) {
// Don't actually lock. Dump() is for debugging purposes only, so it is okay
// if it is racy.
@@ -2161,11 +2173,8 @@
ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
- auto boot_file = GetSnapshotBootIndicatorPath();
- std::string boot_indicator;
- if (android::base::ReadFileToString(boot_file, &boot_indicator)) {
- ss << "Boot indicator: old slot = " << boot_indicator << std::endl;
- }
+ ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
+ ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
bool ok = true;
std::vector<std::string> snapshots;
@@ -2233,6 +2242,21 @@
return state;
}
+bool SnapshotManager::WaitForMerge() {
+ LOG(INFO) << "Waiting for any previous merge request to complete. "
+ << "This can take up to several minutes.";
+ while (true) {
+ auto state = ProcessUpdateState();
+ if (state == UpdateState::Unverified && GetCurrentSlot() == Slot::Target) {
+ LOG(INFO) << "Wait for merge to be initiated.";
+ std::this_thread::sleep_for(kUpdateStateCheckInterval);
+ continue;
+ }
+ LOG(INFO) << "Wait for merge exits with state " << state;
+ return state == UpdateState::None || state == UpdateState::MergeCompleted;
+ }
+}
+
bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callback) {
if (!device_->IsRecovery()) {
LOG(ERROR) << "Data wipes are only allowed in recovery.";
@@ -2278,11 +2302,9 @@
//
// Since the rollback is inevitable, we don't treat a HAL failure
// as an error here.
- std::string old_slot;
- auto boot_file = GetSnapshotBootIndicatorPath();
- if (android::base::ReadFileToString(boot_file, &old_slot) &&
- device_->GetSlotSuffix() != old_slot) {
- LOG(ERROR) << "Reverting to slot " << old_slot << " since update will be deleted.";
+ auto slot = GetCurrentSlot();
+ if (slot == Slot::Target) {
+ LOG(ERROR) << "Reverting to old slot since update will be deleted.";
device_->SetSlotAsUnbootable(slot_number);
}
break;
@@ -2303,5 +2325,36 @@
return true;
}
+bool SnapshotManager::EnsureNoOverflowSnapshot(LockedFile* lock) {
+ CHECK(lock);
+
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock, &snapshots)) {
+ LOG(ERROR) << "Could not list snapshots.";
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ for (const auto& snapshot : snapshots) {
+ std::vector<DeviceMapper::TargetInfo> targets;
+ if (!dm.GetTableStatus(snapshot, &targets)) {
+ LOG(ERROR) << "Could not read snapshot device table: " << snapshot;
+ return false;
+ }
+ if (targets.size() != 1) {
+ LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << snapshot
+ << ", size = " << targets.size();
+ return false;
+ }
+ if (targets[0].IsOverflowSnapshot()) {
+ LOG(ERROR) << "Detected overflow in snapshot " << snapshot
+ << ", CoW device size computation is wrong!";
+ return false;
+ }
+ }
+
+ return true;
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index ff943f2..2da0103 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -273,6 +273,61 @@
return AssertionSuccess();
}
+ // Prepare A/B slot for a partition named "test_partition".
+ AssertionResult PrepareOneSnapshot(uint64_t device_size,
+ std::string* out_snap_device = nullptr) {
+ std::string base_device, cow_device, snap_device;
+ if (!CreatePartition("test_partition_a", device_size)) {
+ return AssertionFailure();
+ }
+ if (!MapUpdatePartitions()) {
+ return AssertionFailure();
+ }
+ if (!dm_.GetDmDevicePathByName("test_partition_b-base", &base_device)) {
+ return AssertionFailure();
+ }
+ SnapshotStatus status;
+ status.set_name("test_partition_b");
+ status.set_device_size(device_size);
+ status.set_snapshot_size(device_size);
+ status.set_cow_file_size(device_size);
+ if (!sm->CreateSnapshot(lock_.get(), &status)) {
+ return AssertionFailure();
+ }
+ if (!CreateCowImage("test_partition_b")) {
+ return AssertionFailure();
+ }
+ if (!MapCowImage("test_partition_b", 10s, &cow_device)) {
+ return AssertionFailure();
+ }
+ if (!sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
+ &snap_device)) {
+ return AssertionFailure();
+ }
+ if (out_snap_device) {
+ *out_snap_device = std::move(snap_device);
+ }
+ return AssertionSuccess();
+ }
+
+ // Simulate a reboot into the new slot.
+ AssertionResult SimulateReboot() {
+ lock_ = nullptr;
+ if (!sm->FinishedSnapshotWrites()) {
+ return AssertionFailure();
+ }
+ if (!dm_.DeleteDevice("test_partition_b")) {
+ return AssertionFailure();
+ }
+ if (!DestroyLogicalPartition("test_partition_b-base")) {
+ return AssertionFailure();
+ }
+ if (!sm->UnmapCowImage("test_partition_b")) {
+ return AssertionFailure();
+ }
+ return AssertionSuccess();
+ }
+
DeviceMapper& dm_;
std::unique_ptr<SnapshotManager::LockedFile> lock_;
android::fiemap::IImageManager* image_manager_ = nullptr;
@@ -389,21 +444,8 @@
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
-
- std::string base_device, cow_device, snap_device;
- ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
- ASSERT_TRUE(MapUpdatePartitions());
- ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b-base", &base_device));
- SnapshotStatus status;
- status.set_name("test_partition_b");
- status.set_device_size(kDeviceSize);
- status.set_snapshot_size(kDeviceSize);
- status.set_cow_file_size(kDeviceSize);
- ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
- ASSERT_TRUE(CreateCowImage("test_partition_b"));
- ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device));
- ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
- &snap_device));
+ std::string snap_device;
+ ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &snap_device));
std::string test_string = "This is a test string.";
{
@@ -455,21 +497,8 @@
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
-
- ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
- ASSERT_TRUE(MapUpdatePartitions());
- SnapshotStatus status;
- status.set_name("test_partition_b");
- status.set_device_size(kDeviceSize);
- status.set_snapshot_size(kDeviceSize);
- status.set_cow_file_size(kDeviceSize);
- ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
- ASSERT_TRUE(CreateCowImage("test_partition_b"));
-
- // Simulate a reboot into the new slot.
- lock_ = nullptr;
- ASSERT_TRUE(sm->FinishedSnapshotWrites());
- ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+ ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+ ASSERT_TRUE(SimulateReboot());
auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
ASSERT_NE(init, nullptr);
@@ -479,6 +508,7 @@
ASSERT_TRUE(AcquireLock());
// Validate that we have a snapshot device.
+ SnapshotStatus status;
ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
ASSERT_EQ(status.state(), SnapshotState::CREATED);
@@ -492,21 +522,8 @@
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
-
- ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
- ASSERT_TRUE(MapUpdatePartitions());
- SnapshotStatus status;
- status.set_name("test_partition_b");
- status.set_device_size(kDeviceSize);
- status.set_snapshot_size(kDeviceSize);
- status.set_cow_file_size(kDeviceSize);
- ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
- ASSERT_TRUE(CreateCowImage("test_partition_b"));
-
- // Simulate a reboot into the new slot.
- lock_ = nullptr;
- ASSERT_TRUE(sm->FinishedSnapshotWrites());
- ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+ ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+ ASSERT_TRUE(SimulateReboot());
// Reflash the super partition.
FormatFakeSuper();
@@ -519,6 +536,7 @@
ASSERT_TRUE(AcquireLock());
+ SnapshotStatus status;
ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
// We should not get a snapshot device now.
@@ -535,21 +553,8 @@
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
-
- ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
- ASSERT_TRUE(MapUpdatePartitions());
- SnapshotStatus status;
- status.set_name("test_partition_b");
- status.set_device_size(kDeviceSize);
- status.set_snapshot_size(kDeviceSize);
- status.set_cow_file_size(kDeviceSize);
- ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
- ASSERT_TRUE(CreateCowImage("test_partition_b"));
-
- // Simulate a reboot into the new slot.
- lock_ = nullptr;
- ASSERT_TRUE(sm->FinishedSnapshotWrites());
- ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base"));
+ ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+ ASSERT_TRUE(SimulateReboot());
auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
ASSERT_NE(init, nullptr);
@@ -601,17 +606,17 @@
std::ostream& operator<<(std::ostream& os, Request request) {
switch (request) {
case Request::LOCK_SHARED:
- return os << "LOCK_SHARED";
+ return os << "Shared";
case Request::LOCK_EXCLUSIVE:
- return os << "LOCK_EXCLUSIVE";
+ return os << "Exclusive";
case Request::UNLOCK:
- return os << "UNLOCK";
+ return os << "Unlock";
case Request::EXIT:
- return os << "EXIT";
+ return os << "Exit";
case Request::UNKNOWN:
[[fallthrough]];
default:
- return os << "UNKNOWN";
+ return os << "Unknown";
}
}
@@ -741,7 +746,7 @@
LockTestParam{Request::LOCK_SHARED, Request::LOCK_EXCLUSIVE}),
[](const testing::TestParamInfo<LockTestP::ParamType>& info) {
std::stringstream ss;
- ss << info.param.first << "_" << info.param.second;
+ ss << info.param.first << info.param.second;
return ss.str();
});
@@ -905,6 +910,36 @@
<< ", hash: " << hashes_[name];
}
+ AssertionResult MapUpdateSnapshots(const std::vector<std::string>& names = {"sys_b", "vnd_b",
+ "prd_b"}) {
+ for (const auto& name : names) {
+ auto res = MapUpdateSnapshot(name);
+ if (!res) {
+ return res;
+ }
+ }
+ return AssertionSuccess();
+ }
+
+ // Create fake install operations to grow the COW device size.
+ void AddOperation(PartitionUpdate* partition_update, uint64_t size_bytes = 0) {
+ auto e = partition_update->add_operations()->add_dst_extents();
+ e->set_start_block(0);
+ if (size_bytes == 0) {
+ size_bytes = GetSize(partition_update);
+ }
+ e->set_num_blocks(size_bytes / manifest_.block_size());
+ }
+
+ void AddOperationForPartitions(std::vector<PartitionUpdate*> partitions = {}) {
+ if (partitions.empty()) {
+ partitions = {sys_, vnd_, prd_};
+ }
+ for (auto* partition : partitions) {
+ AddOperation(partition);
+ }
+ }
+
std::unique_ptr<TestPartitionOpener> opener_;
DeltaArchiveManifest manifest_;
std::unique_ptr<MetadataBuilder> src_;
@@ -932,12 +967,7 @@
SetSize(vnd_, partition_size);
SetSize(prd_, partition_size);
- // Create fake install operations to grow the COW device size.
- for (auto& partition : {sys_, vnd_, prd_}) {
- auto e = partition->add_operations()->add_dst_extents();
- e->set_start_block(0);
- e->set_num_blocks(GetSize(partition) / manifest_.block_size());
- }
+ AddOperationForPartitions();
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
@@ -1064,9 +1094,7 @@
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
// Check that target partitions can be mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- EXPECT_TRUE(MapUpdateSnapshot(name));
- }
+ EXPECT_TRUE(MapUpdateSnapshots());
}
// Test that the old partitions are not modified.
@@ -1075,12 +1103,7 @@
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
- // Create fake install operations to grow the COW device size.
- for (auto& partition : {sys_, vnd_, prd_}) {
- auto e = partition->add_operations()->add_dst_extents();
- e->set_start_block(0);
- e->set_num_blocks(GetSize(partition) / manifest_.block_size());
- }
+ AddOperationForPartitions();
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
@@ -1142,6 +1165,7 @@
// Execute the first update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(MapUpdateSnapshots());
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
@@ -1224,10 +1248,8 @@
group_->set_size(kRetrofitGroupSize);
for (auto* partition : {sys_, vnd_, prd_}) {
SetSize(partition, 2_MiB);
- auto* e = partition->add_operations()->add_dst_extents();
- e->set_start_block(0);
- e->set_num_blocks(2_MiB / manifest_.block_size());
}
+ AddOperationForPartitions();
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
@@ -1270,13 +1292,12 @@
}
// Add operations for sys. The whole device is written.
- auto e = sys_->add_operations()->add_dst_extents();
- e->set_start_block(0);
- e->set_num_blocks(GetSize(sys_) / manifest_.block_size());
+ AddOperation(sys_);
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(MapUpdateSnapshots());
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
@@ -1379,6 +1400,7 @@
// Execute the first update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(MapUpdateSnapshots());
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
@@ -1410,6 +1432,7 @@
// Execute the first update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(MapUpdateSnapshots());
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
@@ -1434,6 +1457,7 @@
// Execute the first update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(MapUpdateSnapshots());
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
@@ -1458,10 +1482,7 @@
const auto block_size = manifest_.block_size();
SetSize(sys_, partition_size);
-
- auto e = sys_->add_operations()->add_dst_extents();
- e->set_start_block(0);
- e->set_num_blocks(data_size / block_size);
+ AddOperation(sys_, data_size);
// Set hastree extents.
sys_->mutable_hash_tree_data_extent()->set_start_block(0);
@@ -1480,7 +1501,8 @@
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- // Write some data to target partition.
+ // Map and write some data to target partition.
+ ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
ASSERT_TRUE(WriteSnapshotAndHash("sys_b", partition_size));
// Finish update.
@@ -1500,6 +1522,69 @@
ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
}
+// Test for overflow bit after update
+TEST_F(SnapshotUpdateTest, Overflow) {
+ const auto actual_write_size = GetSize(sys_);
+ const auto declared_write_size = actual_write_size - 1_MiB;
+
+ AddOperation(sys_, declared_write_size);
+
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+ // Map and write some data to target partitions.
+ ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
+ ASSERT_TRUE(WriteSnapshotAndHash("sys_b", actual_write_size));
+
+ std::vector<android::dm::DeviceMapper::TargetInfo> table;
+ ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table));
+ ASSERT_EQ(1u, table.size());
+ EXPECT_TRUE(table[0].IsOverflowSnapshot());
+
+ ASSERT_FALSE(sm->FinishedSnapshotWrites())
+ << "FinishedSnapshotWrites should detect overflow of CoW device.";
+}
+
+TEST_F(SnapshotUpdateTest, WaitForMerge) {
+ AddOperationForPartitions();
+
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+ // Write some data to target partitions.
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(WriteSnapshotAndHash(name));
+ }
+
+ ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+ // Simulate shutting down the device.
+ ASSERT_TRUE(UnmapAll());
+
+ // After reboot, init does first stage mount.
+ {
+ auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+ ASSERT_NE(nullptr, init);
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+ }
+
+ auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+ ASSERT_NE(nullptr, new_sm);
+
+ auto waiter = std::async(std::launch::async, [&new_sm] { return new_sm->WaitForMerge(); });
+ ASSERT_EQ(std::future_status::timeout, waiter.wait_for(1s))
+ << "WaitForMerge should block when not initiated";
+
+ auto merger =
+ std::async(std::launch::async, [&new_sm] { return new_sm->InitiateMergeAndWait(); });
+ // Small images, so should be merged pretty quickly.
+ ASSERT_EQ(std::future_status::ready, waiter.wait_for(3s)) << "WaitForMerge did not finish";
+ ASSERT_TRUE(waiter.get());
+ ASSERT_THAT(merger.get(), AnyOf(UpdateState::None, UpdateState::MergeCompleted));
+}
+
class FlashAfterUpdateTest : public SnapshotUpdateTest,
public WithParamInterface<std::tuple<uint32_t, bool>> {
public:
@@ -1524,7 +1609,7 @@
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
+ ASSERT_TRUE(MapUpdateSnapshots());
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
diff --git a/init/Android.bp b/init/Android.bp
index 9529617..42d0b33 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -71,6 +71,7 @@
"libpropertyinfoserializer",
"libpropertyinfoparser",
"libsnapshot_init",
+ "lib_apex_manifest_proto_lite",
],
shared_libs: [
"libbacktrace",
diff --git a/init/Android.mk b/init/Android.mk
index ee2d89a..07b0f95 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -52,7 +52,6 @@
first_stage_init.cpp \
first_stage_main.cpp \
first_stage_mount.cpp \
- mount_namespace.cpp \
reboot_utils.cpp \
selabel.cpp \
selinux.cpp \
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 62a19ab..64d9c59 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -59,6 +59,7 @@
#include <fs_mgr.h>
#include <fscrypt/fscrypt.h>
#include <libgsi/libgsi.h>
+#include <logwrap/logwrap.h>
#include <selinux/android.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
@@ -1176,6 +1177,37 @@
return {};
}
+static Result<void> GenerateLinkerConfiguration() {
+ const char* linkerconfig_binary = "/system/bin/linkerconfig";
+ const char* linkerconfig_target = "/linkerconfig";
+ const char* arguments[] = {linkerconfig_binary, "--target", linkerconfig_target};
+
+ if (logwrap_fork_execvp(arraysize(arguments), arguments, nullptr, false, LOG_KLOG, false,
+ nullptr) != 0) {
+ return ErrnoError() << "failed to execute linkerconfig";
+ }
+
+ LOG(INFO) << "linkerconfig generated " << linkerconfig_target
+ << " with mounted APEX modules info";
+
+ return {};
+}
+
+static bool IsApexUpdatable() {
+ static bool updatable = android::sysprop::ApexProperties::updatable().value_or(false);
+ return updatable;
+}
+
+static Result<void> do_update_linker_config(const BuiltinArguments&) {
+ // If APEX is not updatable, then all APEX information are already included in the first
+ // linker config generation, so there is no need to update linker configuration again.
+ if (IsApexUpdatable()) {
+ return GenerateLinkerConfiguration();
+ }
+
+ return {};
+}
+
static Result<void> parse_apex_configs() {
glob_t glob_result;
static constexpr char glob_pattern[] = "/apex/*/etc/*.rc";
@@ -1251,6 +1283,12 @@
if (!parse_configs) {
return parse_configs.error();
}
+
+ auto update_linker_config = do_update_linker_config(args);
+ if (!update_linker_config) {
+ return update_linker_config.error();
+ }
+
return {};
}
@@ -1317,6 +1355,7 @@
{"perform_apex_config", {0, 0, {false, do_perform_apex_config}}},
{"umount", {1, 1, {false, do_umount}}},
{"umount_all", {1, 1, {false, do_umount_all}}},
+ {"update_linker_config", {0, 0, {false, do_update_linker_config}}},
{"readahead", {1, 2, {true, do_readahead}}},
{"remount_userdata", {0, 0, {false, do_remount_userdata}}},
{"restart", {1, 1, {false, do_restart}}},
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 9121bac..e5e99e1 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -34,6 +34,7 @@
#include <fs_mgr.h>
#include <fs_mgr_dm_linear.h>
#include <fs_mgr_overlayfs.h>
+#include <libfiemap/image_manager.h>
#include <libgsi/libgsi.h>
#include <liblp/liblp.h>
#include <libsnapshot/snapshot.h>
@@ -46,6 +47,7 @@
using android::base::Split;
using android::base::Timer;
+using android::fiemap::IImageManager;
using android::fs_mgr::AvbHandle;
using android::fs_mgr::AvbHandleStatus;
using android::fs_mgr::AvbHashtreeResult;
@@ -93,7 +95,7 @@
bool IsDmLinearEnabled();
void GetDmLinearMetadataDevice(std::set<std::string>* devices);
bool InitDmLinearBackingDevices(const android::fs_mgr::LpMetadata& metadata);
- void UseGsiIfPresent();
+ void UseDsuIfPresent();
ListenerAction UeventCallback(const Uevent& uevent, std::set<std::string>* required_devices);
@@ -102,7 +104,7 @@
virtual bool SetUpDmVerity(FstabEntry* fstab_entry) = 0;
bool need_dm_verity_;
- bool gsi_not_on_userdata_ = false;
+ bool dsu_not_on_userdata_ = false;
Fstab fstab_;
std::string lp_metadata_partition_;
@@ -511,7 +513,7 @@
// this case, we mount system first then pivot to it. From that point on,
// we are effectively identical to a system-as-root device.
bool FirstStageMount::TrySwitchSystemAsRoot() {
- UseGsiIfPresent();
+ UseDsuIfPresent();
auto system_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
return entry.mount_point == "/system";
@@ -520,7 +522,7 @@
if (system_partition == fstab_.end()) return true;
if (MountPartition(system_partition, false /* erase_same_mounts */)) {
- if (gsi_not_on_userdata_ && fs_mgr_verity_is_check_at_most_once(*system_partition)) {
+ if (dsu_not_on_userdata_ && fs_mgr_verity_is_check_at_most_once(*system_partition)) {
LOG(ERROR) << "check_most_at_once forbidden on external media";
return false;
}
@@ -596,49 +598,40 @@
return true;
}
-void FirstStageMount::UseGsiIfPresent() {
+void FirstStageMount::UseDsuIfPresent() {
std::string error;
if (!android::gsi::CanBootIntoGsi(&error)) {
- LOG(INFO) << "GSI " << error << ", proceeding with normal boot";
+ LOG(INFO) << "DSU " << error << ", proceeding with normal boot";
return;
}
- auto metadata = android::fs_mgr::ReadFromImageFile(gsi::kDsuLpMetadataFile);
- if (!metadata) {
- LOG(ERROR) << "GSI partition layout could not be read";
- return;
- }
-
- if (!InitDmLinearBackingDevices(*metadata.get())) {
- return;
- }
-
- // Find the super name. PartitionOpener will ensure this translates to the
- // correct block device path.
- auto super = GetMetadataSuperBlockDevice(*metadata.get());
- auto super_name = android::fs_mgr::GetBlockDevicePartitionName(*super);
- if (!android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_name)) {
- LOG(ERROR) << "GSI partition layout could not be instantiated";
+ auto init_devices = [this](std::set<std::string> devices) -> bool {
+ if (devices.count("userdata") == 0 || devices.size() > 1) {
+ dsu_not_on_userdata_ = true;
+ }
+ return InitRequiredDevices(std::move(devices));
+ };
+ auto images = IImageManager::Open("dsu", 0ms);
+ if (!images || !images->MapAllImages(init_devices)) {
+ LOG(ERROR) << "DSU partition layout could not be instantiated";
return;
}
if (!android::gsi::MarkSystemAsGsi()) {
- PLOG(ERROR) << "GSI indicator file could not be written";
+ PLOG(ERROR) << "DSU indicator file could not be written";
return;
}
std::string lp_names = "";
std::vector<std::string> dsu_partitions;
- for (auto&& partition : metadata->partitions) {
- auto name = fs_mgr::GetPartitionName(partition);
+ for (auto&& name : images->GetAllBackingImages()) {
dsu_partitions.push_back(name);
lp_names += name + ",";
}
// Publish the logical partition names for TransformFstabForDsu
WriteFile(gsi::kGsiLpNamesFile, lp_names);
TransformFstabForDsu(&fstab_, dsu_partitions);
- gsi_not_on_userdata_ = (super_name != "userdata");
}
bool FirstStageMountVBootV1::GetDmVerityDevices(std::set<std::string>* devices) {
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index c33e0de..93eb244 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -27,6 +27,7 @@
#include <android-base/properties.h>
#include <android-base/result.h>
#include <android-base/unique_fd.h>
+#include <apex_manifest.pb.h>
#include "util.h"
@@ -90,6 +91,19 @@
return {};
}
+static Result<std::string> GetApexName(const std::string& apex_dir) {
+ const std::string manifest_path = apex_dir + "/apex_manifest.pb";
+ std::string content;
+ if (!android::base::ReadFileToString(manifest_path, &content)) {
+ return Error() << "Failed to read manifest file: " << manifest_path;
+ }
+ apex::proto::ApexManifest manifest;
+ if (!manifest.ParseFromString(content)) {
+ return Error() << "Can't parse manifest file: " << manifest_path;
+ }
+ return manifest.name();
+}
+
static Result<void> ActivateFlattenedApexesFrom(const std::string& from_dir,
const std::string& to_dir) {
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(from_dir.c_str()), closedir);
@@ -101,7 +115,12 @@
if (entry->d_name[0] == '.') continue;
if (entry->d_type == DT_DIR) {
const std::string apex_path = from_dir + "/" + entry->d_name;
- const std::string mount_path = to_dir + "/" + entry->d_name;
+ const auto apex_name = GetApexName(apex_path);
+ if (!apex_name) {
+ LOG(ERROR) << apex_path << " is not an APEX directory: " << apex_name.error();
+ continue;
+ }
+ const std::string mount_path = to_dir + "/" + (*apex_name);
if (auto result = MountDir(apex_path, mount_path); !result) {
return result;
}
@@ -129,26 +148,21 @@
return false;
}
}
- // Special casing for the ART APEX
- constexpr const char kArtApexMountPath[] = "/apex/com.android.art";
- static const std::vector<std::string> kArtApexDirNames = {"com.android.art.release",
- "com.android.art.debug"};
- bool success = false;
- for (const auto& name : kArtApexDirNames) {
- std::string path = kApexTop + "/" + name;
- if (access(path.c_str(), F_OK) == 0) {
- if (auto result = MountDir(path, kArtApexMountPath); !result) {
- LOG(ERROR) << result.error();
- return false;
- }
- success = true;
- break;
- }
+ return true;
+}
+
+static Result<void> MountLinkerConfigForDefaultNamespace() {
+ // No need to mount linkerconfig for default mount namespace if the path does not exist (which
+ // would mean it is already mounted)
+ if (access("/linkerconfig/default", 0) != 0) {
+ return {};
}
- if (!success) {
- PLOG(ERROR) << "Failed to bind mount the ART APEX to " << kArtApexMountPath;
+
+ if (mount("/linkerconfig/default", "/linkerconfig", nullptr, MS_BIND | MS_REC, nullptr) != 0) {
+ return ErrnoError() << "Failed to mount linker configuration for default mount namespace.";
}
- return success;
+
+ return {};
}
static android::base::unique_fd bootstrap_ns_fd;
@@ -222,6 +236,11 @@
PLOG(ERROR) << "Failed to switch back to the default mount namespace.";
return false;
}
+
+ if (auto result = MountLinkerConfigForDefaultNamespace(); !result) {
+ LOG(ERROR) << result.error();
+ return false;
+ }
}
LOG(INFO) << "Switched to default mount namespace";
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 0e61234..225bc9c 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -834,6 +834,10 @@
}
static void HandleUserspaceReboot() {
+ if (!android::sysprop::InitProperties::is_userspace_reboot_supported().value_or(false)) {
+ LOG(ERROR) << "Attempted a userspace reboot on a device that doesn't support it";
+ return;
+ }
// Spinnig up a separate thread will fail the setns call later in the boot sequence.
// Fork a new process to monitor userspace reboot while we are investigating a better solution.
pid_t pid = fork();
diff --git a/init/service.cpp b/init/service.cpp
index a97935e..0e27ff1 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -325,8 +325,8 @@
LOG(ERROR) << "updatable process '" << name_ << "' exited 4 times "
<< (boot_completed ? "in 4 minutes" : "before boot completed");
// Notifies update_verifier and apexd
- SetProperty("sys.init.updatable_crashing", "1");
SetProperty("sys.init.updatable_crashing_process_name", name_);
+ SetProperty("sys.init.updatable_crashing", "1");
}
}
} else {
diff --git a/init/sysprop/InitProperties.sysprop b/init/sysprop/InitProperties.sysprop
index d6a1ab6..c856358 100644
--- a/init/sysprop/InitProperties.sysprop
+++ b/init/sysprop/InitProperties.sysprop
@@ -25,3 +25,12 @@
integer_as_bool: true
}
+# Shows whenever the device supports userspace reboot or not.
+prop {
+ api_name: "is_userspace_reboot_supported"
+ type: Boolean
+ scope: System
+ access: Readonly
+ prop_name: "ro.init.userspace_reboot.is_supported"
+ integer_as_bool: true
+}
diff --git a/init/sysprop/api/com.android.sysprop.init-current.txt b/init/sysprop/api/com.android.sysprop.init-current.txt
index 8da50e0..b8bcef9 100644
--- a/init/sysprop/api/com.android.sysprop.init-current.txt
+++ b/init/sysprop/api/com.android.sysprop.init-current.txt
@@ -1,6 +1,11 @@
props {
module: "android.sysprop.InitProperties"
prop {
+ api_name: "is_userspace_reboot_supported"
+ prop_name: "ro.init.userspace_reboot.is_supported"
+ integer_as_bool: true
+ }
+ prop {
api_name: "userspace_reboot_in_progress"
access: ReadWrite
prop_name: "sys.init.userspace_reboot.in_progress"
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 2b39ca6..dc31b28 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -86,6 +86,7 @@
{ 00751, AID_ROOT, AID_SHELL, 0, "system/xbin" },
{ 00751, AID_ROOT, AID_SHELL, 0, "system/apex/*/bin" },
{ 00751, AID_ROOT, AID_SHELL, 0, "system_ext/bin" },
+ { 00751, AID_ROOT, AID_SHELL, 0, "system_ext/apex/*/bin" },
{ 00751, AID_ROOT, AID_SHELL, 0, "vendor/bin" },
{ 00755, AID_ROOT, AID_SHELL, 0, "vendor" },
{ 00755, AID_ROOT, AID_ROOT, 0, 0 },
@@ -209,6 +210,7 @@
{ 00755, AID_ROOT, AID_SHELL, 0, "system/xbin/*" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system/apex/*/bin/*" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system_ext/bin/*" },
+ { 00755, AID_ROOT, AID_SHELL, 0, "system_ext/apex/*/bin/*" },
{ 00755, AID_ROOT, AID_SHELL, 0, "vendor/bin/*" },
{ 00755, AID_ROOT, AID_SHELL, 0, "vendor/xbin/*" },
{ 00644, AID_ROOT, AID_ROOT, 0, 0 },
diff --git a/liblog/Android.bp b/liblog/Android.bp
index 656d4dd..bab57c0 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -17,7 +17,6 @@
liblog_sources = [
"log_event_list.cpp",
"log_event_write.cpp",
- "logger_lock.cpp",
"logger_name.cpp",
"logger_read.cpp",
"logger_write.cpp",
@@ -96,7 +95,10 @@
},
},
- header_libs: ["liblog_headers"],
+ header_libs: [
+ "libbase_headers",
+ "liblog_headers",
+ ],
export_header_lib_headers: ["liblog_headers"],
stubs: {
diff --git a/liblog/fake_log_device.cpp b/liblog/fake_log_device.cpp
index 4143fa6..2ec6393 100644
--- a/liblog/fake_log_device.cpp
+++ b/liblog/fake_log_device.cpp
@@ -31,6 +31,7 @@
#include <mutex>
+#include <android-base/no_destructor.h>
#include <android/log.h>
#include <log/log_id.h>
#include <log/logprint.h>
@@ -48,21 +49,16 @@
#define TRACE(...) ((void)0)
#endif
-static int FakeAvailable(log_id_t);
-static int FakeOpen();
static void FakeClose();
static int FakeWrite(log_id_t log_id, struct timespec* ts, struct iovec* vec, size_t nr);
struct android_log_transport_write fakeLoggerWrite = {
- .name = "fake",
- .logMask = 0,
- .available = FakeAvailable,
- .open = FakeOpen,
.close = FakeClose,
.write = FakeWrite,
};
typedef struct LogState {
+ bool initialized = false;
/* global minimum priority */
int global_min_priority;
@@ -76,19 +72,8 @@
} tagSet[kTagSetSize];
} LogState;
-/*
- * Locking. Since we're emulating a device, we need to be prepared
- * to have multiple callers at the same time. This lock is used
- * to both protect the fd list and to prevent LogStates from being
- * freed out from under a user.
- */
-std::mutex mutex;
-
static LogState log_state;
-
-static int FakeAvailable(log_id_t) {
- return 0;
-}
+static android::base::NoDestructor<std::mutex> fake_log_mutex;
/*
* Configure logging based on ANDROID_LOG_TAGS environment variable. We
@@ -103,8 +88,8 @@
* We also want to check ANDROID_PRINTF_LOG to determine how the output
* will look.
*/
-int FakeOpen() {
- std::lock_guard guard{mutex};
+void InitializeLogStateLocked() {
+ log_state.initialized = true;
/* global min priority defaults to "info" level */
log_state.global_min_priority = ANDROID_LOG_INFO;
@@ -129,7 +114,7 @@
}
if (i == kMaxTagLen) {
TRACE("ERROR: env tag too long (%d chars max)\n", kMaxTagLen - 1);
- return 0;
+ return;
}
tagName[i] = '\0';
@@ -180,7 +165,7 @@
if (*tags != '\0' && !isspace(*tags)) {
TRACE("ERROR: garbage in tag env; expected whitespace\n");
TRACE(" env='%s'\n", tags);
- return 0;
+ return;
}
}
@@ -224,7 +209,6 @@
}
log_state.output_format = format;
- return 0;
}
/*
@@ -474,7 +458,11 @@
* Also guarantees that only one thread is in showLog() at a given
* time (if it matters).
*/
- std::lock_guard guard{mutex};
+ auto lock = std::lock_guard{*fake_log_mutex};
+
+ if (!log_state.initialized) {
+ InitializeLogStateLocked();
+ }
if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS || log_id == LOG_ID_SECURITY) {
TRACE("%s: ignoring binary log\n", android_log_id_to_name(log_id));
@@ -532,7 +520,7 @@
* help debug HOST tools ...
*/
static void FakeClose() {
- std::lock_guard guard{mutex};
+ auto lock = std::lock_guard{*fake_log_mutex};
memset(&log_state, 0, sizeof(log_state));
}
diff --git a/liblog/logd_writer.cpp b/liblog/logd_writer.cpp
index a22c3be..3c6eb69 100644
--- a/liblog/logd_writer.cpp
+++ b/liblog/logd_writer.cpp
@@ -30,97 +30,76 @@
#include <time.h>
#include <unistd.h>
+#include <shared_mutex>
+
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
#include "log_portability.h"
#include "logger.h"
+#include "rwlock.h"
#include "uio.h"
-static int logdAvailable(log_id_t LogId);
-static int logdOpen();
-static void logdClose();
-static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+static int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+static void LogdClose();
struct android_log_transport_write logdLoggerWrite = {
- .name = "logd",
- .logMask = 0,
- .context.sock = -EBADF,
- .available = logdAvailable,
- .open = logdOpen,
- .close = logdClose,
- .write = logdWrite,
+ .close = LogdClose,
+ .write = LogdWrite,
};
-/* log_init_lock assumed */
-static int logdOpen() {
- int i, ret = 0;
+static int logd_socket;
+static RwLock logd_socket_lock;
- i = atomic_load(&logdLoggerWrite.context.sock);
- if (i < 0) {
- int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
- if (sock < 0) {
- ret = -errno;
- } else {
- struct sockaddr_un un;
- memset(&un, 0, sizeof(struct sockaddr_un));
- un.sun_family = AF_UNIX;
- strcpy(un.sun_path, "/dev/socket/logdw");
-
- if (TEMP_FAILURE_RETRY(connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) <
- 0) {
- ret = -errno;
- switch (ret) {
- case -ENOTCONN:
- case -ECONNREFUSED:
- case -ENOENT:
- i = atomic_exchange(&logdLoggerWrite.context.sock, ret);
- [[fallthrough]];
- default:
- break;
- }
- close(sock);
- } else {
- ret = atomic_exchange(&logdLoggerWrite.context.sock, sock);
- if ((ret >= 0) && (ret != sock)) {
- close(ret);
- }
- ret = 0;
- }
- }
+static void OpenSocketLocked() {
+ logd_socket = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
+ if (logd_socket <= 0) {
+ return;
}
- return ret;
-}
+ sockaddr_un un = {};
+ un.sun_family = AF_UNIX;
+ strcpy(un.sun_path, "/dev/socket/logdw");
-static void __logdClose(int negative_errno) {
- int sock = atomic_exchange(&logdLoggerWrite.context.sock, negative_errno);
- if (sock >= 0) {
- close(sock);
+ if (TEMP_FAILURE_RETRY(
+ connect(logd_socket, reinterpret_cast<sockaddr*>(&un), sizeof(sockaddr_un))) < 0) {
+ close(logd_socket);
+ logd_socket = 0;
}
}
-static void logdClose() {
- __logdClose(-EBADF);
+static void OpenSocket() {
+ auto lock = std::unique_lock{logd_socket_lock};
+ if (logd_socket > 0) {
+ // Someone raced us and opened the socket already.
+ return;
+ }
+
+ OpenSocketLocked();
}
-static int logdAvailable(log_id_t logId) {
- if (logId >= LOG_ID_MAX || logId == LOG_ID_KERNEL) {
- return -EINVAL;
+static void ResetSocket(int old_socket) {
+ auto lock = std::unique_lock{logd_socket_lock};
+ if (old_socket != logd_socket) {
+ // Someone raced us and reset the socket already.
+ return;
}
- if (atomic_load(&logdLoggerWrite.context.sock) < 0) {
- if (access("/dev/socket/logdw", W_OK) == 0) {
- return 0;
- }
- return -EBADF;
- }
- return 1;
+ close(logd_socket);
+ logd_socket = 0;
+ OpenSocketLocked();
}
-static int logdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
+static void LogdClose() {
+ auto lock = std::unique_lock{logd_socket_lock};
+ if (logd_socket > 0) {
+ close(logd_socket);
+ }
+ logd_socket = 0;
+}
+
+static int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
ssize_t ret;
- int sock;
static const unsigned headerLength = 1;
struct iovec newVec[nr + headerLength];
android_log_header_t header;
@@ -128,15 +107,16 @@
static atomic_int dropped;
static atomic_int droppedSecurity;
- sock = atomic_load(&logdLoggerWrite.context.sock);
- if (sock < 0) switch (sock) {
- case -ENOTCONN:
- case -ECONNREFUSED:
- case -ENOENT:
- break;
- default:
- return -EBADF;
- }
+ auto lock = std::shared_lock{logd_socket_lock};
+ if (logd_socket <= 0) {
+ lock.unlock();
+ OpenSocket();
+ lock.lock();
+ }
+
+ if (logd_socket <= 0) {
+ return -EBADF;
+ }
/* logd, after initialization and priv drop */
if (__android_log_uid() == AID_LOGD) {
@@ -155,41 +135,39 @@
newVec[0].iov_base = (unsigned char*)&header;
newVec[0].iov_len = sizeof(header);
- if (sock >= 0) {
- int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0, memory_order_relaxed);
- if (snapshot) {
- android_log_event_int_t buffer;
+ int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0, memory_order_relaxed);
+ if (snapshot) {
+ android_log_event_int_t buffer;
- header.id = LOG_ID_SECURITY;
- buffer.header.tag = LIBLOG_LOG_TAG;
- buffer.payload.type = EVENT_TYPE_INT;
- buffer.payload.data = snapshot;
+ header.id = LOG_ID_SECURITY;
+ buffer.header.tag = LIBLOG_LOG_TAG;
+ buffer.payload.type = EVENT_TYPE_INT;
+ buffer.payload.data = snapshot;
- newVec[headerLength].iov_base = &buffer;
- newVec[headerLength].iov_len = sizeof(buffer);
+ newVec[headerLength].iov_base = &buffer;
+ newVec[headerLength].iov_len = sizeof(buffer);
- ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
- if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
- atomic_fetch_add_explicit(&droppedSecurity, snapshot, memory_order_relaxed);
- }
+ ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, 2));
+ if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
+ atomic_fetch_add_explicit(&droppedSecurity, snapshot, memory_order_relaxed);
}
- snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
- if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO, "liblog", strlen("liblog"),
- ANDROID_LOG_VERBOSE)) {
- android_log_event_int_t buffer;
+ }
+ snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
+ if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO, "liblog", strlen("liblog"),
+ ANDROID_LOG_VERBOSE)) {
+ android_log_event_int_t buffer;
- header.id = LOG_ID_EVENTS;
- buffer.header.tag = LIBLOG_LOG_TAG;
- buffer.payload.type = EVENT_TYPE_INT;
- buffer.payload.data = snapshot;
+ header.id = LOG_ID_EVENTS;
+ buffer.header.tag = LIBLOG_LOG_TAG;
+ buffer.payload.type = EVENT_TYPE_INT;
+ buffer.payload.data = snapshot;
- newVec[headerLength].iov_base = &buffer;
- newVec[headerLength].iov_len = sizeof(buffer);
+ newVec[headerLength].iov_base = &buffer;
+ newVec[headerLength].iov_len = sizeof(buffer);
- ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
- if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
- atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
- }
+ ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, 2));
+ if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
+ atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
}
}
@@ -208,49 +186,26 @@
}
}
- /*
- * The write below could be lost, but will never block.
- *
- * ENOTCONN occurs if logd has died.
- * ENOENT occurs if logd is not running and socket is missing.
- * ECONNREFUSED occurs if we can not reconnect to logd.
- * EAGAIN occurs if logd is overloaded.
- */
- if (sock < 0) {
- ret = sock;
- } else {
- ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i));
- if (ret < 0) {
- ret = -errno;
- }
+ // The write below could be lost, but will never block.
+ // EAGAIN occurs if logd is overloaded, other errors indicate that something went wrong with
+ // the connection, so we reset it and try again.
+ ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
+ if (ret < 0 && errno != EAGAIN) {
+ int old_socket = logd_socket;
+ lock.unlock();
+ ResetSocket(old_socket);
+ lock.lock();
+
+ ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
}
- switch (ret) {
- case -ENOTCONN:
- case -ECONNREFUSED:
- case -ENOENT:
- if (__android_log_trylock()) {
- return ret; /* in a signal handler? try again when less stressed */
- }
- __logdClose(ret);
- ret = logdOpen();
- __android_log_unlock();
- if (ret < 0) {
- return ret;
- }
-
- ret = TEMP_FAILURE_RETRY(writev(atomic_load(&logdLoggerWrite.context.sock), newVec, i));
- if (ret < 0) {
- ret = -errno;
- }
- [[fallthrough]];
- default:
- break;
+ if (ret < 0) {
+ ret = -errno;
}
if (ret > (ssize_t)sizeof(header)) {
ret -= sizeof(header);
- } else if (ret == -EAGAIN) {
+ } else if (ret < 0) {
atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
if (logId == LOG_ID_SECURITY) {
atomic_fetch_add_explicit(&droppedSecurity, 1, memory_order_relaxed);
diff --git a/liblog/logger.h b/liblog/logger.h
index 9d74d29..40d5fe5 100644
--- a/liblog/logger.h
+++ b/liblog/logger.h
@@ -26,20 +26,7 @@
__BEGIN_DECLS
-/* Union, sock or fd of zero is not allowed unless static initialized */
-union android_log_context_union {
- void* priv;
- atomic_int sock;
- atomic_int fd;
-};
-
struct android_log_transport_write {
- const char* name; /* human name to describe the transport */
- unsigned logMask; /* mask cache of available() success */
- union android_log_context_union context; /* Initialized by static allocation */
-
- int (*available)(log_id_t logId); /* Does not cause resources to be taken */
- int (*open)(); /* can be called multiple times, reusing current resources */
void (*close)(); /* free up resources */
/* write log to transport, returns number of bytes propagated, or -errno */
int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec,
@@ -83,8 +70,4 @@
}
#endif
-void __android_log_lock();
-int __android_log_trylock();
-void __android_log_unlock();
-
__END_DECLS
diff --git a/liblog/logger_lock.cpp b/liblog/logger_lock.cpp
deleted file mode 100644
index 4636b00..0000000
--- a/liblog/logger_lock.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2007-2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * Some OS specific dribs and drabs (locking etc).
- */
-
-#if !defined(_WIN32)
-#include <pthread.h>
-#endif
-
-#include "logger.h"
-
-#if !defined(_WIN32)
-static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
-#endif
-
-void __android_log_lock() {
-#if !defined(_WIN32)
- /*
- * If we trigger a signal handler in the middle of locked activity and the
- * signal handler logs a message, we could get into a deadlock state.
- */
- pthread_mutex_lock(&log_init_lock);
-#endif
-}
-
-int __android_log_trylock() {
-#if !defined(_WIN32)
- return pthread_mutex_trylock(&log_init_lock);
-#else
- return 0;
-#endif
-}
-
-void __android_log_unlock() {
-#if !defined(_WIN32)
- pthread_mutex_unlock(&log_init_lock);
-#endif
-}
diff --git a/liblog/logger_write.cpp b/liblog/logger_write.cpp
index e1772f1..d38b402 100644
--- a/liblog/logger_write.cpp
+++ b/liblog/logger_write.cpp
@@ -24,7 +24,6 @@
#include <android/set_abort_message.h>
#endif
-#include <log/event_tag_map.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
@@ -47,11 +46,8 @@
android_log_transport_write* android_log_persist_write = nullptr;
#endif
-static int __write_to_log_init(log_id_t, struct iovec* vec, size_t nr);
-static int (*write_to_log)(log_id_t, struct iovec* vec, size_t nr) = __write_to_log_init;
-
-static int check_log_uid_permissions() {
#if defined(__ANDROID__)
+static int check_log_uid_permissions() {
uid_t uid = __android_log_uid();
/* Matches clientHasLogCredentials() in logd */
@@ -88,51 +84,14 @@
}
}
}
-#endif
return 0;
}
-
-static void __android_log_cache_available(struct android_log_transport_write* node) {
- uint32_t i;
-
- if (node->logMask) {
- return;
- }
-
- for (i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
- if (i != LOG_ID_KERNEL && (i != LOG_ID_SECURITY || check_log_uid_permissions() == 0) &&
- (*node->available)(static_cast<log_id_t>(i)) >= 0) {
- node->logMask |= 1 << i;
- }
- }
-}
-
-#if defined(__ANDROID__)
-static atomic_uintptr_t tagMap;
#endif
/*
* Release any logger resources. A new log write will immediately re-acquire.
*/
void __android_log_close() {
-#if defined(__ANDROID__)
- EventTagMap* m;
-#endif
-
- __android_log_lock();
-
- write_to_log = __write_to_log_init;
-
- /*
- * Threads that are actively writing at this point are not held back
- * by a lock and are at risk of dropping the messages with a return code
- * -EBADF. Prefer to return error code than add the overhead of a lock to
- * each log writing call to guarantee delivery. In addition, anyone
- * calling this is doing so to release the logging resources and shut down,
- * for them to do so with outstanding log requests in other threads is a
- * disengenuous use of this function.
- */
-
if (android_log_write != nullptr) {
android_log_write->close();
}
@@ -141,64 +100,18 @@
android_log_persist_write->close();
}
-#if defined(__ANDROID__)
- /*
- * Additional risk here somewhat mitigated by immediately unlock flushing
- * the processor cache. The multi-threaded race that we choose to accept,
- * to minimize locking, is an atomic_load in a writer picking up a value
- * just prior to entering this routine. There will be an use after free.
- *
- * Again, anyone calling this is doing so to release the logging resources
- * is most probably going to quiesce then shut down; or to restart after
- * a fork so the risk should be non-existent. For this reason we
- * choose a mitigation stance for efficiency instead of incuring the cost
- * of a lock for every log write.
- */
- m = (EventTagMap*)atomic_exchange(&tagMap, (uintptr_t)0);
-#endif
-
- __android_log_unlock();
-
-#if defined(__ANDROID__)
- if (m != (EventTagMap*)(uintptr_t)-1LL) android_closeEventTagMap(m);
-#endif
}
-static bool transport_initialize(android_log_transport_write* transport) {
- if (transport == nullptr) {
- return false;
- }
-
- __android_log_cache_available(transport);
- if (!transport->logMask) {
- return false;
- }
-
- // TODO: Do we actually need to call close() if open() fails?
- if (transport->open() < 0) {
- transport->close();
- return false;
- }
-
- return true;
-}
-
-/* log_init_lock assumed */
-static int __write_to_log_initialize() {
- if (!transport_initialize(android_log_write)) {
- return -ENODEV;
- }
-
- transport_initialize(android_log_persist_write);
-
- return 1;
-}
-
-static int __write_to_log_daemon(log_id_t log_id, struct iovec* vec, size_t nr) {
+static int write_to_log(log_id_t log_id, struct iovec* vec, size_t nr) {
int ret, save_errno;
struct timespec ts;
save_errno = errno;
+
+ if (log_id == LOG_ID_KERNEL) {
+ return -EINVAL;
+ }
+
#if defined(__ANDROID__)
clock_gettime(android_log_clockid(), &ts);
@@ -219,49 +132,10 @@
return -EPERM;
}
} else if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS) {
- const char* tag;
- size_t len;
- EventTagMap *m, *f;
-
if (vec[0].iov_len < 4) {
errno = save_errno;
return -EINVAL;
}
-
- tag = NULL;
- len = 0;
- f = NULL;
- m = (EventTagMap*)atomic_load(&tagMap);
-
- if (!m) {
- ret = __android_log_trylock();
- m = (EventTagMap*)atomic_load(&tagMap); /* trylock flush cache */
- if (!m) {
- m = android_openEventTagMap(NULL);
- if (ret) { /* trylock failed, use local copy, mark for close */
- f = m;
- } else {
- if (!m) { /* One chance to open map file */
- m = (EventTagMap*)(uintptr_t)-1LL;
- }
- atomic_store(&tagMap, (uintptr_t)m);
- }
- }
- if (!ret) { /* trylock succeeded, unlock */
- __android_log_unlock();
- }
- }
- if (m && (m != (EventTagMap*)(uintptr_t)-1LL)) {
- tag = android_lookupEventTag_len(m, &len, *static_cast<uint32_t*>(vec[0].iov_base));
- }
- ret = __android_log_is_loggable_len(ANDROID_LOG_INFO, tag, len, ANDROID_LOG_VERBOSE);
- if (f) { /* local copy marked for close */
- android_closeEventTagMap(f);
- }
- if (!ret) {
- errno = save_errno;
- return -EPERM;
- }
} else {
int prio = *static_cast<int*>(vec[0].iov_base);
const char* tag = static_cast<const char*>(vec[1].iov_base);
@@ -283,9 +157,8 @@
#endif
ret = 0;
- size_t i = 1 << log_id;
- if (android_log_write != nullptr && (android_log_write->logMask & i)) {
+ if (android_log_write != nullptr) {
ssize_t retval;
retval = android_log_write->write(log_id, &ts, vec, nr);
if (ret >= 0) {
@@ -293,7 +166,7 @@
}
}
- if (android_log_persist_write != nullptr && (android_log_persist_write->logMask & i)) {
+ if (android_log_persist_write != nullptr) {
android_log_persist_write->write(log_id, &ts, vec, nr);
}
@@ -301,29 +174,6 @@
return ret;
}
-static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {
- int ret, save_errno = errno;
-
- __android_log_lock();
-
- if (write_to_log == __write_to_log_init) {
- ret = __write_to_log_initialize();
- if (ret < 0) {
- __android_log_unlock();
- errno = save_errno;
- return ret;
- }
-
- write_to_log = __write_to_log_daemon;
- }
-
- __android_log_unlock();
-
- ret = write_to_log(log_id, vec, nr);
- errno = save_errno;
- return ret;
-}
-
int __android_log_write(int prio, const char* tag, const char* msg) {
return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
}
diff --git a/liblog/pmsg_writer.cpp b/liblog/pmsg_writer.cpp
index 54980d9..4f45780 100644
--- a/liblog/pmsg_writer.cpp
+++ b/liblog/pmsg_writer.cpp
@@ -25,68 +25,47 @@
#include <sys/types.h>
#include <time.h>
+#include <shared_mutex>
+
#include <log/log_properties.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
#include "log_portability.h"
#include "logger.h"
+#include "rwlock.h"
#include "uio.h"
-static int pmsgOpen();
-static void pmsgClose();
-static int pmsgAvailable(log_id_t logId);
-static int pmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
+static void PmsgClose();
+static int PmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr);
struct android_log_transport_write pmsgLoggerWrite = {
- .name = "pmsg",
- .logMask = 0,
- .context.fd = -1,
- .available = pmsgAvailable,
- .open = pmsgOpen,
- .close = pmsgClose,
- .write = pmsgWrite,
+ .close = PmsgClose,
+ .write = PmsgWrite,
};
-static int pmsgOpen() {
- int fd = atomic_load(&pmsgLoggerWrite.context.fd);
- if (fd < 0) {
- int i;
+static int pmsg_fd;
+static RwLock pmsg_fd_lock;
- fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
- i = atomic_exchange(&pmsgLoggerWrite.context.fd, fd);
- if ((i >= 0) && (i != fd)) {
- close(i);
- }
+static void PmsgOpen() {
+ auto lock = std::unique_lock{pmsg_fd_lock};
+ if (pmsg_fd > 0) {
+ // Someone raced us and opened the socket already.
+ return;
}
- return fd;
+ pmsg_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
}
-static void pmsgClose() {
- int fd = atomic_exchange(&pmsgLoggerWrite.context.fd, -1);
- if (fd >= 0) {
- close(fd);
+static void PmsgClose() {
+ auto lock = std::unique_lock{pmsg_fd_lock};
+ if (pmsg_fd > 0) {
+ close(pmsg_fd);
}
+ pmsg_fd = 0;
}
-static int pmsgAvailable(log_id_t logId) {
- if (logId > LOG_ID_SECURITY) {
- return -EINVAL;
- }
- if ((logId != LOG_ID_SECURITY) && (logId != LOG_ID_EVENTS) && !__android_log_is_debuggable()) {
- return -EINVAL;
- }
- if (atomic_load(&pmsgLoggerWrite.context.fd) < 0) {
- if (access("/dev/pmsg0", W_OK) == 0) {
- return 0;
- }
- return -EBADF;
- }
- return 1;
-}
-
-static int pmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
+static int PmsgWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
static const unsigned headerLength = 2;
struct iovec newVec[nr + headerLength];
android_log_header_t header;
@@ -94,17 +73,31 @@
size_t i, payloadSize;
ssize_t ret;
- if ((logId == LOG_ID_EVENTS) && !__android_log_is_debuggable()) {
- if (vec[0].iov_len < 4) {
- return -EINVAL;
+ if (!__android_log_is_debuggable()) {
+ if (logId != LOG_ID_EVENTS && logId != LOG_ID_SECURITY) {
+ return -1;
}
- if (SNET_EVENT_LOG_TAG != *static_cast<uint32_t*>(vec[0].iov_base)) {
- return -EPERM;
+ if (logId == LOG_ID_EVENTS) {
+ if (vec[0].iov_len < 4) {
+ return -EINVAL;
+ }
+
+ if (SNET_EVENT_LOG_TAG != *static_cast<uint32_t*>(vec[0].iov_base)) {
+ return -EPERM;
+ }
}
}
- if (atomic_load(&pmsgLoggerWrite.context.fd) < 0) {
+ auto lock = std::shared_lock{pmsg_fd_lock};
+
+ if (pmsg_fd <= 0) {
+ lock.unlock();
+ PmsgOpen();
+ lock.lock();
+ }
+
+ if (pmsg_fd <= 0) {
return -EBADF;
}
@@ -158,7 +151,7 @@
}
pmsgHeader.len += payloadSize;
- ret = TEMP_FAILURE_RETRY(writev(atomic_load(&pmsgLoggerWrite.context.fd), newVec, i));
+ ret = TEMP_FAILURE_RETRY(writev(pmsg_fd, newVec, i));
if (ret < 0) {
ret = errno ? -errno : -ENOTCONN;
}
@@ -193,7 +186,6 @@
/* Write a buffer as filename references (tag = <basedir>:<basename>) */
ssize_t __android_log_pmsg_file_write(log_id_t logId, char prio, const char* filename,
const char* buf, size_t len) {
- bool weOpened;
size_t length, packet_len;
const char* tag;
char *cp, *slash;
@@ -233,7 +225,6 @@
vec[1].iov_base = (unsigned char*)tag;
vec[1].iov_len = length;
- weOpened = false;
for (ts.tv_nsec = 0, length = len; length; ts.tv_nsec += ANDROID_LOG_PMSG_FILE_SEQUENCE) {
ssize_t ret;
size_t transfer;
@@ -254,37 +245,15 @@
vec[2].iov_base = (unsigned char*)buf;
vec[2].iov_len = transfer;
- if (atomic_load(&pmsgLoggerWrite.context.fd) < 0) {
- if (!weOpened) { /* Impossible for weOpened = true here */
- __android_log_lock();
- }
- weOpened = atomic_load(&pmsgLoggerWrite.context.fd) < 0;
- if (!weOpened) {
- __android_log_unlock();
- } else if (pmsgOpen() < 0) {
- __android_log_unlock();
- free(cp);
- return -EBADF;
- }
- }
-
- ret = pmsgWrite(logId, &ts, vec, sizeof(vec) / sizeof(vec[0]));
+ ret = PmsgWrite(logId, &ts, vec, sizeof(vec) / sizeof(vec[0]));
if (ret <= 0) {
- if (weOpened) {
- pmsgClose();
- __android_log_unlock();
- }
free(cp);
return ret ? ret : (len - length);
}
length -= transfer;
buf += transfer;
}
- if (weOpened) {
- pmsgClose();
- __android_log_unlock();
- }
free(cp);
return len;
}
diff --git a/liblog/rwlock.h b/liblog/rwlock.h
new file mode 100644
index 0000000..00f1806
--- /dev/null
+++ b/liblog/rwlock.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <pthread.h>
+
+// As of the end of Dec 2019, std::shared_mutex is *not* simply a pthread_rwlock, but rather a
+// combination of std::mutex and std::condition variable, which is obviously less efficient. This
+// immitates what std::shared_mutex should be doing and is compatible with std::shared_lock and
+// std::unique_lock.
+
+class RwLock {
+ public:
+ RwLock() {}
+ ~RwLock() {}
+
+ void lock() { pthread_rwlock_wrlock(&rwlock_); }
+ void unlock() { pthread_rwlock_unlock(&rwlock_); }
+
+ void lock_shared() { pthread_rwlock_rdlock(&rwlock_); }
+ void unlock_shared() { pthread_rwlock_unlock(&rwlock_); }
+
+ private:
+ pthread_rwlock_t rwlock_ = PTHREAD_RWLOCK_INITIALIZER;
+};
diff --git a/liblog/tests/Android.bp b/liblog/tests/Android.bp
index 99df4ca..f58c524 100644
--- a/liblog/tests/Android.bp
+++ b/liblog/tests/Android.bp
@@ -62,6 +62,7 @@
"log_time_test.cpp",
"log_wrap_test.cpp",
"logprint_test.cpp",
+ "rwlock_test.cpp",
],
shared_libs: [
"libcutils",
diff --git a/liblog/tests/rwlock_test.cpp b/liblog/tests/rwlock_test.cpp
new file mode 100644
index 0000000..617d5c4
--- /dev/null
+++ b/liblog/tests/rwlock_test.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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 "../rwlock.h"
+
+#include <chrono>
+#include <shared_mutex>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+using namespace std::literals;
+
+TEST(rwlock, reader_then_reader_lock) {
+ RwLock lock;
+
+ bool thread_ran = false;
+ auto read_guard = std::shared_lock{lock};
+
+ auto reader_thread = std::thread([&] {
+ auto read_guard = std::shared_lock{lock};
+ thread_ran = true;
+ });
+
+ auto end_time = std::chrono::steady_clock::now() + 1s;
+
+ while (std::chrono::steady_clock::now() < end_time) {
+ if (thread_ran) {
+ break;
+ }
+ }
+
+ EXPECT_EQ(true, thread_ran);
+
+ // Unlock the lock in case something went wrong, to ensure that we can still join() the thread.
+ read_guard.unlock();
+ reader_thread.join();
+}
+
+template <template <typename> typename L1, template <typename> typename L2>
+void TestBlockingLocks() {
+ RwLock lock;
+
+ bool thread_ran = false;
+ auto read_guard = L1{lock};
+
+ auto reader_thread = std::thread([&] {
+ auto read_guard = L2{lock};
+ thread_ran = true;
+ });
+
+ auto end_time = std::chrono::steady_clock::now() + 1s;
+
+ while (std::chrono::steady_clock::now() < end_time) {
+ if (thread_ran) {
+ break;
+ }
+ }
+
+ EXPECT_EQ(false, thread_ran);
+
+ read_guard.unlock();
+ reader_thread.join();
+
+ EXPECT_EQ(true, thread_ran);
+}
+
+TEST(rwlock, reader_then_writer_lock) {
+ TestBlockingLocks<std::shared_lock, std::unique_lock>();
+}
+
+TEST(rwlock, writer_then_reader_lock) {
+ TestBlockingLocks<std::unique_lock, std::shared_lock>();
+}
+
+TEST(rwlock, writer_then_writer_lock) {
+ TestBlockingLocks<std::unique_lock, std::unique_lock>();
+}
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index 333fc55..ee6ae7a 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -44,6 +44,9 @@
bool Rmmod(const std::string& module_name);
std::vector<std::string> GetDependencies(const std::string& module);
bool ModuleExists(const std::string& module_name);
+ void AddOption(const std::string& module_name, const std::string& option_name,
+ const std::string& value);
+ std::string GetKernelCmdline();
bool ParseDepCallback(const std::string& base_path, const std::vector<std::string>& args);
bool ParseAliasCallback(const std::vector<std::string>& args);
@@ -51,6 +54,7 @@
bool ParseLoadCallback(const std::vector<std::string>& args);
bool ParseOptionsCallback(const std::vector<std::string>& args);
bool ParseBlacklistCallback(const std::vector<std::string>& args);
+ void ParseKernelCmdlineOptions();
void ParseCfg(const std::string& cfg, std::function<bool(const std::vector<std::string>&)> f);
std::vector<std::pair<std::string, std::string>> module_aliases_;
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 6b9107f..f22bbf1 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -238,6 +238,80 @@
return;
}
+void Modprobe::AddOption(const std::string& module_name, const std::string& option_name,
+ const std::string& value) {
+ auto canonical_name = MakeCanonical(module_name);
+ auto options_iter = module_options_.find(canonical_name);
+ auto option_str = option_name + "=" + value;
+ if (options_iter != module_options_.end()) {
+ options_iter->second = options_iter->second + " " + option_str;
+ } else {
+ module_options_.emplace(canonical_name, option_str);
+ }
+}
+
+void Modprobe::ParseKernelCmdlineOptions(void) {
+ std::string cmdline = GetKernelCmdline();
+ std::string module_name = "";
+ std::string option_name = "";
+ std::string value = "";
+ bool in_module = true;
+ bool in_option = false;
+ bool in_value = false;
+ bool in_quotes = false;
+ int start = 0;
+
+ for (int i = 0; i < cmdline.size(); i++) {
+ if (cmdline[i] == '"') {
+ in_quotes = !in_quotes;
+ }
+
+ if (in_quotes) continue;
+
+ if (cmdline[i] == ' ') {
+ if (in_value) {
+ value = cmdline.substr(start, i - start);
+ if (!module_name.empty() && !option_name.empty()) {
+ AddOption(module_name, option_name, value);
+ }
+ }
+ module_name = "";
+ option_name = "";
+ value = "";
+ in_value = false;
+ start = i + 1;
+ in_module = true;
+ continue;
+ }
+
+ if (cmdline[i] == '.') {
+ if (in_module) {
+ module_name = cmdline.substr(start, i - start);
+ start = i + 1;
+ in_module = false;
+ }
+ in_option = true;
+ continue;
+ }
+
+ if (cmdline[i] == '=') {
+ if (in_option) {
+ option_name = cmdline.substr(start, i - start);
+ start = i + 1;
+ in_option = false;
+ }
+ in_value = true;
+ continue;
+ }
+ }
+ if (in_value && !in_quotes) {
+ value = cmdline.substr(start, cmdline.size() - start);
+ if (!module_name.empty() && !option_name.empty()) {
+ AddOption(module_name, option_name, value);
+ }
+ }
+}
+
Modprobe::Modprobe(const std::vector<std::string>& base_paths) {
using namespace std::placeholders;
@@ -261,6 +335,7 @@
ParseCfg(base_path + "/modules.blacklist", blacklist_callback);
}
+ ParseKernelCmdlineOptions();
android::base::SetMinimumLogSeverity(android::base::INFO);
}
diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp
index 8bebe4c..99472c1 100644
--- a/libmodprobe/libmodprobe_ext.cpp
+++ b/libmodprobe/libmodprobe_ext.cpp
@@ -17,11 +17,20 @@
#include <sys/stat.h>
#include <sys/syscall.h>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <modprobe/modprobe.h>
+std::string Modprobe::GetKernelCmdline(void) {
+ std::string cmdline;
+ if (!android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
+ return "";
+ }
+ return cmdline;
+}
+
bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
android::base::unique_fd fd(
TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
diff --git a/libmodprobe/libmodprobe_ext_test.cpp b/libmodprobe/libmodprobe_ext_test.cpp
index 7d817b1..057dea3 100644
--- a/libmodprobe/libmodprobe_ext_test.cpp
+++ b/libmodprobe/libmodprobe_ext_test.cpp
@@ -29,6 +29,10 @@
#include "libmodprobe_test.h"
+std::string Modprobe::GetKernelCmdline(void) {
+ return kernel_cmdline;
+}
+
bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
auto deps = GetDependencies(MakeCanonical(path_name));
if (deps.empty()) {
@@ -57,7 +61,7 @@
bool Modprobe::Rmmod(const std::string& module_name) {
for (auto it = modules_loaded.begin(); it != modules_loaded.end(); it++) {
- if (*it == module_name) {
+ if (*it == module_name || android::base::StartsWith(*it, module_name + " ")) {
modules_loaded.erase(it);
return true;
}
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index a711631..879c7f2 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -31,7 +31,13 @@
// Used by libmodprobe_ext_test to report which modules would have been loaded.
std::vector<std::string> modules_loaded;
+// Used by libmodprobe_ext_test to fake a kernel commandline
+std::string kernel_cmdline;
+
TEST(libmodprobe, Test) {
+ kernel_cmdline =
+ "flag1 flag2 test1.option1=50 test4.option3=\"set x\" test1.option2=60 "
+ "test8. test5.option1= test10.option1=1";
test_modules = {
"/test1.ko", "/test2.ko", "/test3.ko", "/test4.ko", "/test5.ko",
"/test6.ko", "/test7.ko", "/test8.ko", "/test9.ko", "/test10.ko",
@@ -42,25 +48,33 @@
"/test14.ko",
"/test15.ko",
"/test3.ko",
- "/test4.ko",
- "/test1.ko",
+ "/test4.ko option3=\"set x\"",
+ "/test1.ko option1=50 option2=60",
"/test6.ko",
"/test2.ko",
- "/test5.ko",
+ "/test5.ko option1=",
"/test8.ko",
"/test7.ko param1=4",
"/test9.ko param_x=1 param_y=2 param_z=3",
- "/test10.ko",
+ "/test10.ko option1=1",
"/test12.ko",
"/test11.ko",
"/test13.ko",
};
std::vector<std::string> expected_after_remove = {
- "/test14.ko", "/test15.ko", "/test1.ko",
- "/test6.ko", "/test2.ko", "/test5.ko",
- "/test8.ko", "/test7.ko param1=4", "/test9.ko param_x=1 param_y=2 param_z=3",
- "/test10.ko", "/test12.ko", "/test11.ko",
+ "/test14.ko",
+ "/test15.ko",
+ "/test1.ko option1=50 option2=60",
+ "/test6.ko",
+ "/test2.ko",
+ "/test5.ko option1=",
+ "/test8.ko",
+ "/test7.ko param1=4",
+ "/test9.ko param_x=1 param_y=2 param_z=3",
+ "/test10.ko option1=1",
+ "/test12.ko",
+ "/test11.ko",
"/test13.ko",
};
diff --git a/libmodprobe/libmodprobe_test.h b/libmodprobe/libmodprobe_test.h
index a001b69..e7b949f 100644
--- a/libmodprobe/libmodprobe_test.h
+++ b/libmodprobe/libmodprobe_test.h
@@ -19,5 +19,6 @@
#include <string>
#include <vector>
+extern std::string kernel_cmdline;
extern std::vector<std::string> test_modules;
extern std::vector<std::string> modules_loaded;
diff --git a/libstats/Android.bp b/libstats/Android.bp
deleted file mode 100644
index f5ee1da..0000000
--- a/libstats/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-//
-// 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.
-//
-
-// ==========================================================
-// Native library to write stats log to statsd socket
-// ==========================================================
-cc_library {
- name: "libstatssocket",
- srcs: [
- "stats_event_list.c",
- "statsd_writer.c",
- ],
- host_supported: true,
- cflags: [
- "-Wall",
- "-Werror",
- "-DLIBLOG_LOG_TAG=1006",
- "-DWRITE_TO_STATSD=1",
- "-DWRITE_TO_LOGD=0",
- ],
- export_include_dirs: ["include"],
- shared_libs: [
- "libcutils",
- "liblog",
- ],
-}
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
new file mode 100644
index 0000000..465c05a
--- /dev/null
+++ b/libstats/push_compat/Android.bp
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// =========================================================================
+// Native library that toggles between the old and new statsd socket
+// protocols. This library should only be used by DNS resolver or other
+// native modules on Q that log pushed atoms to statsd.
+// =========================================================================
+cc_defaults {
+ name: "libstatspush_compat_defaults",
+ srcs: [
+ "statsd_writer.c",
+ "stats_event_list.c",
+ "StatsEventCompat.cpp"
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-DWRITE_TO_STATSD=1",
+ "-DWRITE_TO_LOGD=0",
+ ],
+ header_libs: ["libstatssocket_headers"],
+ static_libs: [
+ "libbase",
+ "liblog",
+ "libutils",
+ ],
+}
+
+cc_library {
+ name: "libstatspush_compat",
+ defaults: ["libstatspush_compat_defaults"],
+ export_include_dirs: ["include"],
+ static_libs: ["libgtest_prod"],
+}
+
+cc_test {
+ name: "libstatspush_compat_test",
+ defaults: ["libstatspush_compat_defaults"],
+ test_suites: ["device_tests"],
+ srcs: [
+ "tests/StatsEventCompat_test.cpp",
+ ],
+ static_libs: ["libgmock"],
+}
+
diff --git a/libstats/push_compat/StatsEventCompat.cpp b/libstats/push_compat/StatsEventCompat.cpp
new file mode 100644
index 0000000..edfa070
--- /dev/null
+++ b/libstats/push_compat/StatsEventCompat.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2019 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 "include/StatsEventCompat.h"
+#include <android-base/properties.h>
+#include <android/api-level.h>
+#include <android/log.h>
+#include <dlfcn.h>
+#include <utils/SystemClock.h>
+
+using android::base::GetProperty;
+
+const static int kStatsEventTag = 1937006964;
+
+/* Checking ro.build.version.release is fragile, as the release field is
+ * an opaque string without structural guarantees. However, testing confirms
+ * that on Q devices, the property is "10," and on R, it is "R." Until
+ * android_get_device_api_level() is updated, this is the only solution.
+ *
+ * TODO(b/146019024): migrate to android_get_device_api_level()
+ */
+const bool StatsEventCompat::mPlatformAtLeastR =
+ GetProperty("ro.build.version.codename", "") == "R" ||
+ android_get_device_api_level() > __ANDROID_API_Q__;
+
+// definitions of static class variables
+bool StatsEventCompat::mAttemptedLoad = false;
+struct stats_event_api_table* StatsEventCompat::mStatsEventApi = nullptr;
+std::mutex StatsEventCompat::mLoadLock;
+
+StatsEventCompat::StatsEventCompat() : mEventQ(kStatsEventTag) {
+ // guard loading because StatsEventCompat might be called from multithreaded
+ // environment
+ {
+ std::lock_guard<std::mutex> lg(mLoadLock);
+ if (!mAttemptedLoad) {
+ void* handle = dlopen("libstatssocket.so", RTLD_NOW);
+ if (handle) {
+ mStatsEventApi = (struct stats_event_api_table*)dlsym(handle, "table");
+ } else {
+ ALOGE("dlopen failed: %s\n", dlerror());
+ }
+ }
+ mAttemptedLoad = true;
+ }
+
+ if (mStatsEventApi) {
+ mEventR = mStatsEventApi->obtain();
+ } else if (!mPlatformAtLeastR) {
+ mEventQ << android::elapsedRealtimeNano();
+ }
+}
+
+StatsEventCompat::~StatsEventCompat() {
+ if (mStatsEventApi) mStatsEventApi->release(mEventR);
+}
+
+void StatsEventCompat::setAtomId(int32_t atomId) {
+ if (mStatsEventApi) {
+ mStatsEventApi->set_atom_id(mEventR, (uint32_t)atomId);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ << atomId;
+ }
+}
+
+void StatsEventCompat::writeInt32(int32_t value) {
+ if (mStatsEventApi) {
+ mStatsEventApi->write_int32(mEventR, value);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ << value;
+ }
+}
+
+void StatsEventCompat::writeInt64(int64_t value) {
+ if (mStatsEventApi) {
+ mStatsEventApi->write_int64(mEventR, value);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ << value;
+ }
+}
+
+void StatsEventCompat::writeFloat(float value) {
+ if (mStatsEventApi) {
+ mStatsEventApi->write_float(mEventR, value);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ << value;
+ }
+}
+
+void StatsEventCompat::writeBool(bool value) {
+ if (mStatsEventApi) {
+ mStatsEventApi->write_bool(mEventR, value);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ << value;
+ }
+}
+
+void StatsEventCompat::writeByteArray(const char* buffer, size_t length) {
+ if (mStatsEventApi) {
+ mStatsEventApi->write_byte_array(mEventR, (const uint8_t*)buffer, length);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ.AppendCharArray(buffer, length);
+ }
+}
+
+void StatsEventCompat::writeString(const char* value) {
+ if (value == nullptr) value = "";
+
+ if (mStatsEventApi) {
+ mStatsEventApi->write_string8(mEventR, value);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ << value;
+ }
+}
+
+void StatsEventCompat::writeAttributionChain(const int32_t* uids, size_t numUids,
+ const vector<const char*>& tags) {
+ if (mStatsEventApi) {
+ mStatsEventApi->write_attribution_chain(mEventR, (const uint32_t*)uids, tags.data(),
+ (uint8_t)numUids);
+ } else if (!mPlatformAtLeastR) {
+ mEventQ.begin();
+ for (size_t i = 0; i < numUids; i++) {
+ mEventQ.begin();
+ mEventQ << uids[i];
+ const char* tag = tags[i] ? tags[i] : "";
+ mEventQ << tag;
+ mEventQ.end();
+ }
+ mEventQ.end();
+ }
+}
+
+void StatsEventCompat::writeKeyValuePairs(const map<int, int32_t>& int32Map,
+ const map<int, int64_t>& int64Map,
+ const map<int, const char*>& stringMap,
+ const map<int, float>& floatMap) {
+ if (mStatsEventApi) {
+ vector<struct key_value_pair> pairs;
+
+ for (const auto& it : int32Map) {
+ pairs.push_back({.key = it.first, .valueType = INT32_TYPE, .int32Value = it.second});
+ }
+ for (const auto& it : int64Map) {
+ pairs.push_back({.key = it.first, .valueType = INT64_TYPE, .int64Value = it.second});
+ }
+ for (const auto& it : stringMap) {
+ pairs.push_back({.key = it.first, .valueType = STRING_TYPE, .stringValue = it.second});
+ }
+ for (const auto& it : floatMap) {
+ pairs.push_back({.key = it.first, .valueType = FLOAT_TYPE, .floatValue = it.second});
+ }
+
+ mStatsEventApi->write_key_value_pairs(mEventR, pairs.data(), (uint8_t)pairs.size());
+ }
+
+ else if (!mPlatformAtLeastR) {
+ mEventQ.begin();
+ writeKeyValuePairMap(int32Map);
+ writeKeyValuePairMap(int64Map);
+ writeKeyValuePairMap(stringMap);
+ writeKeyValuePairMap(floatMap);
+ mEventQ.end();
+ }
+}
+
+template <class T>
+void StatsEventCompat::writeKeyValuePairMap(const map<int, T>& keyValuePairMap) {
+ for (const auto& it : keyValuePairMap) {
+ mEventQ.begin();
+ mEventQ << it.first;
+ mEventQ << it.second;
+ mEventQ.end();
+ }
+}
+
+// explicitly specify which types we're going to use
+template void StatsEventCompat::writeKeyValuePairMap<int32_t>(const map<int, int32_t>&);
+template void StatsEventCompat::writeKeyValuePairMap<int64_t>(const map<int, int64_t>&);
+template void StatsEventCompat::writeKeyValuePairMap<float>(const map<int, float>&);
+template void StatsEventCompat::writeKeyValuePairMap<const char*>(const map<int, const char*>&);
+
+void StatsEventCompat::addBoolAnnotation(uint8_t annotationId, bool value) {
+ if (mStatsEventApi) mStatsEventApi->add_bool_annotation(mEventR, annotationId, value);
+ // Don't do anything if on Q.
+}
+
+void StatsEventCompat::addInt32Annotation(uint8_t annotationId, int32_t value) {
+ if (mStatsEventApi) mStatsEventApi->add_int32_annotation(mEventR, annotationId, value);
+ // Don't do anything if on Q.
+}
+
+int StatsEventCompat::writeToSocket() {
+ if (mStatsEventApi) {
+ mStatsEventApi->build(mEventR);
+ return mStatsEventApi->write(mEventR);
+ }
+
+ if (!mPlatformAtLeastR) return mEventQ.write(LOG_ID_STATS);
+
+ // We reach here only if we're on R, but libstatspush_compat was unable to
+ // be loaded using dlopen.
+ return -ENOLINK;
+}
+
+bool StatsEventCompat::usesNewSchema() {
+ return mStatsEventApi != nullptr;
+}
diff --git a/libstats/push_compat/include/StatsEventCompat.h b/libstats/push_compat/include/StatsEventCompat.h
new file mode 100644
index 0000000..a8cde68
--- /dev/null
+++ b/libstats/push_compat/include/StatsEventCompat.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+#include <map>
+#include <mutex>
+#include <vector>
+#include "stats_event.h"
+#include "stats_event_list.h"
+
+using std::map;
+using std::vector;
+
+class StatsEventCompat {
+ public:
+ StatsEventCompat();
+ ~StatsEventCompat();
+
+ void setAtomId(int32_t atomId);
+ void writeInt32(int32_t value);
+ void writeInt64(int64_t value);
+ void writeFloat(float value);
+ void writeBool(bool value);
+ void writeByteArray(const char* buffer, size_t length);
+ void writeString(const char* value);
+
+ // Pre-condition: numUids == tags.size()
+ void writeAttributionChain(const int32_t* uids, size_t numUids,
+ const vector<const char*>& tags);
+
+ void writeKeyValuePairs(const map<int, int32_t>& int32Map, const map<int, int64_t>& int64Map,
+ const map<int, const char*>& stringMap,
+ const map<int, float>& floatMap);
+
+ void addBoolAnnotation(uint8_t annotationId, bool value);
+ void addInt32Annotation(uint8_t annotationId, int32_t value);
+
+ int writeToSocket();
+
+ private:
+ // static member variables
+ const static bool mPlatformAtLeastR;
+ static bool mAttemptedLoad;
+ static std::mutex mLoadLock;
+ static struct stats_event_api_table* mStatsEventApi;
+
+ // non-static member variables
+ struct stats_event* mEventR = nullptr;
+ stats_event_list mEventQ;
+
+ template <class T>
+ void writeKeyValuePairMap(const map<int, T>& keyValuePairMap);
+
+ bool usesNewSchema();
+ FRIEND_TEST(StatsEventCompatTest, TestDynamicLoading);
+};
diff --git a/libstats/include/stats_event_list.h b/libstats/push_compat/include/stats_event_list.h
similarity index 100%
rename from libstats/include/stats_event_list.h
rename to libstats/push_compat/include/stats_event_list.h
diff --git a/libstats/stats_event_list.c b/libstats/push_compat/stats_event_list.c
similarity index 100%
rename from libstats/stats_event_list.c
rename to libstats/push_compat/stats_event_list.c
diff --git a/libstats/statsd_writer.c b/libstats/push_compat/statsd_writer.c
similarity index 98%
rename from libstats/statsd_writer.c
rename to libstats/push_compat/statsd_writer.c
index 073b67f..04d3b46 100644
--- a/libstats/statsd_writer.c
+++ b/libstats/push_compat/statsd_writer.c
@@ -101,7 +101,7 @@
strcpy(un.sun_path, "/dev/socket/statsdw");
if (TEMP_FAILURE_RETRY(
- connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) {
+ connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) {
ret = -errno;
switch (ret) {
case -ENOTCONN:
diff --git a/libstats/statsd_writer.h b/libstats/push_compat/statsd_writer.h
similarity index 100%
copy from libstats/statsd_writer.h
copy to libstats/push_compat/statsd_writer.h
diff --git a/libstats/push_compat/tests/StatsEventCompat_test.cpp b/libstats/push_compat/tests/StatsEventCompat_test.cpp
new file mode 100644
index 0000000..2be24ec
--- /dev/null
+++ b/libstats/push_compat/tests/StatsEventCompat_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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 "include/StatsEventCompat.h"
+#include <android-base/properties.h>
+#include <android/api-level.h>
+#include <gtest/gtest.h>
+
+using android::base::GetProperty;
+
+/* Checking ro.build.version.release is fragile, as the release field is
+ * an opaque string without structural guarantees. However, testing confirms
+ * that on Q devices, the property is "10," and on R, it is "R." Until
+ * android_get_device_api_level() is updated, this is the only solution.
+ *
+ *
+ * TODO(b/146019024): migrate to android_get_device_api_level()
+ */
+const static bool mPlatformAtLeastR = GetProperty("ro.build.version.release", "") == "R" ||
+ android_get_device_api_level() > __ANDROID_API_Q__;
+
+TEST(StatsEventCompatTest, TestDynamicLoading) {
+ StatsEventCompat event;
+ EXPECT_EQ(mPlatformAtLeastR, event.usesNewSchema());
+}
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
new file mode 100644
index 0000000..94c405d
--- /dev/null
+++ b/libstats/socket/Android.bp
@@ -0,0 +1,69 @@
+//
+// 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.
+//
+
+// =========================================================================
+// Native library to write stats log to statsd socket on Android R and later
+// =========================================================================
+cc_library {
+ name: "libstatssocket",
+ srcs: [
+ "stats_buffer_writer.c",
+ "stats_event.c",
+ // TODO(b/145573568): Remove stats_event_list once stats_event
+ // migration is complete.
+ "stats_event_list.c",
+ "statsd_writer.c",
+ ],
+ host_supported: true,
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-DLIBLOG_LOG_TAG=1006",
+ "-DWRITE_TO_STATSD=1",
+ "-DWRITE_TO_LOGD=0",
+ ],
+ export_include_dirs: ["include"],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ ],
+}
+
+cc_library_headers {
+ name: "libstatssocket_headers",
+ export_include_dirs: ["include"],
+ host_supported: true,
+}
+
+cc_benchmark {
+ name: "libstatssocket_benchmark",
+ srcs: [
+ "benchmark/main.cpp",
+ "benchmark/stats_event_benchmark.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ static_libs: [
+ "libstatssocket",
+ ],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ "libgtest_prod",
+ ],
+}
diff --git a/libstats/socket/benchmark/main.cpp b/libstats/socket/benchmark/main.cpp
new file mode 100644
index 0000000..5ebdf6e
--- /dev/null
+++ b/libstats/socket/benchmark/main.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 <benchmark/benchmark.h>
+
+BENCHMARK_MAIN();
diff --git a/libstats/socket/benchmark/stats_event_benchmark.cpp b/libstats/socket/benchmark/stats_event_benchmark.cpp
new file mode 100644
index 0000000..b487c4d
--- /dev/null
+++ b/libstats/socket/benchmark/stats_event_benchmark.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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 "benchmark/benchmark.h"
+#include "stats_event.h"
+
+static struct stats_event* constructStatsEvent() {
+ struct stats_event* event = stats_event_obtain();
+ stats_event_set_atom_id(event, 100);
+
+ // randomly sample atom size
+ for (int i = 0; i < rand() % 800; i++) {
+ stats_event_write_int32(event, i);
+ }
+
+ return event;
+}
+
+static void BM_stats_event_truncate_buffer(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ struct stats_event* event = constructStatsEvent();
+ stats_event_build(event);
+ stats_event_write(event);
+ stats_event_release(event);
+ }
+}
+
+BENCHMARK(BM_stats_event_truncate_buffer);
+
+static void BM_stats_event_full_buffer(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ struct stats_event* event = constructStatsEvent();
+ stats_event_truncate_buffer(event, false);
+ stats_event_build(event);
+ stats_event_write(event);
+ stats_event_release(event);
+ }
+}
+
+BENCHMARK(BM_stats_event_full_buffer);
diff --git a/libstats/socket/include/stats_buffer_writer.h b/libstats/socket/include/stats_buffer_writer.h
new file mode 100644
index 0000000..de4a5e2
--- /dev/null
+++ b/libstats/socket/include/stats_buffer_writer.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __CPLUSPLUS
+void stats_log_close();
+int write_buffer_to_statsd(void* buffer, size_t size, uint32_t atomId);
+#ifdef __cplusplus
+}
+#endif // __CPLUSPLUS
diff --git a/libstats/socket/include/stats_event.h b/libstats/socket/include/stats_event.h
new file mode 100644
index 0000000..080e957
--- /dev/null
+++ b/libstats/socket/include/stats_event.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifndef ANDROID_STATS_LOG_STATS_EVENT_H
+#define ANDROID_STATS_LOG_STATS_EVENT_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/*
+ * Functionality to build and store the buffer sent over the statsd socket.
+ * This code defines and encapsulates the socket protocol.
+ *
+ * Usage:
+ * struct stats_event* event = stats_event_obtain();
+ *
+ * stats_event_set_atom_id(event, atomId);
+ * stats_event_write_int32(event, 24);
+ * stats_event_add_bool_annotation(event, 1, true); // annotations apply to the previous field
+ * stats_event_add_int32_annotation(event, 2, 128);
+ * stats_event_write_float(event, 2.0);
+ *
+ * stats_event_build(event);
+ * stats_event_write(event);
+ * stats_event_release(event);
+ *
+ * Notes:
+ * (a) write_<type>() and add_<type>_annotation() should be called in the order that fields
+ * and annotations are defined in the atom.
+ * (b) set_atom_id() can be called anytime before stats_event_write().
+ * (c) add_<type>_annotation() calls apply to the previous field.
+ * (d) If errors occur, stats_event_write() will write a bitmask of the errors to the socket.
+ * (e) All strings should be encoded using UTF8.
+ */
+
+/* ERRORS */
+#define ERROR_NO_TIMESTAMP 0x1
+#define ERROR_NO_ATOM_ID 0x2
+#define ERROR_OVERFLOW 0x4
+#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
+#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
+#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
+#define ERROR_INVALID_ANNOTATION_ID 0x40
+#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
+#define ERROR_TOO_MANY_ANNOTATIONS 0x100
+#define ERROR_TOO_MANY_FIELDS 0x200
+#define ERROR_INVALID_VALUE_TYPE 0x400
+#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
+
+/* TYPE IDS */
+#define INT32_TYPE 0x00
+#define INT64_TYPE 0x01
+#define STRING_TYPE 0x02
+#define LIST_TYPE 0x03
+#define FLOAT_TYPE 0x04
+#define BOOL_TYPE 0x05
+#define BYTE_ARRAY_TYPE 0x06
+#define OBJECT_TYPE 0x07
+#define KEY_VALUE_PAIRS_TYPE 0x08
+#define ATTRIBUTION_CHAIN_TYPE 0x09
+#define ERROR_TYPE 0x0F
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __CPLUSPLUS
+
+struct stats_event;
+
+/* SYSTEM API */
+struct stats_event* stats_event_obtain();
+// The build function can be called multiple times without error. If the event
+// has been built before, this function is a no-op.
+void stats_event_build(struct stats_event* event);
+int stats_event_write(struct stats_event* event);
+void stats_event_release(struct stats_event* event);
+
+void stats_event_set_atom_id(struct stats_event* event, uint32_t atomId);
+
+void stats_event_write_int32(struct stats_event* event, int32_t value);
+void stats_event_write_int64(struct stats_event* event, int64_t value);
+void stats_event_write_float(struct stats_event* event, float value);
+void stats_event_write_bool(struct stats_event* event, bool value);
+
+void stats_event_write_byte_array(struct stats_event* event, const uint8_t* buf, size_t numBytes);
+
+// Buf must be null-terminated.
+void stats_event_write_string8(struct stats_event* event, const char* value);
+
+// Tags must be null-terminated.
+void stats_event_write_attribution_chain(struct stats_event* event, const uint32_t* uids,
+ const char* const* tags, uint8_t numNodes);
+
+/* key_value_pair struct can be constructed as follows:
+ * struct key_value_pair pair = {.key = key, .valueType = STRING_TYPE,
+ * .stringValue = buf};
+ */
+struct key_value_pair {
+ int32_t key;
+ uint8_t valueType; // expected to be INT32_TYPE, INT64_TYPE, FLOAT_TYPE, or STRING_TYPE
+ union {
+ int32_t int32Value;
+ int64_t int64Value;
+ float floatValue;
+ const char* stringValue; // must be null terminated
+ };
+};
+
+void stats_event_write_key_value_pairs(struct stats_event* event, struct key_value_pair* pairs,
+ uint8_t numPairs);
+
+void stats_event_add_bool_annotation(struct stats_event* event, uint8_t annotationId, bool value);
+void stats_event_add_int32_annotation(struct stats_event* event, uint8_t annotationId,
+ int32_t value);
+
+uint32_t stats_event_get_atom_id(struct stats_event* event);
+// Size is an output parameter.
+uint8_t* stats_event_get_buffer(struct stats_event* event, size_t* size);
+uint32_t stats_event_get_errors(struct stats_event* event);
+
+// This table is used by StatsEventCompat to access the stats_event API.
+struct stats_event_api_table {
+ struct stats_event* (*obtain)(void);
+ void (*build)(struct stats_event*);
+ int (*write)(struct stats_event*);
+ void (*release)(struct stats_event*);
+ void (*set_atom_id)(struct stats_event*, uint32_t);
+ void (*write_int32)(struct stats_event*, int32_t);
+ void (*write_int64)(struct stats_event*, int64_t);
+ void (*write_float)(struct stats_event*, float);
+ void (*write_bool)(struct stats_event*, bool);
+ void (*write_byte_array)(struct stats_event*, const uint8_t*, size_t);
+ void (*write_string8)(struct stats_event*, const char*);
+ void (*write_attribution_chain)(struct stats_event*, const uint32_t*, const char* const*,
+ uint8_t);
+ void (*write_key_value_pairs)(struct stats_event*, struct key_value_pair*, uint8_t);
+ void (*add_bool_annotation)(struct stats_event*, uint8_t, bool);
+ void (*add_int32_annotation)(struct stats_event*, uint8_t, int32_t);
+ uint32_t (*get_atom_id)(struct stats_event*);
+ uint8_t* (*get_buffer)(struct stats_event*, size_t*);
+ uint32_t (*get_errors)(struct stats_event*);
+};
+
+// exposed for benchmarking only
+void stats_event_truncate_buffer(struct stats_event* event, bool truncate);
+
+#ifdef __cplusplus
+}
+#endif // __CPLUSPLUS
+
+#endif // ANDROID_STATS_LOG_STATS_EVENT_H
diff --git a/libstats/socket/include/stats_event_list.h b/libstats/socket/include/stats_event_list.h
new file mode 100644
index 0000000..7a26536
--- /dev/null
+++ b/libstats/socket/include/stats_event_list.h
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <log/log_event_list.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void reset_log_context(android_log_context ctx);
+int write_to_logger(android_log_context context, log_id_t id);
+void note_log_drop(int error, int atomId);
+void stats_log_close();
+int android_log_write_char_array(android_log_context ctx, const char* value, size_t len);
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __cplusplus
+/**
+ * A copy of android_log_event_list class.
+ *
+ * android_log_event_list is going to be deprecated soon, so copy it here to
+ * avoid creating dependency on upstream code. TODO(b/78304629): Rewrite this
+ * code.
+ */
+class stats_event_list {
+ private:
+ android_log_context ctx;
+ int ret;
+
+ stats_event_list(const stats_event_list&) = delete;
+ void operator=(const stats_event_list&) = delete;
+
+ public:
+ explicit stats_event_list(int tag) : ret(0) {
+ ctx = create_android_logger(static_cast<uint32_t>(tag));
+ }
+ ~stats_event_list() { android_log_destroy(&ctx); }
+
+ int close() {
+ int retval = android_log_destroy(&ctx);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return retval;
+ }
+
+ /* To allow above C calls to use this class as parameter */
+ operator android_log_context() const { return ctx; }
+
+ /* return errors or transmit status */
+ int status() const { return ret; }
+
+ int begin() {
+ int retval = android_log_write_list_begin(ctx);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret;
+ }
+ int end() {
+ int retval = android_log_write_list_end(ctx);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret;
+ }
+
+ stats_event_list& operator<<(int32_t value) {
+ int retval = android_log_write_int32(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ stats_event_list& operator<<(uint32_t value) {
+ int retval = android_log_write_int32(ctx, static_cast<int32_t>(value));
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ stats_event_list& operator<<(bool value) {
+ int retval = android_log_write_int32(ctx, value ? 1 : 0);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ stats_event_list& operator<<(int64_t value) {
+ int retval = android_log_write_int64(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ stats_event_list& operator<<(uint64_t value) {
+ int retval = android_log_write_int64(ctx, static_cast<int64_t>(value));
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ stats_event_list& operator<<(const char* value) {
+ int retval = android_log_write_string8(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ stats_event_list& operator<<(const std::string& value) {
+ int retval = android_log_write_string8_len(ctx, value.data(), value.length());
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ stats_event_list& operator<<(float value) {
+ int retval = android_log_write_float32(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return *this;
+ }
+
+ int write(log_id_t id = LOG_ID_EVENTS) {
+ /* facilitate -EBUSY retry */
+ if ((ret == -EBUSY) || (ret > 0)) {
+ ret = 0;
+ }
+ int retval = write_to_logger(ctx, id);
+ /* existing errors trump transmission errors */
+ if (!ret) {
+ ret = retval;
+ }
+ return ret;
+ }
+
+ /*
+ * Append<Type> methods removes any integer promotion
+ * confusion, and adds access to string with length.
+ * Append methods are also added for all types for
+ * convenience.
+ */
+
+ bool AppendInt(int32_t value) {
+ int retval = android_log_write_int32(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret >= 0;
+ }
+
+ bool AppendLong(int64_t value) {
+ int retval = android_log_write_int64(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret >= 0;
+ }
+
+ bool AppendString(const char* value) {
+ int retval = android_log_write_string8(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret >= 0;
+ }
+
+ bool AppendString(const char* value, size_t len) {
+ int retval = android_log_write_string8_len(ctx, value, len);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret >= 0;
+ }
+
+ bool AppendString(const std::string& value) {
+ int retval = android_log_write_string8_len(ctx, value.data(), value.length());
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret;
+ }
+
+ bool Append(const std::string& value) {
+ int retval = android_log_write_string8_len(ctx, value.data(), value.length());
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret;
+ }
+
+ bool AppendFloat(float value) {
+ int retval = android_log_write_float32(ctx, value);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret >= 0;
+ }
+
+ template <typename Tvalue>
+ bool Append(Tvalue value) {
+ *this << value;
+ return ret >= 0;
+ }
+
+ bool Append(const char* value, size_t len) {
+ int retval = android_log_write_string8_len(ctx, value, len);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret >= 0;
+ }
+
+ bool AppendCharArray(const char* value, size_t len) {
+ int retval = android_log_write_char_array(ctx, value, len);
+ if (retval < 0) {
+ ret = retval;
+ }
+ return ret >= 0;
+ }
+};
+
+#endif
diff --git a/libstats/socket/stats_buffer_writer.c b/libstats/socket/stats_buffer_writer.c
new file mode 100644
index 0000000..c5c591d
--- /dev/null
+++ b/libstats/socket/stats_buffer_writer.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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 "include/stats_buffer_writer.h"
+#ifdef __ANDROID__
+#include <cutils/properties.h>
+#endif
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include "statsd_writer.h"
+
+static const uint32_t kStatsEventTag = 1937006964;
+
+extern struct android_log_transport_write statsdLoggerWrite;
+
+static int __write_to_statsd_init(struct iovec* vec, size_t nr);
+static int (*__write_to_statsd)(struct iovec* vec, size_t nr) = __write_to_statsd_init;
+
+void note_log_drop(int error, int atomId) {
+ statsdLoggerWrite.noteDrop(error, atomId);
+}
+
+void stats_log_close() {
+ statsd_writer_init_lock();
+ __write_to_statsd = __write_to_statsd_init;
+ if (statsdLoggerWrite.close) {
+ (*statsdLoggerWrite.close)();
+ }
+ statsd_writer_init_unlock();
+}
+
+int write_buffer_to_statsd(void* buffer, size_t size, uint32_t atomId) {
+ int ret = 1;
+
+#ifdef __ANDROID__
+ bool statsdEnabled = property_get_bool("ro.statsd.enable", true);
+#else
+ bool statsdEnabled = false;
+#endif
+
+ if (statsdEnabled) {
+ struct iovec vecs[2];
+ vecs[0].iov_base = (void*)&kStatsEventTag;
+ vecs[0].iov_len = sizeof(kStatsEventTag);
+ vecs[1].iov_base = buffer;
+ vecs[1].iov_len = size;
+
+ ret = __write_to_statsd(vecs, 2);
+
+ if (ret < 0) {
+ note_log_drop(ret, atomId);
+ }
+ }
+
+ return ret;
+}
+
+static int __write_to_stats_daemon(struct iovec* vec, size_t nr) {
+ int save_errno;
+ struct timespec ts;
+ size_t len, i;
+
+ for (len = i = 0; i < nr; ++i) {
+ len += vec[i].iov_len;
+ }
+ if (!len) {
+ return -EINVAL;
+ }
+
+ save_errno = errno;
+#if defined(__ANDROID__)
+ clock_gettime(CLOCK_REALTIME, &ts);
+#else
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+#endif
+
+ int ret = (int)(*statsdLoggerWrite.write)(&ts, vec, nr);
+ errno = save_errno;
+ return ret;
+}
+
+static int __write_to_statsd_initialize_locked() {
+ if (!statsdLoggerWrite.open || ((*statsdLoggerWrite.open)() < 0)) {
+ if (statsdLoggerWrite.close) {
+ (*statsdLoggerWrite.close)();
+ return -ENODEV;
+ }
+ }
+ return 1;
+}
+
+static int __write_to_statsd_init(struct iovec* vec, size_t nr) {
+ int ret, save_errno = errno;
+
+ statsd_writer_init_lock();
+
+ if (__write_to_statsd == __write_to_statsd_init) {
+ ret = __write_to_statsd_initialize_locked();
+ if (ret < 0) {
+ statsd_writer_init_unlock();
+ errno = save_errno;
+ return ret;
+ }
+
+ __write_to_statsd = __write_to_stats_daemon;
+ }
+
+ statsd_writer_init_unlock();
+
+ ret = __write_to_statsd(vec, nr);
+ errno = save_errno;
+ return ret;
+}
diff --git a/libstats/socket/stats_event.c b/libstats/socket/stats_event.c
new file mode 100644
index 0000000..15039c6
--- /dev/null
+++ b/libstats/socket/stats_event.c
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2019 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 "include/stats_event.h"
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "stats_buffer_writer.h"
+
+#define LOGGER_ENTRY_MAX_PAYLOAD 4068
+// Max payload size is 4 bytes less as 4 bytes are reserved for stats_eventTag.
+// See android_util_Stats_Log.cpp
+#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - 4)
+
+/* POSITIONS */
+#define POS_NUM_ELEMENTS 1
+#define POS_TIMESTAMP (POS_NUM_ELEMENTS + sizeof(uint8_t))
+#define POS_ATOM_ID (POS_TIMESTAMP + sizeof(uint8_t) + sizeof(uint64_t))
+#define POS_FIRST_FIELD (POS_ATOM_ID + sizeof(uint8_t) + sizeof(uint32_t))
+
+/* LIMITS */
+#define MAX_ANNOTATION_COUNT 15
+#define MAX_BYTE_VALUE 127 // parsing side requires that lengths fit in 7 bits
+
+// The stats_event struct holds the serialized encoding of an event
+// within a buf. Also includes other required fields.
+struct stats_event {
+ uint8_t* buf;
+ size_t lastFieldPos; // location of last field within the buf
+ size_t size; // number of valid bytes within buffer
+ uint32_t numElements;
+ uint32_t atomId;
+ uint32_t errors;
+ bool truncate;
+ bool built;
+};
+
+static int64_t get_elapsed_realtime_ns() {
+ struct timespec t;
+ t.tv_sec = t.tv_nsec = 0;
+ clock_gettime(CLOCK_BOOTTIME, &t);
+ return (int64_t)t.tv_sec * 1000000000LL + t.tv_nsec;
+}
+
+struct stats_event* stats_event_obtain() {
+ struct stats_event* event = malloc(sizeof(struct stats_event));
+ event->buf = (uint8_t*)calloc(MAX_EVENT_PAYLOAD, 1);
+ event->buf[0] = OBJECT_TYPE;
+ event->atomId = 0;
+ event->errors = 0;
+ event->truncate = true; // truncate for both pulled and pushed atoms
+ event->built = false;
+
+ // place the timestamp
+ uint64_t timestampNs = get_elapsed_realtime_ns();
+ event->buf[POS_TIMESTAMP] = INT64_TYPE;
+ memcpy(&event->buf[POS_TIMESTAMP + sizeof(uint8_t)], ×tampNs, sizeof(timestampNs));
+
+ event->numElements = 1;
+ event->lastFieldPos = 0; // 0 since we haven't written a field yet
+ event->size = POS_FIRST_FIELD;
+
+ return event;
+}
+
+void stats_event_release(struct stats_event* event) {
+ free(event->buf);
+ free(event);
+}
+
+void stats_event_set_atom_id(struct stats_event* event, uint32_t atomId) {
+ event->atomId = atomId;
+ event->buf[POS_ATOM_ID] = INT32_TYPE;
+ memcpy(&event->buf[POS_ATOM_ID + sizeof(uint8_t)], &atomId, sizeof(atomId));
+ event->numElements++;
+}
+
+// Side-effect: modifies event->errors if the buffer would overflow
+static bool overflows(struct stats_event* event, size_t size) {
+ if (event->size + size > MAX_EVENT_PAYLOAD) {
+ event->errors |= ERROR_OVERFLOW;
+ return true;
+ }
+ return false;
+}
+
+// Side-effect: all append functions increment event->size if there is
+// sufficient space within the buffer to place the value
+static void append_byte(struct stats_event* event, uint8_t value) {
+ if (!overflows(event, sizeof(value))) {
+ event->buf[event->size] = value;
+ event->size += sizeof(value);
+ }
+}
+
+static void append_bool(struct stats_event* event, bool value) {
+ append_byte(event, (uint8_t)value);
+}
+
+static void append_int32(struct stats_event* event, int32_t value) {
+ if (!overflows(event, sizeof(value))) {
+ memcpy(&event->buf[event->size], &value, sizeof(value));
+ event->size += sizeof(value);
+ }
+}
+
+static void append_int64(struct stats_event* event, int64_t value) {
+ if (!overflows(event, sizeof(value))) {
+ memcpy(&event->buf[event->size], &value, sizeof(value));
+ event->size += sizeof(value);
+ }
+}
+
+static void append_float(struct stats_event* event, float value) {
+ if (!overflows(event, sizeof(value))) {
+ memcpy(&event->buf[event->size], &value, sizeof(value));
+ event->size += sizeof(float);
+ }
+}
+
+static void append_byte_array(struct stats_event* event, const uint8_t* buf, size_t size) {
+ if (!overflows(event, size)) {
+ memcpy(&event->buf[event->size], buf, size);
+ event->size += size;
+ }
+}
+
+// Side-effect: modifies event->errors if buf is not properly null-terminated
+static void append_string(struct stats_event* event, const char* buf) {
+ size_t size = strnlen(buf, MAX_EVENT_PAYLOAD);
+ if (size == MAX_EVENT_PAYLOAD) {
+ event->errors |= ERROR_STRING_NOT_NULL_TERMINATED;
+ return;
+ }
+
+ append_int32(event, size);
+ append_byte_array(event, (uint8_t*)buf, size);
+}
+
+static void start_field(struct stats_event* event, uint8_t typeId) {
+ event->lastFieldPos = event->size;
+ append_byte(event, typeId);
+ event->numElements++;
+}
+
+void stats_event_write_int32(struct stats_event* event, int32_t value) {
+ if (event->errors) return;
+
+ start_field(event, INT32_TYPE);
+ append_int32(event, value);
+}
+
+void stats_event_write_int64(struct stats_event* event, int64_t value) {
+ if (event->errors) return;
+
+ start_field(event, INT64_TYPE);
+ append_int64(event, value);
+}
+
+void stats_event_write_float(struct stats_event* event, float value) {
+ if (event->errors) return;
+
+ start_field(event, FLOAT_TYPE);
+ append_float(event, value);
+}
+
+void stats_event_write_bool(struct stats_event* event, bool value) {
+ if (event->errors) return;
+
+ start_field(event, BOOL_TYPE);
+ append_bool(event, value);
+}
+
+void stats_event_write_byte_array(struct stats_event* event, const uint8_t* buf, size_t numBytes) {
+ if (event->errors) return;
+
+ start_field(event, BYTE_ARRAY_TYPE);
+ append_int32(event, numBytes);
+ append_byte_array(event, buf, numBytes);
+}
+
+// Value is assumed to be encoded using UTF8
+void stats_event_write_string8(struct stats_event* event, const char* value) {
+ if (event->errors) return;
+
+ start_field(event, STRING_TYPE);
+ append_string(event, value);
+}
+
+// Tags are assumed to be encoded using UTF8
+void stats_event_write_attribution_chain(struct stats_event* event, const uint32_t* uids,
+ const char* const* tags, uint8_t numNodes) {
+ if (numNodes > MAX_BYTE_VALUE) event->errors |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
+ if (event->errors) return;
+
+ start_field(event, ATTRIBUTION_CHAIN_TYPE);
+ append_byte(event, numNodes);
+
+ for (uint8_t i = 0; i < numNodes; i++) {
+ append_int32(event, uids[i]);
+ append_string(event, tags[i]);
+ }
+}
+
+void stats_event_write_key_value_pairs(struct stats_event* event, struct key_value_pair* pairs,
+ uint8_t numPairs) {
+ if (numPairs > MAX_BYTE_VALUE) event->errors |= ERROR_TOO_MANY_KEY_VALUE_PAIRS;
+ if (event->errors) return;
+
+ start_field(event, KEY_VALUE_PAIRS_TYPE);
+ append_byte(event, numPairs);
+
+ for (uint8_t i = 0; i < numPairs; i++) {
+ append_int32(event, pairs[i].key);
+ append_byte(event, pairs[i].valueType);
+ switch (pairs[i].valueType) {
+ case INT32_TYPE:
+ append_int32(event, pairs[i].int32Value);
+ break;
+ case INT64_TYPE:
+ append_int64(event, pairs[i].int64Value);
+ break;
+ case FLOAT_TYPE:
+ append_float(event, pairs[i].floatValue);
+ break;
+ case STRING_TYPE:
+ append_string(event, pairs[i].stringValue);
+ break;
+ default:
+ event->errors |= ERROR_INVALID_VALUE_TYPE;
+ return;
+ }
+ }
+}
+
+// Side-effect: modifies event->errors if field has too many annotations
+static void increment_annotation_count(struct stats_event* event) {
+ uint8_t fieldType = event->buf[event->lastFieldPos] & 0x0F;
+ uint32_t oldAnnotationCount = (event->buf[event->lastFieldPos] & 0xF0) >> 4;
+ uint32_t newAnnotationCount = oldAnnotationCount + 1;
+
+ if (newAnnotationCount > MAX_ANNOTATION_COUNT) {
+ event->errors |= ERROR_TOO_MANY_ANNOTATIONS;
+ return;
+ }
+
+ event->buf[event->lastFieldPos] = (((uint8_t)newAnnotationCount << 4) & 0xF0) | fieldType;
+}
+
+void stats_event_add_bool_annotation(struct stats_event* event, uint8_t annotationId, bool value) {
+ if (event->lastFieldPos == 0) event->errors |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+ if (annotationId > MAX_BYTE_VALUE) event->errors |= ERROR_ANNOTATION_ID_TOO_LARGE;
+ if (event->errors) return;
+
+ append_byte(event, annotationId);
+ append_byte(event, BOOL_TYPE);
+ append_bool(event, value);
+ increment_annotation_count(event);
+}
+
+void stats_event_add_int32_annotation(struct stats_event* event, uint8_t annotationId,
+ int32_t value) {
+ if (event->lastFieldPos == 0) event->errors |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
+ if (annotationId > MAX_BYTE_VALUE) event->errors |= ERROR_ANNOTATION_ID_TOO_LARGE;
+ if (event->errors) return;
+
+ append_byte(event, annotationId);
+ append_byte(event, INT32_TYPE);
+ append_int32(event, value);
+ increment_annotation_count(event);
+}
+
+uint32_t stats_event_get_atom_id(struct stats_event* event) {
+ return event->atomId;
+}
+
+uint8_t* stats_event_get_buffer(struct stats_event* event, size_t* size) {
+ if (size) *size = event->size;
+ return event->buf;
+}
+
+uint32_t stats_event_get_errors(struct stats_event* event) {
+ return event->errors;
+}
+
+void stats_event_truncate_buffer(struct stats_event* event, bool truncate) {
+ event->truncate = truncate;
+}
+
+void stats_event_build(struct stats_event* event) {
+ if (event->built) return;
+
+ if (event->atomId == 0) event->errors |= ERROR_NO_ATOM_ID;
+
+ if (event->numElements > MAX_BYTE_VALUE) {
+ event->errors |= ERROR_TOO_MANY_FIELDS;
+ } else {
+ event->buf[POS_NUM_ELEMENTS] = event->numElements;
+ }
+
+ // If there are errors, rewrite buffer.
+ if (event->errors) {
+ event->buf[POS_NUM_ELEMENTS] = 3;
+ event->buf[POS_FIRST_FIELD] = ERROR_TYPE;
+ memcpy(&event->buf[POS_FIRST_FIELD + sizeof(uint8_t)], &event->errors,
+ sizeof(event->errors));
+ event->size = POS_FIRST_FIELD + sizeof(uint8_t) + sizeof(uint32_t);
+ }
+
+ // Truncate the buffer to the appropriate length in order to limit our
+ // memory usage.
+ if (event->truncate) event->buf = (uint8_t*)realloc(event->buf, event->size);
+
+ event->built = true;
+}
+
+int stats_event_write(struct stats_event* event) {
+ stats_event_build(event);
+ return write_buffer_to_statsd(&event->buf, event->size, event->atomId);
+}
+
+struct stats_event_api_table table = {
+ stats_event_obtain,
+ stats_event_build,
+ stats_event_write,
+ stats_event_release,
+ stats_event_set_atom_id,
+ stats_event_write_int32,
+ stats_event_write_int64,
+ stats_event_write_float,
+ stats_event_write_bool,
+ stats_event_write_byte_array,
+ stats_event_write_string8,
+ stats_event_write_attribution_chain,
+ stats_event_write_key_value_pairs,
+ stats_event_add_bool_annotation,
+ stats_event_add_int32_annotation,
+ stats_event_get_atom_id,
+ stats_event_get_buffer,
+ stats_event_get_errors,
+};
diff --git a/libstats/socket/stats_event_list.c b/libstats/socket/stats_event_list.c
new file mode 100644
index 0000000..661a223
--- /dev/null
+++ b/libstats/socket/stats_event_list.c
@@ -0,0 +1,155 @@
+/*
+ * 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 "include/stats_event_list.h"
+
+#include <string.h>
+#include <sys/time.h>
+#include "stats_buffer_writer.h"
+
+#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
+
+typedef struct {
+ uint32_t tag;
+ unsigned pos; /* Read/write position into buffer */
+ unsigned count[ANDROID_MAX_LIST_NEST_DEPTH + 1]; /* Number of elements */
+ unsigned list[ANDROID_MAX_LIST_NEST_DEPTH + 1]; /* pos for list counter */
+ unsigned list_nest_depth;
+ unsigned len; /* Length or raw buffer. */
+ bool overflow;
+ bool list_stop; /* next call decrement list_nest_depth and issue a stop */
+ enum {
+ kAndroidLoggerRead = 1,
+ kAndroidLoggerWrite = 2,
+ } read_write_flag;
+ uint8_t storage[LOGGER_ENTRY_MAX_PAYLOAD];
+} android_log_context_internal;
+
+// Similar to create_android_logger(), but instead of allocation a new buffer,
+// this function resets the buffer for resuse.
+void reset_log_context(android_log_context ctx) {
+ if (!ctx) {
+ return;
+ }
+ android_log_context_internal* context = (android_log_context_internal*)(ctx);
+ uint32_t tag = context->tag;
+ memset(context, 0, sizeof(android_log_context_internal));
+
+ context->tag = tag;
+ context->read_write_flag = kAndroidLoggerWrite;
+ size_t needed = sizeof(uint8_t) + sizeof(uint8_t);
+ if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+ context->overflow = true;
+ }
+ /* Everything is a list */
+ context->storage[context->pos + 0] = EVENT_TYPE_LIST;
+ context->list[0] = context->pos + 1;
+ context->pos += needed;
+}
+
+int stats_write_list(android_log_context ctx) {
+ android_log_context_internal* context;
+ const char* msg;
+ ssize_t len;
+
+ context = (android_log_context_internal*)(ctx);
+ if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+ return -EBADF;
+ }
+
+ if (context->list_nest_depth) {
+ return -EIO;
+ }
+
+ /* NB: if there was overflow, then log is truncated. Nothing reported */
+ context->storage[1] = context->count[0];
+ len = context->len = context->pos;
+ msg = (const char*)context->storage;
+ /* it's not a list */
+ if (context->count[0] <= 1) {
+ len -= sizeof(uint8_t) + sizeof(uint8_t);
+ if (len < 0) {
+ len = 0;
+ }
+ msg += sizeof(uint8_t) + sizeof(uint8_t);
+ }
+
+ return write_buffer_to_statsd((void*)msg, len, 0);
+}
+
+int write_to_logger(android_log_context ctx, log_id_t id) {
+ int retValue = 0;
+
+ if (WRITE_TO_LOGD) {
+ retValue = android_log_write_list(ctx, id);
+ }
+
+ if (WRITE_TO_STATSD) {
+ // log_event_list's cast operator is overloaded.
+ int ret = stats_write_list(ctx);
+ // In debugging phase, we may write to both logd and statsd. Prefer to
+ // return statsd socket write error code here.
+ if (ret < 0) {
+ retValue = ret;
+ }
+ }
+
+ return retValue;
+}
+
+static inline void copy4LE(uint8_t* buf, uint32_t val) {
+ buf[0] = val & 0xFF;
+ buf[1] = (val >> 8) & 0xFF;
+ buf[2] = (val >> 16) & 0xFF;
+ buf[3] = (val >> 24) & 0xFF;
+}
+
+// Note: this function differs from android_log_write_string8_len in that the length passed in
+// should be treated as actual length and not max length.
+int android_log_write_char_array(android_log_context ctx, const char* value, size_t actual_len) {
+ size_t needed;
+ ssize_t len = actual_len;
+ android_log_context_internal* context;
+
+ context = (android_log_context_internal*)ctx;
+ if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+ return -EBADF;
+ }
+ if (context->overflow) {
+ return -EIO;
+ }
+ if (!value) {
+ value = "";
+ len = 0;
+ }
+ needed = sizeof(uint8_t) + sizeof(int32_t) + len;
+ if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+ /* Truncate string for delivery */
+ len = MAX_EVENT_PAYLOAD - context->pos - 1 - sizeof(int32_t);
+ if (len <= 0) {
+ context->overflow = true;
+ return -EIO;
+ }
+ }
+ context->count[context->list_nest_depth]++;
+ context->storage[context->pos + 0] = EVENT_TYPE_STRING;
+ copy4LE(&context->storage[context->pos + 1], len);
+ if (len) {
+ memcpy(&context->storage[context->pos + 5], value, len);
+ }
+ context->pos += needed;
+ return len;
+}
diff --git a/libstats/statsd_writer.c b/libstats/socket/statsd_writer.c
similarity index 98%
copy from libstats/statsd_writer.c
copy to libstats/socket/statsd_writer.c
index 073b67f..04d3b46 100644
--- a/libstats/statsd_writer.c
+++ b/libstats/socket/statsd_writer.c
@@ -101,7 +101,7 @@
strcpy(un.sun_path, "/dev/socket/statsdw");
if (TEMP_FAILURE_RETRY(
- connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) {
+ connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) {
ret = -errno;
switch (ret) {
case -ENOTCONN:
diff --git a/libstats/statsd_writer.h b/libstats/socket/statsd_writer.h
similarity index 100%
rename from libstats/statsd_writer.h
rename to libstats/socket/statsd_writer.h
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 8fe7854..2351afa 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -24,7 +24,6 @@
#include <linux/if_link.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink_log.h>
-#include <linux/netfilter_ipv4/ipt_ULOG.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
@@ -39,6 +38,23 @@
const int LOCAL_QLOG_NL_EVENT = 112;
const int LOCAL_NFLOG_PACKET = NFNL_SUBSYS_ULOG << 8 | NFULNL_MSG_PACKET;
+/* From deprecated ipt_ULOG.h to parse QLOG_NL_EVENT. */
+#define ULOG_MAC_LEN 80
+#define ULOG_PREFIX_LEN 32
+typedef struct ulog_packet_msg {
+ unsigned long mark;
+ long timestamp_sec;
+ long timestamp_usec;
+ unsigned int hook;
+ char indev_name[IFNAMSIZ];
+ char outdev_name[IFNAMSIZ];
+ size_t data_len;
+ char prefix[ULOG_PREFIX_LEN];
+ unsigned char mac_len;
+ unsigned char mac[ULOG_MAC_LEN];
+ unsigned char payload[0];
+} ulog_packet_msg_t;
+
#include <android-base/parseint.h>
#include <log/log.h>
#include <sysutils/NetlinkEvent.h>
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index 7676289..341275d 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -78,10 +78,31 @@
CrcGenerateTable();
Crc64GenerateTable();
- std::vector<uint8_t> src(gnu_debugdata_size_);
- if (!memory_->ReadFully(gnu_debugdata_offset_, src.data(), gnu_debugdata_size_)) {
- gnu_debugdata_offset_ = 0;
- gnu_debugdata_size_ = static_cast<uint64_t>(-1);
+ // Verify the request is not larger than the max size_t value.
+ if (gnu_debugdata_size_ > SIZE_MAX) {
+ return nullptr;
+ }
+ size_t initial_buffer_size;
+ if (__builtin_mul_overflow(5, gnu_debugdata_size_, &initial_buffer_size)) {
+ return nullptr;
+ }
+
+ size_t buffer_increment;
+ if (__builtin_mul_overflow(2, gnu_debugdata_size_, &buffer_increment)) {
+ return nullptr;
+ }
+
+ std::unique_ptr<uint8_t[]> src(new (std::nothrow) uint8_t[gnu_debugdata_size_]);
+ if (src.get() == nullptr) {
+ return nullptr;
+ }
+
+ std::unique_ptr<MemoryBuffer> dst(new MemoryBuffer);
+ if (!dst->Resize(initial_buffer_size)) {
+ return nullptr;
+ }
+
+ if (!memory_->ReadFully(gnu_debugdata_offset_, src.get(), gnu_debugdata_size_)) {
return nullptr;
}
@@ -89,21 +110,23 @@
CXzUnpacker state;
alloc.Alloc = [](ISzAllocPtr, size_t size) { return malloc(size); };
alloc.Free = [](ISzAllocPtr, void* ptr) { return free(ptr); };
-
XzUnpacker_Construct(&state, &alloc);
- std::unique_ptr<MemoryBuffer> dst(new MemoryBuffer);
int return_val;
size_t src_offset = 0;
size_t dst_offset = 0;
ECoderStatus status;
- dst->Resize(5 * gnu_debugdata_size_);
do {
- size_t src_remaining = src.size() - src_offset;
+ size_t src_remaining = gnu_debugdata_size_ - src_offset;
size_t dst_remaining = dst->Size() - dst_offset;
- if (dst_remaining < 2 * gnu_debugdata_size_) {
- dst->Resize(dst->Size() + 2 * gnu_debugdata_size_);
- dst_remaining += 2 * gnu_debugdata_size_;
+ if (dst_remaining < buffer_increment) {
+ size_t new_size;
+ if (__builtin_add_overflow(dst->Size(), buffer_increment, &new_size) ||
+ !dst->Resize(new_size)) {
+ XzUnpacker_Free(&state);
+ return nullptr;
+ }
+ dst_remaining += buffer_increment;
}
return_val = XzUnpacker_Code(&state, dst->GetPtr(dst_offset), &dst_remaining, &src[src_offset],
&src_remaining, true, CODER_FINISH_ANY, &status);
@@ -112,13 +135,13 @@
} while (return_val == SZ_OK && status == CODER_STATUS_NOT_FINISHED);
XzUnpacker_Free(&state);
if (return_val != SZ_OK || !XzUnpacker_IsStreamWasFinished(&state)) {
- gnu_debugdata_offset_ = 0;
- gnu_debugdata_size_ = static_cast<uint64_t>(-1);
return nullptr;
}
// Shrink back down to the exact size.
- dst->Resize(dst_offset);
+ if (!dst->Resize(dst_offset)) {
+ return nullptr;
+ }
return dst.release();
}
diff --git a/libunwindstack/Memory.cpp b/libunwindstack/Memory.cpp
index a66cd5b..8de3d98 100644
--- a/libunwindstack/Memory.cpp
+++ b/libunwindstack/Memory.cpp
@@ -206,12 +206,12 @@
}
size_t MemoryBuffer::Read(uint64_t addr, void* dst, size_t size) {
- if (addr >= raw_.size()) {
+ if (addr >= size_) {
return 0;
}
- size_t bytes_left = raw_.size() - static_cast<size_t>(addr);
- const unsigned char* actual_base = static_cast<const unsigned char*>(raw_.data()) + addr;
+ size_t bytes_left = size_ - static_cast<size_t>(addr);
+ const unsigned char* actual_base = static_cast<const unsigned char*>(raw_) + addr;
size_t actual_len = std::min(bytes_left, size);
memcpy(dst, actual_base, actual_len);
@@ -219,7 +219,7 @@
}
uint8_t* MemoryBuffer::GetPtr(size_t offset) {
- if (offset < raw_.size()) {
+ if (offset < size_) {
return &raw_[offset];
}
return nullptr;
diff --git a/libunwindstack/MemoryBuffer.h b/libunwindstack/MemoryBuffer.h
index 3fe4bbb..a91e59f 100644
--- a/libunwindstack/MemoryBuffer.h
+++ b/libunwindstack/MemoryBuffer.h
@@ -29,18 +29,27 @@
class MemoryBuffer : public Memory {
public:
MemoryBuffer() = default;
- virtual ~MemoryBuffer() = default;
+ virtual ~MemoryBuffer() { free(raw_); }
size_t Read(uint64_t addr, void* dst, size_t size) override;
uint8_t* GetPtr(size_t offset);
- void Resize(size_t size) { raw_.resize(size); }
+ bool Resize(size_t size) {
+ raw_ = reinterpret_cast<uint8_t*>(realloc(raw_, size));
+ if (raw_ == nullptr) {
+ size_ = 0;
+ return false;
+ }
+ size_ = size;
+ return true;
+ }
- uint64_t Size() { return raw_.size(); }
+ uint64_t Size() { return size_; }
private:
- std::vector<uint8_t> raw_;
+ uint8_t* raw_ = nullptr;
+ size_t size_ = 0;
};
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfCfaLogTest.cpp b/libunwindstack/tests/DwarfCfaLogTest.cpp
index 9dd0cdd..def4088 100644
--- a/libunwindstack/tests/DwarfCfaLogTest.cpp
+++ b/libunwindstack/tests/DwarfCfaLogTest.cpp
@@ -774,6 +774,6 @@
cfa_gnu_negative_offset_extended, cfa_register_override);
typedef ::testing::Types<uint32_t, uint64_t> DwarfCfaLogTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfCfaLogTest, DwarfCfaLogTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfCfaLogTest, DwarfCfaLogTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfCfaTest.cpp b/libunwindstack/tests/DwarfCfaTest.cpp
index dd71490..9c6ab05 100644
--- a/libunwindstack/tests/DwarfCfaTest.cpp
+++ b/libunwindstack/tests/DwarfCfaTest.cpp
@@ -963,6 +963,6 @@
cfa_register_override);
typedef ::testing::Types<uint32_t, uint64_t> DwarfCfaTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfCfaTest, DwarfCfaTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfCfaTest, DwarfCfaTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfDebugFrameTest.cpp b/libunwindstack/tests/DwarfDebugFrameTest.cpp
index 2b36f17..b6f574a 100644
--- a/libunwindstack/tests/DwarfDebugFrameTest.cpp
+++ b/libunwindstack/tests/DwarfDebugFrameTest.cpp
@@ -825,6 +825,6 @@
GetFdeFromOffset64_lsda_address, GetFdeFromPc_interleaved);
typedef ::testing::Types<uint32_t, uint64_t> DwarfDebugFrameTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfDebugFrameTest, DwarfDebugFrameTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfDebugFrameTest, DwarfDebugFrameTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfEhFrameTest.cpp b/libunwindstack/tests/DwarfEhFrameTest.cpp
index 4792fb5..46a25a4 100644
--- a/libunwindstack/tests/DwarfEhFrameTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameTest.cpp
@@ -128,6 +128,6 @@
REGISTER_TYPED_TEST_SUITE_P(DwarfEhFrameTest, GetFdeCieFromOffset32, GetFdeCieFromOffset64);
typedef ::testing::Types<uint32_t, uint64_t> DwarfEhFrameTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfEhFrameTest, DwarfEhFrameTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfEhFrameTest, DwarfEhFrameTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
index 768a808..6aa3867 100644
--- a/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
+++ b/libunwindstack/tests/DwarfEhFrameWithHdrTest.cpp
@@ -552,6 +552,6 @@
GetCieFde32, GetCieFde64, GetFdeFromPc_fde_not_found);
typedef ::testing::Types<uint32_t, uint64_t> DwarfEhFrameWithHdrTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfEhFrameWithHdrTest, DwarfEhFrameWithHdrTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfEhFrameWithHdrTest, DwarfEhFrameWithHdrTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfOpLogTest.cpp b/libunwindstack/tests/DwarfOpLogTest.cpp
index f4ade5d..8dbf6e8 100644
--- a/libunwindstack/tests/DwarfOpLogTest.cpp
+++ b/libunwindstack/tests/DwarfOpLogTest.cpp
@@ -68,6 +68,6 @@
REGISTER_TYPED_TEST_SUITE_P(DwarfOpLogTest, multiple_ops);
typedef ::testing::Types<uint32_t, uint64_t> DwarfOpLogTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfOpLogTest, DwarfOpLogTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfOpLogTest, DwarfOpLogTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfOpTest.cpp b/libunwindstack/tests/DwarfOpTest.cpp
index 0898ec0..0e2d91a 100644
--- a/libunwindstack/tests/DwarfOpTest.cpp
+++ b/libunwindstack/tests/DwarfOpTest.cpp
@@ -1581,6 +1581,6 @@
is_dex_pc);
typedef ::testing::Types<uint32_t, uint64_t> DwarfOpTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfOpTest, DwarfOpTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfOpTest, DwarfOpTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/DwarfSectionImplTest.cpp b/libunwindstack/tests/DwarfSectionImplTest.cpp
index a9d6dad..cac59b7 100644
--- a/libunwindstack/tests/DwarfSectionImplTest.cpp
+++ b/libunwindstack/tests/DwarfSectionImplTest.cpp
@@ -583,6 +583,6 @@
GetCfaLocationInfo_cie_not_cached, GetCfaLocationInfo_cie_cached, Log);
typedef ::testing::Types<uint32_t, uint64_t> DwarfSectionImplTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, DwarfSectionImplTest, DwarfSectionImplTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfSectionImplTest, DwarfSectionImplTestTypes);
} // namespace unwindstack
diff --git a/libunwindstack/tests/ElfFake.h b/libunwindstack/tests/ElfFake.h
index c33908d..fc90dab 100644
--- a/libunwindstack/tests/ElfFake.h
+++ b/libunwindstack/tests/ElfFake.h
@@ -105,6 +105,9 @@
void FakeSetDynamicVaddrStart(uint64_t vaddr) { dynamic_vaddr_start_ = vaddr; }
void FakeSetDynamicVaddrEnd(uint64_t vaddr) { dynamic_vaddr_end_ = vaddr; }
+ void FakeSetGnuDebugdataOffset(uint64_t offset) { gnu_debugdata_offset_ = offset; }
+ void FakeSetGnuDebugdataSize(uint64_t size) { gnu_debugdata_size_ = size; }
+
private:
std::unordered_map<std::string, uint64_t> globals_;
std::string fake_build_id_;
diff --git a/libunwindstack/tests/ElfInterfaceTest.cpp b/libunwindstack/tests/ElfInterfaceTest.cpp
index ea27e3e..3cf90fe 100644
--- a/libunwindstack/tests/ElfInterfaceTest.cpp
+++ b/libunwindstack/tests/ElfInterfaceTest.cpp
@@ -1944,4 +1944,23 @@
CheckLoadBiasInFirstExecPhdr<Elf64_Ehdr, Elf64_Phdr, ElfInterface64>(0x5000, 0x1000, -0x4000);
}
+TEST_F(ElfInterfaceTest, huge_gnu_debugdata_size) {
+ ElfInterfaceFake interface(nullptr);
+
+ interface.FakeSetGnuDebugdataOffset(0x1000);
+ interface.FakeSetGnuDebugdataSize(0xffffffffffffffffUL);
+ ASSERT_TRUE(interface.CreateGnuDebugdataMemory() == nullptr);
+
+ interface.FakeSetGnuDebugdataSize(0x4000000000000UL);
+ ASSERT_TRUE(interface.CreateGnuDebugdataMemory() == nullptr);
+
+ // This should exceed the size_t value of the first allocation.
+#if defined(__LP64__)
+ interface.FakeSetGnuDebugdataSize(0x3333333333333334ULL);
+#else
+ interface.FakeSetGnuDebugdataSize(0x33333334);
+#endif
+ ASSERT_TRUE(interface.CreateGnuDebugdataMemory() == nullptr);
+}
+
} // namespace unwindstack
diff --git a/libunwindstack/tests/SymbolsTest.cpp b/libunwindstack/tests/SymbolsTest.cpp
index ae3c349..c58aeff 100644
--- a/libunwindstack/tests/SymbolsTest.cpp
+++ b/libunwindstack/tests/SymbolsTest.cpp
@@ -367,6 +367,6 @@
symtab_read_cached, get_global);
typedef ::testing::Types<Elf32_Sym, Elf64_Sym> SymbolsTestTypes;
-INSTANTIATE_TYPED_TEST_SUITE_P(, SymbolsTest, SymbolsTestTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, SymbolsTest, SymbolsTestTypes);
} // namespace unwindstack
diff --git a/libutils/include/utils/RefBase.h b/libutils/include/utils/RefBase.h
index 42c6efb..89f048d 100644
--- a/libutils/include/utils/RefBase.h
+++ b/libutils/include/utils/RefBase.h
@@ -455,6 +455,7 @@
};
#undef COMPARE_WEAK
+#undef COMPARE_WEAK_FUNCTIONAL
// ---------------------------------------------------------------------------
// No user serviceable parts below here.
diff --git a/libutils/include/utils/StrongPointer.h b/libutils/include/utils/StrongPointer.h
index 07dd3f1..100e507 100644
--- a/libutils/include/utils/StrongPointer.h
+++ b/libutils/include/utils/StrongPointer.h
@@ -134,7 +134,8 @@
void sp_report_race();
void sp_report_stack_pointer();
-#undef COMPARE
+#undef COMPARE_STRONG
+#undef COMPARE_STRONG_FUNCTIONAL
// ---------------------------------------------------------------------------
// No user serviceable parts below here.
diff --git a/libvndksupport/linker.cpp b/libvndksupport/linker.cpp
index cf0f618..30b9c2e 100644
--- a/libvndksupport/linker.cpp
+++ b/libvndksupport/linker.cpp
@@ -26,9 +26,7 @@
#include <initializer_list>
-__attribute__((weak)) extern "C" android_namespace_t* android_get_exported_namespace(const char*);
-__attribute__((weak)) extern "C" void* android_dlopen_ext(const char*, int,
- const android_dlextinfo*);
+extern "C" android_namespace_t* android_get_exported_namespace(const char*);
namespace {
@@ -42,10 +40,8 @@
static VendorNamespace get_vendor_namespace() {
static VendorNamespace result = ([] {
for (const char* name : {"sphal", "default"}) {
- if (android_get_exported_namespace != nullptr) {
- if (android_namespace_t* ns = android_get_exported_namespace(name)) {
- return VendorNamespace{ns, name};
- }
+ if (android_namespace_t* ns = android_get_exported_namespace(name)) {
+ return VendorNamespace{ns, name};
}
}
return VendorNamespace{};
@@ -59,10 +55,6 @@
if (getpid() == 1) {
return 0;
}
- if (android_get_exported_namespace == nullptr) {
- ALOGD("android_get_exported_namespace() not available. Assuming system process.");
- return 0;
- }
// In vendor process, 'vndk' namespace is not visible, whereas in system
// process, it is.
@@ -76,10 +68,7 @@
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = vendor_namespace.ptr,
};
- void* handle = nullptr;
- if (android_dlopen_ext != nullptr) {
- handle = android_dlopen_ext(name, flag, &dlextinfo);
- }
+ void* handle = android_dlopen_ext(name, flag, &dlextinfo);
if (!handle) {
ALOGE("Could not load %s from %s namespace: %s.", name, vendor_namespace.name,
dlerror());
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index e3bb2ab..1bbffaf 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -177,7 +177,7 @@
cc_binary {
name: "ziptool",
defaults: ["libziparchive_flags"],
- srcs: ["unzip.cpp"],
+ srcs: ["ziptool.cpp"],
shared_libs: [
"libbase",
"libziparchive",
@@ -198,3 +198,15 @@
host_supported: true,
corpus: ["testdata/*"],
}
+
+sh_test {
+ name: "ziptool-tests",
+ src: "run-ziptool-tests-on-android.sh",
+ filename: "run-ziptool-tests-on-android.sh",
+ test_suites: ["general-tests"],
+ host_supported: true,
+ device_supported: false,
+ test_config: "ziptool-tests.xml",
+ data: ["cli-tests/**/*"],
+ target_required: ["cli-test", "ziptool"],
+}
diff --git a/libziparchive/cli-tests/files/example.zip b/libziparchive/cli-tests/files/example.zip
new file mode 100644
index 0000000..c3292e9
--- /dev/null
+++ b/libziparchive/cli-tests/files/example.zip
Binary files differ
diff --git a/libziparchive/cli-tests/unzip.test b/libziparchive/cli-tests/unzip.test
new file mode 100755
index 0000000..6e5cbf2
--- /dev/null
+++ b/libziparchive/cli-tests/unzip.test
@@ -0,0 +1,148 @@
+# unzip tests.
+
+# Note: since "master key", Android uses libziparchive for all zip file
+# handling, and that scans the whole central directory immediately. Not only
+# lookups by name but also iteration is implemented using the resulting hash
+# table, meaning that any test that makes assumptions about iteration order
+# will fail on Android.
+
+name: unzip -l
+command: unzip -l $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/x.txt ]
+expected-stdout:
+ Archive: $FILES/example.zip
+ Length Date Time Name
+ --------- ---------- ----- ----
+ 1024 2017-06-04 08:45 d1/d2/x.txt
+ --------- -------
+ 1024 1 file
+---
+
+name: unzip -lq
+command: unzip -lq $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/x.txt ]
+expected-stdout:
+ Length Date Time Name
+ --------- ---------- ----- ----
+ 1024 2017-06-04 08:45 d1/d2/x.txt
+ --------- -------
+ 1024 1 file
+---
+
+name: unzip -lv
+command: unzip -lv $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/file ]
+expected-stdout:
+ Archive: $FILES/example.zip
+ Length Method Size Cmpr Date Time CRC-32 Name
+ -------- ------ ------- ---- ---------- ----- -------- ----
+ 1024 Defl:N 11 99% 2017-06-04 08:45 48d7f063 d1/d2/x.txt
+ -------- ------- --- -------
+ 1024 11 99% 1 file
+---
+
+name: unzip -v
+command: unzip -v $FILES/example.zip d1/d2/x.txt
+after: [ ! -f d1/d2/file ]
+expected-stdout:
+ Archive: $FILES/example.zip
+ Length Method Size Cmpr Date Time CRC-32 Name
+ -------- ------ ------- ---- ---------- ----- -------- ----
+ 1024 Defl:N 11 99% 2017-06-04 08:45 48d7f063 d1/d2/x.txt
+ -------- ------- --- -------
+ 1024 11 99% 1 file
+---
+
+name: unzip one file
+command: unzip -q $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+after: [ ! -f d1/d2/b.txt ]
+expected-stdout:
+ a
+---
+
+name: unzip all files
+command: unzip -q $FILES/example.zip
+after: [ -f d1/d2/a.txt ]
+after: [ -f d1/d2/b.txt ]
+after: [ -f d1/d2/c.txt ]
+after: [ -f d1/d2/empty.txt ]
+after: [ -f d1/d2/x.txt ]
+after: [ -d d1/d2/dir ]
+expected-stdout:
+---
+
+name: unzip -o
+before: mkdir -p d1/d2
+before: echo b > d1/d2/a.txt
+command: unzip -q -o $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+expected-stdout:
+ a
+---
+
+name: unzip -n
+before: mkdir -p d1/d2
+before: echo b > d1/d2/a.txt
+command: unzip -q -n $FILES/example.zip d1/d2/a.txt && cat d1/d2/a.txt
+expected-stdout:
+ b
+---
+
+# The reference implementation will create *one* level of missing directories,
+# so this succeeds.
+name: unzip -d shallow non-existent
+command: unzip -q -d will-be-created $FILES/example.zip d1/d2/a.txt
+after: [ -d will-be-created ]
+after: [ -f will-be-created/d1/d2/a.txt ]
+---
+
+# The reference implementation will *only* create one level of missing
+# directories, so this fails.
+name: unzip -d deep non-existent
+command: unzip -q -d oh-no/will-not-be-created $FILES/example.zip d1/d2/a.txt 2> stderr ; echo $? > status
+after: [ ! -d oh-no ]
+after: [ ! -d oh-no/will-not-be-created ]
+after: [ ! -f oh-no/will-not-be-created/d1/d2/a.txt ]
+after: grep -q "oh-no/will-not-be-created" stderr
+after: grep -q "No such file or directory" stderr
+# The reference implementation has *lots* of non-zero exit values, but we stick to 0 and 1.
+after: [ $(cat status) -gt 0 ]
+---
+
+name: unzip -d exists
+before: mkdir dir
+command: unzip -q -d dir $FILES/example.zip d1/d2/a.txt && cat dir/d1/d2/a.txt
+after: [ ! -f d1/d2/a.txt ]
+expected-stdout:
+ a
+---
+
+name: unzip -p
+command: unzip -p $FILES/example.zip d1/d2/a.txt
+after: [ ! -f d1/d2/a.txt ]
+expected-stdout:
+ a
+---
+
+name: unzip -x FILE...
+# Note: the RI ignores -x DIR for some reason, but it's not obvious we should.
+command: unzip -q $FILES/example.zip -x d1/d2/a.txt d1/d2/b.txt d1/d2/empty.txt d1/d2/x.txt && cat d1/d2/c.txt
+after: [ ! -f d1/d2/a.txt ]
+after: [ ! -f d1/d2/b.txt ]
+after: [ ! -f d1/d2/empty.txt ]
+after: [ ! -f d1/d2/x.txt ]
+after: [ -d d1/d2/dir ]
+expected-stdout:
+ ccc
+---
+
+name: unzip FILE -x FILE...
+command: unzip -q $FILES/example.zip d1/d2/a.txt d1/d2/b.txt -x d1/d2/a.txt && cat d1/d2/b.txt
+after: [ ! -f d1/d2/a.txt ]
+after: [ -f d1/d2/b.txt ]
+after: [ ! -f d1/d2/c.txt ]
+after: [ ! -f d1/d2/empty.txt ]
+after: [ ! -f d1/d2/x.txt ]
+after: [ ! -d d1/d2/dir ]
+expected-stdout:
+ bb
+---
diff --git a/libziparchive/cli-tests/zipinfo.test b/libziparchive/cli-tests/zipinfo.test
new file mode 100755
index 0000000..d5bce1c
--- /dev/null
+++ b/libziparchive/cli-tests/zipinfo.test
@@ -0,0 +1,53 @@
+# zipinfo tests.
+
+# Note: since "master key", Android uses libziparchive for all zip file
+# handling, and that scans the whole central directory immediately. Not only
+# lookups by name but also iteration is implemented using the resulting hash
+# table, meaning that any test that makes assumptions about iteration order
+# will fail on Android.
+
+name: zipinfo -1
+command: zipinfo -1 $FILES/example.zip | sort
+expected-stdout:
+ d1/
+ d1/d2/a.txt
+ d1/d2/b.txt
+ d1/d2/c.txt
+ d1/d2/dir/
+ d1/d2/empty.txt
+ d1/d2/x.txt
+---
+
+name: zipinfo header
+command: zipinfo $FILES/example.zip | head -2
+expected-stdout:
+ Archive: $FILES/example.zip
+ Zip file size: 1082 bytes, number of entries: 7
+---
+
+name: zipinfo footer
+command: zipinfo $FILES/example.zip | tail -1
+expected-stdout:
+ 7 files, 1033 bytes uncompressed, 20 bytes compressed: 98.1%
+---
+
+name: zipinfo directory
+# The RI doesn't use ISO dates.
+command: zipinfo $FILES/example.zip d1/ | sed s/17-Jun-/2017-06-/
+expected-stdout:
+ drwxr-x--- 3.0 unx 0 bx stor 2017-06-04 08:40 d1/
+---
+
+name: zipinfo stored
+# The RI doesn't use ISO dates.
+command: zipinfo $FILES/example.zip d1/d2/empty.txt | sed s/17-Jun-/2017-06-/
+expected-stdout:
+ -rw-r----- 3.0 unx 0 bx stor 2017-06-04 08:43 d1/d2/empty.txt
+---
+
+name: zipinfo deflated
+# The RI doesn't use ISO dates.
+command: zipinfo $FILES/example.zip d1/d2/x.txt | sed s/17-Jun-/2017-06-/
+expected-stdout:
+ -rw-r----- 3.0 unx 1024 tx defN 2017-06-04 08:45 d1/d2/x.txt
+---
diff --git a/libziparchive/run-ziptool-tests-on-android.sh b/libziparchive/run-ziptool-tests-on-android.sh
new file mode 100755
index 0000000..3c23d43
--- /dev/null
+++ b/libziparchive/run-ziptool-tests-on-android.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Copy the tests across.
+adb shell rm -rf /data/local/tmp/ziptool-tests/
+adb shell mkdir /data/local/tmp/ziptool-tests/
+adb push cli-tests/ /data/local/tmp/ziptool-tests/
+#adb push cli-test /data/local/tmp/ziptool-tests/
+
+if tty -s; then
+ dash_t="-t"
+else
+ dash_t=""
+fi
+
+exec adb shell $dash_t cli-test /data/local/tmp/ziptool-tests/cli-tests/*.test
diff --git a/libziparchive/testdata/empty.zip b/libziparchive/testdata/empty.zip
new file mode 100644
index 0000000..15cb0ec
--- /dev/null
+++ b/libziparchive/testdata/empty.zip
Binary files differ
diff --git a/libziparchive/testdata/zero-size-cd.zip b/libziparchive/testdata/zero-size-cd.zip
new file mode 100644
index 0000000..b6c8cbe
--- /dev/null
+++ b/libziparchive/testdata/zero-size-cd.zip
Binary files differ
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index ef29188..68837cc 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -265,14 +265,10 @@
ALOGV("+++ num_entries=%" PRIu32 " dir_size=%" PRIu32 " dir_offset=%" PRIu32, eocd->num_records,
eocd->cd_size, eocd->cd_start_offset);
- /*
- * It all looks good. Create a mapping for the CD, and set the fields
- * in archive.
- */
-
+ // It all looks good. Create a mapping for the CD, and set the fields
+ // in archive.
if (!archive->InitializeCentralDirectory(static_cast<off64_t>(eocd->cd_start_offset),
static_cast<size_t>(eocd->cd_size))) {
- ALOGE("Zip: failed to intialize central directory.\n");
return kMmapFailed;
}
@@ -354,7 +350,7 @@
if (archive->hash_table == nullptr) {
ALOGW("Zip: unable to allocate the %u-entry hash_table, entry size: %zu",
archive->hash_table_size, sizeof(ZipStringOffset));
- return -1;
+ return kAllocationFailed;
}
/*
@@ -365,24 +361,25 @@
const uint8_t* ptr = cd_ptr;
for (uint16_t i = 0; i < num_entries; i++) {
if (ptr > cd_end - sizeof(CentralDirectoryRecord)) {
- ALOGW("Zip: ran off the end (at %" PRIu16 ")", i);
+ ALOGW("Zip: ran off the end (item #%" PRIu16 ", %zu bytes of central directory)", i,
+ cd_length);
#if defined(__ANDROID__)
android_errorWriteLog(0x534e4554, "36392138");
#endif
- return -1;
+ return kInvalidFile;
}
const CentralDirectoryRecord* cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
if (cdr->record_signature != CentralDirectoryRecord::kSignature) {
ALOGW("Zip: missed a central dir sig (at %" PRIu16 ")", i);
- return -1;
+ return kInvalidFile;
}
const off64_t local_header_offset = cdr->local_file_header_offset;
if (local_header_offset >= archive->directory_offset) {
ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu16,
static_cast<int64_t>(local_header_offset), i);
- return -1;
+ return kInvalidFile;
}
const uint16_t file_name_length = cdr->file_name_length;
@@ -394,12 +391,12 @@
ALOGW("Zip: file name for entry %" PRIu16
" exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
i, file_name_length, cd_length);
- return -1;
+ return kInvalidEntryName;
}
// Check that file name is valid UTF-8 and doesn't contain NUL (U+0000) characters.
if (!IsValidEntryName(file_name, file_name_length)) {
ALOGW("Zip: invalid file name at entry %" PRIu16, i);
- return -1;
+ return kInvalidEntryName;
}
// Add the CDE filename to the hash table.
@@ -414,7 +411,7 @@
ptr += sizeof(CentralDirectoryRecord) + file_name_length + extra_length + comment_length;
if ((ptr - cd_ptr) > static_cast<int64_t>(cd_length)) {
ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu16, ptr - cd_ptr, cd_length, i);
- return -1;
+ return kInvalidFile;
}
}
@@ -422,7 +419,7 @@
if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast<uint8_t*>(&lfh_start_bytes),
sizeof(uint32_t), 0)) {
ALOGW("Zip: Unable to read header for entry at offset == 0.");
- return -1;
+ return kInvalidFile;
}
if (lfh_start_bytes != LocalFileHeader::kSignature) {
@@ -430,7 +427,7 @@
#if defined(__ANDROID__)
android_errorWriteLog(0x534e4554, "64211847");
#endif
- return -1;
+ return kInvalidFile;
}
ALOGV("+++ zip good scan %" PRIu16 " entries", num_entries);
@@ -439,16 +436,8 @@
}
static int32_t OpenArchiveInternal(ZipArchive* archive, const char* debug_file_name) {
- int32_t result = -1;
- if ((result = MapCentralDirectory(debug_file_name, archive)) != 0) {
- return result;
- }
-
- if ((result = ParseZipArchive(archive))) {
- return result;
- }
-
- return 0;
+ int32_t result = MapCentralDirectory(debug_file_name, archive);
+ return result != 0 ? result : ParseZipArchive(archive);
}
int32_t OpenArchiveFd(int fd, const char* debug_file_name, ZipArchiveHandle* handle,
@@ -1185,7 +1174,7 @@
return result;
} else {
if (base_ptr_ == nullptr) {
- ALOGE("Zip: invalid file map\n");
+ ALOGE("Zip: invalid file map");
return -1;
}
return static_cast<off64_t>(data_length_);
@@ -1196,12 +1185,12 @@
bool MappedZipFile::ReadAtOffset(uint8_t* buf, size_t len, off64_t off) const {
if (has_fd_) {
if (!android::base::ReadFullyAtOffset(fd_, buf, len, off)) {
- ALOGE("Zip: failed to read at offset %" PRId64 "\n", off);
+ ALOGE("Zip: failed to read at offset %" PRId64, off);
return false;
}
} else {
if (off < 0 || off > static_cast<off64_t>(data_length_)) {
- ALOGE("Zip: invalid offset: %" PRId64 ", data length: %" PRId64 "\n", off, data_length_);
+ ALOGE("Zip: invalid offset: %" PRId64 ", data length: %" PRId64, off, data_length_);
return false;
}
memcpy(buf, static_cast<const uint8_t*>(base_ptr_) + off, len);
@@ -1219,13 +1208,17 @@
if (mapped_zip.HasFd()) {
directory_map = android::base::MappedFile::FromFd(mapped_zip.GetFileDescriptor(),
cd_start_offset, cd_size, PROT_READ);
- if (!directory_map) return false;
+ if (!directory_map) {
+ ALOGE("Zip: failed to map central directory (offset %" PRId64 ", size %zu): %s",
+ cd_start_offset, cd_size, strerror(errno));
+ return false;
+ }
CHECK_EQ(directory_map->size(), cd_size);
central_directory.Initialize(directory_map->data(), 0 /*offset*/, cd_size);
} else {
if (mapped_zip.GetBasePtr() == nullptr) {
- ALOGE("Zip: Failed to map central directory, bad mapped_zip base pointer\n");
+ ALOGE("Zip: Failed to map central directory, bad mapped_zip base pointer");
return false;
}
if (static_cast<off64_t>(cd_start_offset) + static_cast<off64_t>(cd_size) >
diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h
index 60fdec0..1d05fc7 100644
--- a/libziparchive/zip_archive_private.h
+++ b/libziparchive/zip_archive_private.h
@@ -42,6 +42,7 @@
"Invalid entry name",
"I/O error",
"File mapping failed",
+ "Allocation failed",
};
enum ErrorCodes : int32_t {
@@ -87,7 +88,10 @@
// We were not able to mmap the central directory or entry contents.
kMmapFailed = -12,
- kLastErrorCode = kMmapFailed,
+ // An allocation failed.
+ kAllocationFailed = -13,
+
+ kLastErrorCode = kAllocationFailed,
};
class MappedZipFile {
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index 8781ab7..0916304 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -36,13 +36,9 @@
static std::string test_data_dir = android::base::GetExecutableDirectory() + "/testdata";
-static const std::string kMissingZip = "missing.zip";
static const std::string kValidZip = "valid.zip";
static const std::string kLargeZip = "large.zip";
static const std::string kBadCrcZip = "bad_crc.zip";
-static const std::string kCrashApk = "crash.apk";
-static const std::string kBadFilenameZip = "bad_filename.zip";
-static const std::string kUpdateZip = "dummy-update.zip";
static const std::vector<uint8_t> kATxtContents{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a',
'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n'};
@@ -52,13 +48,6 @@
static const std::vector<uint8_t> kBTxtContents{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n'};
-static const std::string kATxtName("a.txt");
-static const std::string kBTxtName("b.txt");
-static const std::string kNonexistentTxtName("nonexistent.txt");
-static const std::string kEmptyTxtName("empty.txt");
-static const std::string kLargeCompressTxtName("compress.txt");
-static const std::string kLargeUncompressTxtName("uncompress.txt");
-
static int32_t OpenArchiveWrapper(const std::string& name, ZipArchiveHandle* handle) {
const std::string abs_path = test_data_dir + "/" + name;
return OpenArchive(abs_path.c_str(), handle);
@@ -69,19 +58,31 @@
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
CloseArchive(handle);
- ASSERT_EQ(-1, OpenArchiveWrapper(kBadFilenameZip, &handle));
+ ASSERT_EQ(kInvalidEntryName, OpenArchiveWrapper("bad_filename.zip", &handle));
CloseArchive(handle);
}
TEST(ziparchive, OutOfBound) {
ZipArchiveHandle handle;
- ASSERT_EQ(-8, OpenArchiveWrapper(kCrashApk, &handle));
+ ASSERT_EQ(kInvalidOffset, OpenArchiveWrapper("crash.apk", &handle));
+ CloseArchive(handle);
+}
+
+TEST(ziparchive, EmptyArchive) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(kEmptyArchive, OpenArchiveWrapper("empty.zip", &handle));
+ CloseArchive(handle);
+}
+
+TEST(ziparchive, ZeroSizeCentralDirectory) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(kInvalidFile, OpenArchiveWrapper("zero-size-cd.zip", &handle));
CloseArchive(handle);
}
TEST(ziparchive, OpenMissing) {
ZipArchiveHandle handle;
- ASSERT_NE(0, OpenArchiveWrapper(kMissingZip, &handle));
+ ASSERT_NE(0, OpenArchiveWrapper("missing.zip", &handle));
// Confirm the file descriptor is not going to be mistaken for a valid one.
ASSERT_EQ(-1, GetFileDescriptor(handle));
@@ -200,7 +201,7 @@
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
ZipEntry data;
- ASSERT_EQ(0, FindEntry(handle, kATxtName, &data));
+ ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
// Known facts about a.txt, from zipinfo -v.
ASSERT_EQ(63, data.offset);
@@ -211,7 +212,7 @@
ASSERT_EQ(static_cast<uint32_t>(0x438a8005), data.mod_time);
// An entry that doesn't exist. Should be a negative return code.
- ASSERT_LT(FindEntry(handle, kNonexistentTxtName, &data), 0);
+ ASSERT_LT(FindEntry(handle, "this file does not exist", &data), 0);
CloseArchive(handle);
}
@@ -259,7 +260,7 @@
// An entry that's deflated.
ZipEntry data;
- ASSERT_EQ(0, FindEntry(handle, kATxtName, &data));
+ ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
const uint32_t a_size = data.uncompressed_length;
ASSERT_EQ(a_size, kATxtContents.size());
uint8_t* buffer = new uint8_t[a_size];
@@ -268,7 +269,7 @@
delete[] buffer;
// An entry that's stored.
- ASSERT_EQ(0, FindEntry(handle, kBTxtName, &data));
+ ASSERT_EQ(0, FindEntry(handle, "b.txt", &data));
const uint32_t b_size = data.uncompressed_length;
ASSERT_EQ(b_size, kBTxtContents.size());
buffer = new uint8_t[b_size];
@@ -323,7 +324,7 @@
ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EmptyEntriesTest", &handle, false));
ZipEntry entry;
- ASSERT_EQ(0, FindEntry(handle, kEmptyTxtName, &entry));
+ ASSERT_EQ(0, FindEntry(handle, "empty.txt", &entry));
ASSERT_EQ(static_cast<uint32_t>(0), entry.uncompressed_length);
uint8_t buffer[1];
ASSERT_EQ(0, ExtractToMemory(handle, &entry, buffer, 1));
@@ -403,7 +404,7 @@
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
ZipEntry entry;
- ASSERT_EQ(0, FindEntry(handle, kATxtName, &entry));
+ ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry));
ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_file.fd));
// Assert that the first 8 bytes of the file haven't been clobbered.
@@ -425,7 +426,7 @@
#if !defined(_WIN32)
TEST(ziparchive, OpenFromMemory) {
- const std::string zip_path = test_data_dir + "/" + kUpdateZip;
+ const std::string zip_path = test_data_dir + "/dummy-update.zip";
android::base::unique_fd fd(open(zip_path.c_str(), O_RDONLY | O_BINARY));
ASSERT_NE(-1, fd);
struct stat sb;
@@ -510,27 +511,27 @@
}
TEST(ziparchive, StreamCompressed) {
- ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContents, false);
+ ZipArchiveStreamTestUsingContents(kValidZip, "a.txt", kATxtContents, false);
}
TEST(ziparchive, StreamUncompressed) {
- ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, false);
+ ZipArchiveStreamTestUsingContents(kValidZip, "b.txt", kBTxtContents, false);
}
TEST(ziparchive, StreamRawCompressed) {
- ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContentsCompressed, true);
+ ZipArchiveStreamTestUsingContents(kValidZip, "a.txt", kATxtContentsCompressed, true);
}
TEST(ziparchive, StreamRawUncompressed) {
- ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, true);
+ ZipArchiveStreamTestUsingContents(kValidZip, "b.txt", kBTxtContents, true);
}
TEST(ziparchive, StreamLargeCompressed) {
- ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeCompressTxtName);
+ ZipArchiveStreamTestUsingMemory(kLargeZip, "compress.txt");
}
TEST(ziparchive, StreamLargeUncompressed) {
- ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeUncompressTxtName);
+ ZipArchiveStreamTestUsingMemory(kLargeZip, "uncompress.txt");
}
TEST(ziparchive, StreamCompressedBadCrc) {
@@ -539,7 +540,7 @@
ZipEntry entry;
std::vector<uint8_t> read_data;
- ZipArchiveStreamTest(handle, kATxtName, false, false, &entry, &read_data);
+ ZipArchiveStreamTest(handle, "a.txt", false, false, &entry, &read_data);
CloseArchive(handle);
}
@@ -550,7 +551,7 @@
ZipEntry entry;
std::vector<uint8_t> read_data;
- ZipArchiveStreamTest(handle, kBTxtName, false, false, &entry, &read_data);
+ ZipArchiveStreamTest(handle, "b.txt", false, false, &entry, &read_data);
CloseArchive(handle);
}
@@ -647,7 +648,8 @@
// Out of bounds.
ASSERT_STREQ("Unknown return code", ErrorCodeString(1));
- ASSERT_STREQ("Unknown return code", ErrorCodeString(-13));
+ ASSERT_STRNE("Unknown return code", ErrorCodeString(kLastErrorCode));
+ ASSERT_STREQ("Unknown return code", ErrorCodeString(kLastErrorCode - 1));
ASSERT_STREQ("I/O error", ErrorCodeString(kIoError));
}
@@ -698,7 +700,7 @@
ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, &kZipFileWithBrokenLfhSignature[0],
kZipFileWithBrokenLfhSignature.size()));
ZipArchiveHandle handle;
- ASSERT_EQ(-1, OpenArchiveFd(tmp_file.fd, "LeadingNonZipBytes", &handle, false));
+ ASSERT_EQ(kInvalidFile, OpenArchiveFd(tmp_file.fd, "LeadingNonZipBytes", &handle, false));
}
class VectorReader : public zip_archive::Reader {
diff --git a/libziparchive/ziptool-tests.xml b/libziparchive/ziptool-tests.xml
new file mode 100644
index 0000000..211119f
--- /dev/null
+++ b/libziparchive/ziptool-tests.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for running ziptool-tests through Atest or in Infra">
+ <option name="test-suite-tag" value="ziptool-tests" />
+ <!-- This test requires a device, so it's not annotated with a null-device. -->
+ <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+ <option name="binary" value="run-ziptool-tests-on-android.sh" />
+ <!-- Test script assumes a relative path with the cli-tests/ folders. -->
+ <option name="relative-path-execution" value="true" />
+ <!-- Tests shouldn't be that long but set 15m to be safe. -->
+ <option name="per-binary-timeout" value="15m" />
+ </test>
+</configuration>
diff --git a/libziparchive/unzip.cpp b/libziparchive/ziptool.cpp
similarity index 95%
rename from libziparchive/unzip.cpp
rename to libziparchive/ziptool.cpp
index 11b575e..dd42e90 100644
--- a/libziparchive/unzip.cpp
+++ b/libziparchive/ziptool.cpp
@@ -52,7 +52,7 @@
static Role role;
static OverwriteMode overwrite_mode = kPrompt;
static bool flag_1 = false;
-static const char* flag_d = nullptr;
+static std::string flag_d;
static bool flag_l = false;
static bool flag_p = false;
static bool flag_q = false;
@@ -214,12 +214,9 @@
}
// Where are we actually extracting to (for human-readable output)?
- std::string dst;
- if (flag_d) {
- dst = flag_d;
- if (!EndsWith(dst, "/")) dst += '/';
- }
- dst += name;
+ // flag_d is the empty string if -d wasn't used, or has a trailing '/'
+ // otherwise.
+ std::string dst = flag_d + name;
// Ensure the directory hierarchy exists.
if (!MakeDirectoryHierarchy(android::base::Dirname(name))) {
@@ -463,6 +460,7 @@
switch (opt) {
case 'd':
flag_d = optarg;
+ if (!EndsWith(flag_d, "/")) flag_d += '/';
break;
case 'l':
flag_l = true;
@@ -511,9 +509,17 @@
}
// Implement -d by changing into that directory.
- // We'll create implicit directories based on paths in the zip file, but we
- // require that the -d directory already exists.
- if (flag_d && chdir(flag_d) == -1) die(errno, "couldn't chdir to %s", flag_d);
+ // We'll create implicit directories based on paths in the zip file, and we'll create
+ // the -d directory itself, but we require that *parents* of the -d directory already exists.
+ // This is pretty arbitrary, but it's the behavior of the original unzip.
+ if (!flag_d.empty()) {
+ if (mkdir(flag_d.c_str(), 0777) == -1 && errno != EEXIST) {
+ die(errno, "couldn't created %s", flag_d.c_str());
+ }
+ if (chdir(flag_d.c_str()) == -1) {
+ die(errno, "couldn't chdir to %s", flag_d.c_str());
+ }
+ }
ProcessAll(zah);
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index ebc0cde..5821379 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -72,7 +72,7 @@
#
# create some directories (some are mount points) and symlinks
LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
- dev proc sys system data odm oem acct config storage mnt apex debug_ramdisk \
+ dev proc sys system data data_mirror odm oem acct config storage mnt apex debug_ramdisk \
linkerconfig $(BOARD_ROOT_EXTRA_FOLDERS)); \
ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
@@ -156,132 +156,6 @@
)
endef
-
-#######################################
-# ld.config.txt selection variables
-#
-_enforce_vndk_at_runtime := false
-ifdef BOARD_VNDK_VERSION
- ifneq ($(BOARD_VNDK_RUNTIME_DISABLE),true)
- _enforce_vndk_at_runtime := true
- endif
-endif
-
-_enforce_vndk_lite_at_runtime := false
-ifeq ($(_enforce_vndk_at_runtime),false)
- ifeq ($(PRODUCT_TREBLE_LINKER_NAMESPACES)|$(SANITIZE_TARGET),true|)
- _enforce_vndk_lite_at_runtime := true
- endif
-endif
-
-#######################################
-# ld.config.txt
-#
-# For VNDK enforced devices that have defined BOARD_VNDK_VERSION, use
-# "ld.config.txt" as a source file. This configuration includes strict VNDK
-# run-time restrictions for vendor process.
-#
-# Other treblized devices, that have not defined BOARD_VNDK_VERSION or that
-# have set BOARD_VNDK_RUNTIME_DISABLE to true, use "ld.config.vndk_lite.txt"
-# as a source file. This configuration does not have strict VNDK run-time
-# restrictions.
-#
-# If the device is not treblized, use "ld.config.legacy.txt" for legacy
-# namespace configuration.
-#
-include $(CLEAR_VARS)
-LOCAL_MODULE := ld.config.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-
-# Start of i18n and ART APEX compatibility.
-#
-# Meta-comment:
-# The placing of this section is somewhat arbitrary. The LOCAL_POST_INSTALL_CMD
-# entries need to be associated with something that goes into /system.
-# ld.config.txt qualifies but it could be anything else in /system until soong
-# supports creation of symlinks. http://b/123333111
-#
-# Keeping the appearance of files/dirs having old locations for apps that have
-# come to rely on them.
-
-# http://b/121248172 - create a link from /system/usr/icu to
-# /apex/com.android.i18n/etc/icu so that apps can find the ICU .dat file.
-# A symlink can't overwrite a directory and the /system/usr/icu directory once
-# existed so the required structure must be created whatever we find.
-LOCAL_POST_INSTALL_CMD = mkdir -p $(TARGET_OUT)/usr && rm -rf $(TARGET_OUT)/usr/icu
-LOCAL_POST_INSTALL_CMD += && ln -sf /apex/com.android.i18n/etc/icu $(TARGET_OUT)/usr/icu
-
-# TODO(b/124106384): Clean up compat symlinks for ART binaries.
-ART_BINARIES := dalvikvm dex2oat
-LOCAL_POST_INSTALL_CMD += && mkdir -p $(TARGET_OUT)/bin
-$(foreach b,$(ART_BINARIES), \
- $(eval LOCAL_POST_INSTALL_CMD += \
- && ln -sf /apex/com.android.art/bin/$(b) $(TARGET_OUT)/bin/$(b)) \
-)
-
-# End of i18n and ART APEX compatibilty.
-
-ifeq ($(_enforce_vndk_at_runtime),true)
-
-# for VNDK enforced devices
-# This file will be replaced with dynamically generated one from system/linkerconfig
-LOCAL_MODULE_STEM := $(LOCAL_MODULE)
-LOCAL_SRC_FILES := etc/ld.config.txt
-include $(BUILD_PREBUILT)
-
-else ifeq ($(_enforce_vndk_lite_at_runtime),true)
-
-# for treblized but VNDK lightly enforced devices
-LOCAL_MODULE_STEM := ld.config.vndk_lite.txt
-include $(BUILD_SYSTEM)/base_rules.mk
-ld_config_template := $(LOCAL_PATH)/etc/ld.config.vndk_lite.txt
-vndk_version := $(PLATFORM_VNDK_VERSION)
-libz_is_llndk := true
-include $(LOCAL_PATH)/update_and_install_ld_config.mk
-
-else
-
-# for legacy non-treblized devices
-LOCAL_MODULE_STEM := $(LOCAL_MODULE)
-LOCAL_SRC_FILES := etc/ld.config.legacy.txt
-include $(BUILD_PREBUILT)
-
-endif # ifeq ($(_enforce_vndk_at_runtime),true)
-
-#######################################
-# ld.config.vndk_lite.txt
-#
-# This module is only for GSI.
-#
-ifeq ($(_enforce_vndk_lite_at_runtime),false)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := ld.config.vndk_lite.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(LOCAL_MODULE)
-include $(BUILD_SYSTEM)/base_rules.mk
-ld_config_template := $(LOCAL_PATH)/etc/ld.config.vndk_lite.txt
-vndk_version := $(PLATFORM_VNDK_VERSION)
-libz_is_llndk := true
-include $(LOCAL_PATH)/update_and_install_ld_config.mk
-
-endif # ifeq ($(_enforce_vndk_lite_at_runtime),false)
-
-_enforce_vndk_at_runtime :=
-_enforce_vndk_lite_at_runtime :=
-
-#######################################
-# ld.config.txt for recovery
-include $(CLEAR_VARS)
-LOCAL_MODULE := ld.config.recovery.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := etc/ld.config.recovery.txt
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc
-LOCAL_MODULE_STEM := ld.config.txt
-include $(BUILD_PREBUILT)
-
#######################################
# sanitizer.libraries.txt
include $(CLEAR_VARS)
diff --git a/rootdir/avb/Android.mk b/rootdir/avb/Android.mk
index 5dc019c..80573fb 100644
--- a/rootdir/avb/Android.mk
+++ b/rootdir/avb/Android.mk
@@ -16,6 +16,21 @@
include $(BUILD_PREBUILT)
#######################################
+# q-developer-gsi.avbpubkey
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := q-developer-gsi.avbpubkey
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/first_stage_ramdisk/avb
+else
+LOCAL_MODULE_PATH := $(TARGET_RAMDISK_OUT)/avb
+endif
+
+include $(BUILD_PREBUILT)
+
+#######################################
# r-gsi.avbpubkey
include $(CLEAR_VARS)
diff --git a/rootdir/avb/q-developer-gsi.avbpubkey b/rootdir/avb/q-developer-gsi.avbpubkey
new file mode 100644
index 0000000..0ace69d
--- /dev/null
+++ b/rootdir/avb/q-developer-gsi.avbpubkey
Binary files differ
diff --git a/rootdir/etc/ld.config.legacy.txt b/rootdir/etc/ld.config.legacy.txt
index a99756a..5c87843 100644
--- a/rootdir/etc/ld.config.legacy.txt
+++ b/rootdir/etc/ld.config.legacy.txt
@@ -1,198 +1,3 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Bionic loader config file.
-# This gives the exactly the same namespace setup in pre-O.
-#
-
-# All binaries gets the same configuration 'legacy'
-dir.legacy = /system
-dir.legacy = /product
-dir.legacy = /vendor
-dir.legacy = /odm
-dir.legacy = /sbin
-
-# Except for /postinstall, where only /system and /product are searched
-dir.postinstall = /postinstall
-
-# Fallback entry to provide APEX namespace lookups for binaries anywhere else.
-# This must be last.
-dir.legacy = /data
-
-[legacy]
-namespace.default.isolated = false
-# Visible to allow links to be created at runtime, e.g. through
-# android_link_namespaces in libnativeloader.
-namespace.default.visible = true
-
-namespace.default.search.paths = /system/${LIB}
-namespace.default.search.paths += /product/${LIB}
-namespace.default.search.paths += /vendor/${LIB}
-namespace.default.search.paths += /odm/${LIB}
-
-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.search.paths += /data/asan/vendor/${LIB}
-namespace.default.asan.search.paths += /vendor/${LIB}
-namespace.default.asan.search.paths += /data/asan/odm/${LIB}
-namespace.default.asan.search.paths += /odm/${LIB}
-
-###############################################################################
-# APEX related namespaces.
-###############################################################################
-
-additional.namespaces = art,conscrypt,media,neuralnetworks,resolv
-
-# Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
-# If a shared library or an executable requests a shared library that
-# cannot be loaded into the default namespace, the dynamic linker tries
-# to load the shared library from the art namespace. And then, if the
-# shared library cannot be loaded from the art namespace either, the
-# dynamic linker tries to load the shared library from the resolv namespace.
-# Finally, if all attempts fail, the dynamic linker returns an error.
-namespace.default.links = art,resolv,neuralnetworks
-namespace.default.asan.links = art,resolv,neuralnetworks
-namespace.default.link.art.shared_libs = libandroidicu.so
-namespace.default.link.art.shared_libs += libdexfile_external.so
-namespace.default.link.art.shared_libs += libdexfiled_external.so
-# TODO(b/120786417 or b/134659294): libicuuc.so and libicui18n.so are kept for app compat.
-namespace.default.link.art.shared_libs += libicui18n.so
-namespace.default.link.art.shared_libs += libicuuc.so
-namespace.default.link.art.shared_libs += libnativebridge.so
-namespace.default.link.art.shared_libs += libnativehelper.so
-namespace.default.link.art.shared_libs += libnativeloader.so
-
-# TODO(b/122876336): Remove libpac.so once it's migrated to Webview
-namespace.default.link.art.shared_libs += libpac.so
-
-# When libnetd_resolv.so can't be found in the default namespace, search for it
-# in the resolv namespace. Don't allow any other libraries from the resolv namespace
-# to be loaded in the default namespace.
-namespace.default.link.resolv.shared_libs = libnetd_resolv.so
-
-# LLNDK library moved into apex
-namespace.default.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "art" APEX namespace
-#
-# This namespace exposes externally accessible libraries from the ART APEX.
-# Keep in sync with the "art" namespace in art/build/apex/ld.config.txt.
-###############################################################################
-namespace.art.isolated = true
-# Visible to allow links to be created at runtime, e.g. through
-# android_link_namespaces in libnativeloader.
-namespace.art.visible = true
-
-namespace.art.search.paths = /apex/com.android.art/${LIB}
-namespace.art.asan.search.paths = /apex/com.android.art/${LIB}
-namespace.art.links = default,neuralnetworks
-# Need allow_all_shared_libs because libart.so can dlopen oat files in
-# /system/framework and /data.
-# TODO(b/130340935): Use a dynamically created linker namespace similar to
-# classloader-namespace for oat files, and tighten this up.
-namespace.art.link.default.allow_all_shared_libs = true
-namespace.art.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "media" APEX namespace
-#
-# This namespace is for libraries within the media APEX.
-###############################################################################
-namespace.media.isolated = true
-namespace.media.visible = true
-
-namespace.media.search.paths = /apex/com.android.media/${LIB}
-namespace.media.asan.search.paths = /apex/com.android.media/${LIB}
-
-namespace.media.permitted.paths = /apex/com.android.media/${LIB}/extractors
-
-namespace.media.links = default
-namespace.media.link.default.shared_libs = libbinder_ndk.so
-namespace.media.link.default.shared_libs += libc.so
-namespace.media.link.default.shared_libs += libcgrouprc.so
-namespace.media.link.default.shared_libs += libdl.so
-namespace.media.link.default.shared_libs += liblog.so
-namespace.media.link.default.shared_libs += libmediametrics.so
-namespace.media.link.default.shared_libs += libmediandk.so
-namespace.media.link.default.shared_libs += libm.so
-namespace.media.link.default.shared_libs += libvndksupport.so
-
-namespace.media.link.default.shared_libs += libclang_rt.asan-aarch64-android.so
-namespace.media.link.default.shared_libs += libclang_rt.asan-arm-android.so
-namespace.media.link.default.shared_libs += libclang_rt.asan-i686-android.so
-namespace.media.link.default.shared_libs += libclang_rt.asan-x86_64-android.so
-namespace.media.link.default.shared_libs += libclang_rt.hwasan-aarch64-android.so
-
-###############################################################################
-# "conscrypt" APEX namespace
-#
-# This namespace is for libraries within the conscrypt APEX.
-# Keep in sync with the "conscrypt" namespace in art/build/apex/ld.config.txt.
-###############################################################################
-namespace.conscrypt.isolated = true
-namespace.conscrypt.visible = true
-
-namespace.conscrypt.search.paths = /apex/com.android.conscrypt/${LIB}
-namespace.conscrypt.asan.search.paths = /apex/com.android.conscrypt/${LIB}
-namespace.conscrypt.links = art,default
-namespace.conscrypt.link.art.shared_libs = libandroidio.so
-namespace.conscrypt.link.default.shared_libs = libc.so
-namespace.conscrypt.link.default.shared_libs += libm.so
-namespace.conscrypt.link.default.shared_libs += libdl.so
-namespace.conscrypt.link.default.shared_libs += liblog.so
-
-###############################################################################
-# "resolv" APEX namespace
-#
-# This namespace is for libraries within the resolv APEX.
-###############################################################################
-namespace.resolv.isolated = true
-namespace.resolv.visible = true
-
-namespace.resolv.search.paths = /apex/com.android.resolv/${LIB}
-namespace.resolv.asan.search.paths = /apex/com.android.resolv/${LIB}
-namespace.resolv.links = default
-namespace.resolv.link.default.shared_libs = libc.so
-namespace.resolv.link.default.shared_libs += libcgrouprc.so
-namespace.resolv.link.default.shared_libs += libm.so
-namespace.resolv.link.default.shared_libs += libdl.so
-namespace.resolv.link.default.shared_libs += libbinder_ndk.so
-namespace.resolv.link.default.shared_libs += liblog.so
-namespace.resolv.link.default.shared_libs += libvndksupport.so
-
-###############################################################################
-# "neuralnetworks" APEX namespace
-#
-# This namespace is for libraries within the NNAPI APEX.
-###############################################################################
-namespace.neuralnetworks.isolated = true
-namespace.neuralnetworks.visible = true
-
-namespace.neuralnetworks.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.asan.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.links = default
-namespace.neuralnetworks.link.default.shared_libs = libc.so
-namespace.neuralnetworks.link.default.shared_libs += libcgrouprc.so
-namespace.neuralnetworks.link.default.shared_libs += libdl.so
-namespace.neuralnetworks.link.default.shared_libs += liblog.so
-namespace.neuralnetworks.link.default.shared_libs += libm.so
-namespace.neuralnetworks.link.default.shared_libs += libnativewindow.so
-namespace.neuralnetworks.link.default.shared_libs += libneuralnetworks_packageinfo.so
-namespace.neuralnetworks.link.default.shared_libs += libsync.so
-namespace.neuralnetworks.link.default.shared_libs += libvndksupport.so
-
-
-###############################################################################
-# Namespace config for binaries under /postinstall.
-# Only one default namespace is defined and it has no directories other than
-# /system/lib and /product/lib in the search paths. This is because linker
-# calls realpath on the search paths and this causes selinux denial if the
-# paths (/vendor, /odm) are not allowed to the poinstall binaries.
-# There is no reason to allow the binaries to access the paths.
-###############################################################################
-[postinstall]
-namespace.default.isolated = false
-namespace.default.search.paths = /system/${LIB}
-namespace.default.search.paths += /product/${LIB}
+# This file is no longer in use.
+# Please update linker configuration generator instead.
+# You can find the code from /system/linkerconfig
\ No newline at end of file
diff --git a/rootdir/etc/ld.config.recovery.txt b/rootdir/etc/ld.config.recovery.txt
index 5d6c01a..5c87843 100644
--- a/rootdir/etc/ld.config.recovery.txt
+++ b/rootdir/etc/ld.config.recovery.txt
@@ -1,9 +1,3 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Bionic loader config file for recovery mode
-#
-
-dir.recovery = /system/bin
-
-[recovery]
-namespace.default.search.paths = /system/${LIB}
+# This file is no longer in use.
+# Please update linker configuration generator instead.
+# You can find the code from /system/linkerconfig
\ No newline at end of file
diff --git a/rootdir/etc/ld.config.vndk_lite.txt b/rootdir/etc/ld.config.vndk_lite.txt
index 9c9f4a9..5c87843 100644
--- a/rootdir/etc/ld.config.vndk_lite.txt
+++ b/rootdir/etc/ld.config.vndk_lite.txt
@@ -1,609 +1,3 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Bionic loader config file.
-#
-
-# Don't change the order here. The first pattern that matches with the
-# absolute path of an executable is selected.
-dir.system = /system/bin/
-dir.system = /system/xbin/
-dir.system = /%SYSTEM_EXT%/bin/
-dir.system = /%PRODUCT%/bin/
-
-dir.vendor = /odm/bin/
-dir.vendor = /vendor/bin/
-dir.vendor = /data/nativetest/odm
-dir.vendor = /data/nativetest64/odm
-dir.vendor = /data/benchmarktest/odm
-dir.vendor = /data/benchmarktest64/odm
-dir.vendor = /data/nativetest/vendor
-dir.vendor = /data/nativetest64/vendor
-dir.vendor = /data/benchmarktest/vendor
-dir.vendor = /data/benchmarktest64/vendor
-
-dir.unrestricted = /data/nativetest/unrestricted
-dir.unrestricted = /data/nativetest64/unrestricted
-
-# TODO(b/123864775): Ensure tests are run from /data/nativetest{,64} or (if
-# necessary) the unrestricted subdirs above. Then clean this up.
-dir.unrestricted = /data/local/tmp
-
-dir.postinstall = /postinstall
-
-# Fallback entry to provide APEX namespace lookups for binaries anywhere else.
-# This must be last.
-dir.system = /data
-
-[system]
-additional.namespaces = art,conscrypt,media,neuralnetworks,resolv,sphal,vndk,rs
-
-###############################################################################
-# "default" namespace
-#
-# Framework-side code runs in this namespace. However, libs from other
-# partitions are also allowed temporarily.
-###############################################################################
-namespace.default.isolated = false
-# Visible because some libraries are dlopen'ed, e.g. libopenjdk is dlopen'ed by
-# libart.
-namespace.default.visible = true
-
-namespace.default.search.paths = /system/${LIB}
-namespace.default.search.paths += /%SYSTEM_EXT%/${LIB}
-namespace.default.search.paths += /%PRODUCT%/${LIB}
-namespace.default.search.paths += /odm/${LIB}
-namespace.default.search.paths += /vendor/${LIB}
-
-namespace.default.asan.search.paths = /data/asan/system/${LIB}
-namespace.default.asan.search.paths += /system/${LIB}
-namespace.default.asan.search.paths += /data/asan/%SYSTEM_EXT%/${LIB}
-namespace.default.asan.search.paths += /%SYSTEM_EXT%/${LIB}
-namespace.default.asan.search.paths += /data/asan/%PRODUCT%/${LIB}
-namespace.default.asan.search.paths += /%PRODUCT%/${LIB}
-namespace.default.asan.search.paths += /data/asan/odm/${LIB}
-namespace.default.asan.search.paths += /odm/${LIB}
-namespace.default.asan.search.paths += /data/asan/vendor/${LIB}
-namespace.default.asan.search.paths += /vendor/${LIB}
-
-# Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
-# If a shared library or an executable requests a shared library that
-# cannot be loaded into the default namespace, the dynamic linker tries
-# to load the shared library from the art namespace. And then, if the
-# shared library cannot be loaded from the art namespace either, the
-# dynamic linker tries to load the shared library from the resolv namespace.
-# Finally, if all attempts fail, the dynamic linker returns an error.
-namespace.default.links = art,resolv,neuralnetworks
-namespace.default.link.art.shared_libs = libandroidicu.so
-namespace.default.link.art.shared_libs += libdexfile_external.so
-namespace.default.link.art.shared_libs += libdexfiled_external.so
-# TODO(b/120786417 or b/134659294): libicuuc.so and libicui18n.so are kept for app compat.
-namespace.default.link.art.shared_libs += libicui18n.so
-namespace.default.link.art.shared_libs += libicuuc.so
-namespace.default.link.art.shared_libs += libnativebridge.so
-namespace.default.link.art.shared_libs += libnativehelper.so
-namespace.default.link.art.shared_libs += libnativeloader.so
-
-# TODO(b/122876336): Remove libpac.so once it's migrated to Webview
-namespace.default.link.art.shared_libs += libpac.so
-
-# When libnetd_resolv.so can't be found in the default namespace, search for it
-# in the resolv namespace. Don't allow any other libraries from the resolv namespace
-# to be loaded in the default namespace.
-namespace.default.link.resolv.shared_libs = libnetd_resolv.so
-
-# LLNDK library moved into apex
-namespace.default.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "art" APEX namespace
-#
-# This namespace pulls in externally accessible libs from the ART APEX.
-# Keep in sync with the "art" namespace in art/build/apex/ld.config.txt.
-###############################################################################
-namespace.art.isolated = true
-# Visible to allow links to be created at runtime, e.g. through
-# android_link_namespaces in libnativeloader.
-namespace.art.visible = true
-
-namespace.art.search.paths = /apex/com.android.art/${LIB}
-namespace.art.asan.search.paths = /apex/com.android.art/${LIB}
-namespace.art.links = default,neuralnetworks
-# Need allow_all_shared_libs because libart.so can dlopen oat files in
-# /system/framework and /data.
-# TODO(b/130340935): Use a dynamically created linker namespace similar to
-# classloader-namespace for oat files, and tighten this up.
-namespace.art.link.default.allow_all_shared_libs = true
-namespace.art.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "media" APEX namespace
-#
-# This namespace is for libraries within the media APEX.
-###############################################################################
-namespace.media.isolated = true
-namespace.media.visible = true
-
-namespace.media.search.paths = /apex/com.android.media/${LIB}
-namespace.media.asan.search.paths = /apex/com.android.media/${LIB}
-
-namespace.media.permitted.paths = /apex/com.android.media/${LIB}/extractors
-
-namespace.media.links = default,neuralnetworks
-namespace.media.link.default.shared_libs = %LLNDK_LIBRARIES%
-namespace.media.link.default.shared_libs += libbinder_ndk.so
-namespace.media.link.default.shared_libs += libmediametrics.so
-namespace.media.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-# LLNDK library moved into apex
-namespace.media.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "conscrypt" APEX namespace
-#
-# This namespace is for libraries within the conscrypt APEX.
-# Keep in sync with the "conscrypt" namespace in art/build/apex/ld.config.txt.
-###############################################################################
-namespace.conscrypt.isolated = true
-namespace.conscrypt.visible = true
-
-namespace.conscrypt.search.paths = /apex/com.android.conscrypt/${LIB}
-namespace.conscrypt.asan.search.paths = /apex/com.android.conscrypt/${LIB}
-namespace.conscrypt.links = art,default
-namespace.conscrypt.link.art.shared_libs = libandroidio.so
-namespace.conscrypt.link.default.shared_libs = libc.so
-namespace.conscrypt.link.default.shared_libs += libm.so
-namespace.conscrypt.link.default.shared_libs += libdl.so
-namespace.conscrypt.link.default.shared_libs += liblog.so
-namespace.conscrypt.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-###############################################################################
-# "resolv" APEX namespace
-#
-# This namespace is for libraries within the resolv APEX.
-###############################################################################
-namespace.resolv.isolated = true
-namespace.resolv.visible = true
-
-namespace.resolv.search.paths = /apex/com.android.resolv/${LIB}
-namespace.resolv.asan.search.paths = /apex/com.android.resolv/${LIB}
-namespace.resolv.links = default
-namespace.resolv.link.default.shared_libs = libc.so
-namespace.resolv.link.default.shared_libs += libcgrouprc.so
-namespace.resolv.link.default.shared_libs += libm.so
-namespace.resolv.link.default.shared_libs += libdl.so
-namespace.resolv.link.default.shared_libs += libbinder_ndk.so
-namespace.resolv.link.default.shared_libs += liblog.so
-namespace.resolv.link.default.shared_libs += libvndksupport.so
-namespace.resolv.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-###############################################################################
-# "sphal" namespace
-#
-# SP-HAL(Sameprocess-HAL)s are the only vendor libraries that are allowed to be
-# loaded inside system processes. libEGL_<chipset>.so, libGLESv2_<chipset>.so,
-# android.hardware.graphics.mapper@2.0-impl.so, etc are SP-HALs.
-#
-# This namespace is exclusivly for SP-HALs. When the framework tries to dynami-
-# cally load SP-HALs, android_dlopen_ext() is used to explicitly specifying
-# that they should be searched and loaded from this namespace.
-#
-# Note that there is no link from the default namespace to this namespace.
-###############################################################################
-namespace.sphal.isolated = true
-# Visible to allow links to be created at runtime, e.g. through
-# android_link_namespaces in libnativeloader.
-namespace.sphal.visible = true
-
-namespace.sphal.search.paths = /odm/${LIB}
-namespace.sphal.search.paths += /vendor/${LIB}
-namespace.sphal.search.paths += /vendor/${LIB}/hw
-
-namespace.sphal.permitted.paths = /odm/${LIB}
-namespace.sphal.permitted.paths += /vendor/${LIB}
-namespace.sphal.permitted.paths += /system/vendor/${LIB}
-
-namespace.sphal.asan.search.paths = /data/asan/odm/${LIB}
-namespace.sphal.asan.search.paths += /odm/${LIB}
-namespace.sphal.asan.search.paths += /data/asan/vendor/${LIB}
-namespace.sphal.asan.search.paths += /vendor/${LIB}
-
-namespace.sphal.asan.permitted.paths = /data/asan/odm/${LIB}
-namespace.sphal.asan.permitted.paths += /odm/${LIB}
-namespace.sphal.asan.permitted.paths += /data/asan/vendor/${LIB}
-namespace.sphal.asan.permitted.paths += /vendor/${LIB}
-
-# Once in this namespace, access to libraries in /system/lib is restricted. Only
-# libs listed here can be used. Order is important here as the namespaces are
-# tried in this order. rs should be before vndk because both are capable
-# of loading libRS_internal.so
-namespace.sphal.links = rs,default,vndk,neuralnetworks
-
-# Renderscript gets separate namespace
-namespace.sphal.link.rs.shared_libs = libRS_internal.so
-
-namespace.sphal.link.default.shared_libs = %LLNDK_LIBRARIES%
-namespace.sphal.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-namespace.sphal.link.vndk.shared_libs = %VNDK_SAMEPROCESS_LIBRARIES%
-
-# LLNDK library moved into apex
-namespace.sphal.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "rs" namespace
-#
-# This namespace is exclusively for Renderscript internal libraries.
-# This namespace has slightly looser restriction than the vndk namespace because
-# of the genuine characteristics of Renderscript; /data is in the permitted path
-# to load the compiled *.so file and libmediandk.so can be used here.
-###############################################################################
-namespace.rs.isolated = true
-namespace.rs.visible = true
-
-namespace.rs.search.paths = /odm/${LIB}/vndk-sp
-namespace.rs.search.paths += /vendor/${LIB}/vndk-sp
-namespace.rs.search.paths += /system/${LIB}/vndk-sp%VNDK_VER%
-namespace.rs.search.paths += /odm/${LIB}
-namespace.rs.search.paths += /vendor/${LIB}
-
-namespace.rs.permitted.paths = /odm/${LIB}
-namespace.rs.permitted.paths += /vendor/${LIB}
-namespace.rs.permitted.paths += /system/vendor/${LIB}
-namespace.rs.permitted.paths += /data
-
-namespace.rs.asan.search.paths = /data/asan/odm/${LIB}/vndk-sp
-namespace.rs.asan.search.paths += /odm/${LIB}/vndk-sp
-namespace.rs.asan.search.paths += /data/asan/vendor/${LIB}/vndk-sp
-namespace.rs.asan.search.paths += /vendor/${LIB}/vndk-sp
-namespace.rs.asan.search.paths += /data/asan/system/${LIB}/vndk-sp%VNDK_VER%
-namespace.rs.asan.search.paths += /system/${LIB}/vndk-sp%VNDK_VER%
-namespace.rs.asan.search.paths += /data/asan/odm/${LIB}
-namespace.rs.asan.search.paths += /odm/${LIB}
-namespace.rs.asan.search.paths += /data/asan/vendor/${LIB}
-namespace.rs.asan.search.paths += /vendor/${LIB}
-
-namespace.rs.asan.permitted.paths = /data/asan/odm/${LIB}
-namespace.rs.asan.permitted.paths += /odm/${LIB}
-namespace.rs.asan.permitted.paths += /data/asan/vendor/${LIB}
-namespace.rs.asan.permitted.paths += /vendor/${LIB}
-namespace.rs.asan.permitted.paths += /data
-
-namespace.rs.links = default,neuralnetworks
-
-namespace.rs.link.default.shared_libs = %LLNDK_LIBRARIES%
-namespace.rs.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-# Private LLNDK libs (e.g. libft2.so) are exceptionally allowed to this
-# namespace because RS framework libs are using them.
-namespace.rs.link.default.shared_libs += %PRIVATE_LLNDK_LIBRARIES%
-
-# LLNDK library moved into apex
-namespace.rs.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "vndk" namespace
-#
-# This namespace is exclusively for vndk-sp libs.
-###############################################################################
-namespace.vndk.isolated = true
-# Visible to allow links to be created at runtime, e.g. through
-# android_link_namespaces in libnativeloader.
-namespace.vndk.visible = true
-
-namespace.vndk.search.paths = /odm/${LIB}/vndk-sp
-namespace.vndk.search.paths += /vendor/${LIB}/vndk-sp
-namespace.vndk.search.paths += /system/${LIB}/vndk-sp%VNDK_VER%
-
-namespace.vndk.permitted.paths = /odm/${LIB}/hw
-namespace.vndk.permitted.paths += /odm/${LIB}/egl
-namespace.vndk.permitted.paths += /vendor/${LIB}/hw
-namespace.vndk.permitted.paths += /vendor/${LIB}/egl
-namespace.vndk.permitted.paths += /system/vendor/${LIB}/egl
-# This is exceptionally required since android.hidl.memory@1.0-impl.so is here
-namespace.vndk.permitted.paths += /system/${LIB}/vndk-sp%VNDK_VER%/hw
-
-namespace.vndk.asan.search.paths = /data/asan/odm/${LIB}/vndk-sp
-namespace.vndk.asan.search.paths += /odm/${LIB}/vndk-sp
-namespace.vndk.asan.search.paths += /data/asan/vendor/${LIB}/vndk-sp
-namespace.vndk.asan.search.paths += /vendor/${LIB}/vndk-sp
-namespace.vndk.asan.search.paths += /data/asan/system/${LIB}/vndk-sp%VNDK_VER%
-namespace.vndk.asan.search.paths += /system/${LIB}/vndk-sp%VNDK_VER%
-
-namespace.vndk.asan.permitted.paths = /data/asan/odm/${LIB}/hw
-namespace.vndk.asan.permitted.paths += /odm/${LIB}/hw
-namespace.vndk.asan.permitted.paths += /data/asan/odm/${LIB}/egl
-namespace.vndk.asan.permitted.paths += /odm/${LIB}/egl
-namespace.vndk.asan.permitted.paths += /data/asan/vendor/${LIB}/hw
-namespace.vndk.asan.permitted.paths += /vendor/${LIB}/hw
-namespace.vndk.asan.permitted.paths += /data/asan/vendor/${LIB}/egl
-namespace.vndk.asan.permitted.paths += /vendor/${LIB}/egl
-
-namespace.vndk.asan.permitted.paths += /data/asan/system/${LIB}/vndk-sp%VNDK_VER%/hw
-namespace.vndk.asan.permitted.paths += /system/${LIB}/vndk-sp%VNDK_VER%/hw
-
-# When these NDK libs are required inside this namespace, then it is redirected
-# to the default namespace. This is possible since their ABI is stable across
-# Android releases.
-namespace.vndk.links = default,neuralnetworks
-
-namespace.vndk.link.default.shared_libs = %LLNDK_LIBRARIES%
-namespace.vndk.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-namespace.vndk.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "neuralnetworks" APEX namespace
-#
-# This namespace is for libraries within the NNAPI APEX.
-###############################################################################
-namespace.neuralnetworks.isolated = true
-namespace.neuralnetworks.visible = true
-
-namespace.neuralnetworks.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.asan.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.links = default
-namespace.neuralnetworks.link.default.shared_libs = libc.so
-namespace.neuralnetworks.link.default.shared_libs += libcgrouprc.so
-namespace.neuralnetworks.link.default.shared_libs += libdl.so
-namespace.neuralnetworks.link.default.shared_libs += liblog.so
-namespace.neuralnetworks.link.default.shared_libs += libm.so
-namespace.neuralnetworks.link.default.shared_libs += libnativewindow.so
-namespace.neuralnetworks.link.default.shared_libs += libneuralnetworks_packageinfo.so
-namespace.neuralnetworks.link.default.shared_libs += libsync.so
-namespace.neuralnetworks.link.default.shared_libs += libvndksupport.so
-namespace.neuralnetworks.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-###############################################################################
-# Namespace config for vendor processes. In O, no restriction is enforced for
-# them. However, in O-MR1, access to /system/${LIB} will not be allowed to
-# the default namespace. 'system' namespace will be added to give limited
-# (LL-NDK only) access.
-###############################################################################
-[vendor]
-additional.namespaces = art,neuralnetworks
-
-namespace.default.isolated = false
-
-namespace.default.search.paths = /odm/${LIB}
-namespace.default.search.paths += /odm/${LIB}/vndk
-namespace.default.search.paths += /odm/${LIB}/vndk-sp
-namespace.default.search.paths += /vendor/${LIB}
-namespace.default.search.paths += /vendor/${LIB}/vndk
-namespace.default.search.paths += /vendor/${LIB}/vndk-sp
-
-# Access to system libraries is allowed
-namespace.default.search.paths += /system/${LIB}/vndk-sp%VNDK_VER%
-namespace.default.search.paths += /system/${LIB}
-namespace.default.search.paths += /%SYSTEM_EXT%/${LIB}
-namespace.default.search.paths += /%PRODUCT%/${LIB}
-# Put /system/lib/vndk at the last search order in vndk_lite for GSI
-namespace.default.search.paths += /system/${LIB}/vndk%VNDK_VER%
-
-namespace.default.asan.search.paths = /data/asan/odm/${LIB}
-namespace.default.asan.search.paths += /odm/${LIB}
-namespace.default.asan.search.paths += /data/asan/odm/${LIB}/vndk
-namespace.default.asan.search.paths += /odm/${LIB}/vndk
-namespace.default.asan.search.paths += /data/asan/odm/${LIB}/vndk-sp
-namespace.default.asan.search.paths += /odm/${LIB}/vndk-sp
-namespace.default.asan.search.paths += /data/asan/vendor/${LIB}
-namespace.default.asan.search.paths += /vendor/${LIB}
-namespace.default.asan.search.paths += /data/asan/vendor/${LIB}/vndk
-namespace.default.asan.search.paths += /vendor/${LIB}/vndk
-namespace.default.asan.search.paths += /data/asan/vendor/${LIB}/vndk-sp
-namespace.default.asan.search.paths += /vendor/${LIB}/vndk-sp
-namespace.default.asan.search.paths += /data/asan/system/${LIB}/vndk-sp%VNDK_VER%
-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/%SYSTEM_EXT%/${LIB}
-namespace.default.asan.search.paths += /%SYSTEM_EXT%/${LIB}
-namespace.default.asan.search.paths += /data/asan/%PRODUCT%/${LIB}
-namespace.default.asan.search.paths += /%PRODUCT%/${LIB}
-namespace.default.asan.search.paths += /data/asan/system/${LIB}/vndk%VNDK_VER%
-namespace.default.asan.search.paths += /system/${LIB}/vndk%VNDK_VER%
-
-namespace.default.links = art,neuralnetworks
-namespace.default.link.art.shared_libs = libdexfile_external.so
-namespace.default.link.art.shared_libs += libdexfiled_external.so
-# TODO(b/120786417 or b/134659294): libicuuc.so and libicui18n.so are kept for app compat.
-namespace.default.link.art.shared_libs += libicui18n.so
-namespace.default.link.art.shared_libs += libicuuc.so
-namespace.default.link.art.shared_libs += libnativebridge.so
-namespace.default.link.art.shared_libs += libnativehelper.so
-namespace.default.link.art.shared_libs += libnativeloader.so
-# Workaround for b/124772622
-namespace.default.link.art.shared_libs += libandroidicu.so
-
-# LLNDK library moved into apex
-namespace.default.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "art" APEX namespace
-#
-# This namespace exposes externally accessible libraries from the ART APEX.
-# Keep in sync with the "art" namespace in art/build/apex/ld.config.txt.
-###############################################################################
-namespace.art.isolated = true
-
-namespace.art.search.paths = /apex/com.android.art/${LIB}
-namespace.art.asan.search.paths = /apex/com.android.art/${LIB}
-namespace.art.links = default
-# TODO(b/130340935): Use a dynamically created linker namespace similar to
-# classloader-namespace for oat files, and tighten this up.
-namespace.art.link.default.allow_all_shared_libs = true
-
-###############################################################################
-# "neuralnetworks" APEX namespace
-#
-# This namespace is for libraries within the NNAPI APEX.
-###############################################################################
-namespace.neuralnetworks.isolated = true
-namespace.neuralnetworks.visible = true
-
-namespace.neuralnetworks.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.asan.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.links = default
-namespace.neuralnetworks.link.default.shared_libs = libc.so
-namespace.neuralnetworks.link.default.shared_libs += libcgrouprc.so
-namespace.neuralnetworks.link.default.shared_libs += libdl.so
-namespace.neuralnetworks.link.default.shared_libs += liblog.so
-namespace.neuralnetworks.link.default.shared_libs += libm.so
-namespace.neuralnetworks.link.default.shared_libs += libnativewindow.so
-namespace.neuralnetworks.link.default.shared_libs += libneuralnetworks_packageinfo.so
-namespace.neuralnetworks.link.default.shared_libs += libsync.so
-namespace.neuralnetworks.link.default.shared_libs += libvndksupport.so
-namespace.neuralnetworks.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-###############################################################################
-# Namespace config for native tests that need access to both system and vendor
-# libraries. This replicates the default linker config (done by
-# init_default_namespace_no_config in bionic/linker/linker.cpp), except that it
-# includes the requisite namespace setup for APEXes.
-###############################################################################
-[unrestricted]
-additional.namespaces = art,media,conscrypt,resolv,neuralnetworks
-
-# Visible to allow links to be created at runtime, e.g. through
-# android_link_namespaces in libnativeloader.
-namespace.default.visible = true
-
-namespace.default.search.paths = /system/${LIB}
-namespace.default.search.paths += /odm/${LIB}
-namespace.default.search.paths += /vendor/${LIB}
-
-namespace.default.asan.search.paths = /data/asan/system/${LIB}
-namespace.default.asan.search.paths += /system/${LIB}
-namespace.default.asan.search.paths += /data/asan/odm/${LIB}
-namespace.default.asan.search.paths += /odm/${LIB}
-namespace.default.asan.search.paths += /data/asan/vendor/${LIB}
-namespace.default.asan.search.paths += /vendor/${LIB}
-
-# Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
-namespace.default.links = art,resolv,neuralnetworks
-namespace.default.link.art.shared_libs = libandroidicu.so
-namespace.default.link.art.shared_libs += libdexfile_external.so
-namespace.default.link.art.shared_libs += libdexfiled_external.so
-# TODO(b/120786417 or b/134659294): libicuuc.so and libicui18n.so are kept for app compat.
-namespace.default.link.art.shared_libs += libicui18n.so
-namespace.default.link.art.shared_libs += libicuuc.so
-namespace.default.link.art.shared_libs += libnativebridge.so
-namespace.default.link.art.shared_libs += libnativehelper.so
-namespace.default.link.art.shared_libs += libnativeloader.so
-
-# TODO(b/122876336): Remove libpac.so once it's migrated to Webview
-namespace.default.link.art.shared_libs += libpac.so
-
-namespace.default.link.resolv.shared_libs = libnetd_resolv.so
-
-# LLNDK library moved into apex
-namespace.default.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "art" APEX namespace
-#
-# This namespace exposes externally accessible libraries from the ART APEX.
-# Keep in sync with the "art" namespace in art/build/apex/ld.config.txt.
-###############################################################################
-namespace.art.isolated = true
-# Visible to allow links to be created at runtime, e.g. through
-# android_link_namespaces in libnativeloader.
-namespace.art.visible = true
-
-namespace.art.search.paths = /apex/com.android.art/${LIB}
-namespace.art.asan.search.paths = /apex/com.android.art/${LIB}
-namespace.art.links = default
-# TODO(b/130340935): Use a dynamically created linker namespace similar to
-# classloader-namespace for oat files, and tighten this up.
-namespace.runtime.link.default.allow_all_shared_libs = true
-
-###############################################################################
-# "media" APEX namespace
-#
-# This namespace is for libraries within the media APEX.
-###############################################################################
-namespace.media.isolated = true
-namespace.media.visible = true
-
-namespace.media.search.paths = /apex/com.android.media/${LIB}
-namespace.media.asan.search.paths = /apex/com.android.media/${LIB}
-
-namespace.media.permitted.paths = /apex/com.android.media/${LIB}/extractors
-
-namespace.media.links = default,neuralnetworks
-namespace.media.link.default.shared_libs = %LLNDK_LIBRARIES%
-namespace.media.link.default.shared_libs += libbinder_ndk.so
-namespace.media.link.default.shared_libs += libmediametrics.so
-namespace.media.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-# LLNDK library moved into apex
-namespace.media.link.neuralnetworks.shared_libs = libneuralnetworks.so
-
-###############################################################################
-# "conscrypt" APEX namespace
-#
-# This namespace is for libraries within the conscrypt APEX.
-# Keep in sync with the "conscrypt" namespace in art/build/apex/ld.config.txt.
-###############################################################################
-namespace.conscrypt.isolated = true
-namespace.conscrypt.visible = true
-
-namespace.conscrypt.search.paths = /apex/com.android.conscrypt/${LIB}
-namespace.conscrypt.asan.search.paths = /apex/com.android.conscrypt/${LIB}
-namespace.conscrypt.links = art,default
-namespace.conscrypt.link.art.shared_libs = libandroidio.so
-namespace.conscrypt.link.default.shared_libs = libc.so
-namespace.conscrypt.link.default.shared_libs += libm.so
-namespace.conscrypt.link.default.shared_libs += libdl.so
-namespace.conscrypt.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-###############################################################################
-# "resolv" APEX namespace
-#
-# This namespace is for libraries within the resolv APEX.
-###############################################################################
-namespace.resolv.isolated = true
-namespace.resolv.visible = true
-
-namespace.resolv.search.paths = /apex/com.android.resolv/${LIB}
-namespace.resolv.asan.search.paths = /apex/com.android.resolv/${LIB}
-namespace.resolv.links = default
-namespace.resolv.link.default.shared_libs = libc.so
-namespace.resolv.link.default.shared_libs += libcgrouprc.so
-namespace.resolv.link.default.shared_libs += libm.so
-namespace.resolv.link.default.shared_libs += libdl.so
-namespace.resolv.link.default.shared_libs += libbinder_ndk.so
-namespace.resolv.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-###############################################################################
-# "neuralnetworks" APEX namespace
-#
-# This namespace is for libraries within the NNAPI APEX.
-###############################################################################
-namespace.neuralnetworks.isolated = true
-namespace.neuralnetworks.visible = true
-
-namespace.neuralnetworks.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.asan.search.paths = /apex/com.android.neuralnetworks/${LIB}
-namespace.neuralnetworks.links = default
-namespace.neuralnetworks.link.default.shared_libs = libc.so
-namespace.neuralnetworks.link.default.shared_libs += libcgrouprc.so
-namespace.neuralnetworks.link.default.shared_libs += libdl.so
-namespace.neuralnetworks.link.default.shared_libs += liblog.so
-namespace.neuralnetworks.link.default.shared_libs += libm.so
-namespace.neuralnetworks.link.default.shared_libs += libnativewindow.so
-namespace.neuralnetworks.link.default.shared_libs += libneuralnetworks_packageinfo.so
-namespace.neuralnetworks.link.default.shared_libs += libsync.so
-namespace.neuralnetworks.link.default.shared_libs += libvndksupport.so
-namespace.neuralnetworks.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
-
-###############################################################################
-# Namespace config for binaries under /postinstall.
-# Only default namespace is defined and default has no directories
-# other than /system/lib in the search paths. This is because linker calls
-# realpath on the search paths and this causes selinux denial if the paths
-# (/vendor, /odm) are not allowed to the postinstall binaries. There is no
-# reason to allow the binaries to access the paths.
-###############################################################################
-[postinstall]
-namespace.default.isolated = false
-namespace.default.search.paths = /system/${LIB}
-namespace.default.search.paths += /%SYSTEM_EXT%/${LIB}
-namespace.default.search.paths += /%PRODUCT%/${LIB}
+# This file is no longer in use.
+# Please update linker configuration generator instead.
+# You can find the code from /system/linkerconfig
\ No newline at end of file
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 2ec0669..7a3339d 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -38,9 +38,18 @@
# Allow up to 32K FDs per process
setrlimit nofile 32768 32768
+ # Set up linker config subdirectories based on mount namespaces
+ mkdir /linkerconfig/bootstrap 0755
+ mkdir /linkerconfig/default 0755
+
# Generate ld.config.txt for early executed processes
- exec -- /system/bin/linkerconfig --target /linkerconfig/ld.config.txt
- chmod 444 /linkerconfig/ld.config.txt
+ exec -- /system/bin/linkerconfig --target /linkerconfig/bootstrap
+ chmod 644 /linkerconfig/bootstrap/ld.config.txt
+ copy /linkerconfig/bootstrap/ld.config.txt /linkerconfig/default/ld.config.txt
+ chmod 644 /linkerconfig/default/ld.config.txt
+
+ # Mount bootstrap linker configuration as current
+ mount none /linkerconfig/bootstrap /linkerconfig bind rec
start ueventd
@@ -49,6 +58,9 @@
# the libraries are available to the processes started after this statement.
exec_start apexd-bootstrap
+ # Generate linker config based on apex mounted in bootstrap namespace
+ update_linker_config
+
# These must already exist by the time boringssl_self_test32 / boringssl_self_test64 run.
mkdir /dev/boringssl 0755 root root
mkdir /dev/boringssl/selftest 0755 root root
@@ -650,12 +662,35 @@
mkdir /data/user 0711 system system encryption=None
mkdir /data/user_de 0711 system system encryption=None
- symlink /data/data /data/user/0
+
+ # Unlink /data/user/0 if we previously symlink it to /data/data
+ rm /data/user/0
+
+ # Bind mount /data/user/0 to /data/data
+ mkdir /data/user/0 0700 system system encryption=None
+ mount none /data/data /data/user/0 bind rec
# Special-case /data/media/obb per b/64566063
mkdir /data/media 0770 media_rw media_rw encryption=None
mkdir /data/media/obb 0770 media_rw media_rw encryption=Attempt
+ # A tmpfs directory, which will contain all apps CE DE data directory that
+ # bind mount from the original source.
+ chown root root /data_mirror
+ chmod 0700 /data_mirror
+ mount tmpfs tmpfs /data_mirror mode=0700,uid=0,gid=1000 nodev noexec nosuid
+ restorecon /data_mirror
+ mkdir /data_mirror/data_ce 0700 root root
+ mkdir /data_mirror/data_de 0700 root root
+
+ # Create CE and DE data directory for default volume
+ mkdir /data_mirror/data_ce/null 0700 root root
+ mkdir /data_mirror/data_de/null 0700 root root
+
+ # Bind mount CE and DE data directory to mirror's default volume directory
+ mount none /data/user /data_mirror/data_ce/null bind rec
+ mount none /data/user_de /data_mirror/data_de/null bind rec
+
mkdir /data/cache 0770 system cache encryption=Require
mkdir /data/cache/recovery 0770 system cache
mkdir /data/cache/backup_stage 0700 system system
@@ -665,6 +700,9 @@
mkdir /data/rollback 0700 system system encryption=DeleteIfNecessary
mkdir /data/rollback-observer 0700 system system encryption=DeleteIfNecessary
+ # Create root dir for Incremental Service
+ mkdir /data/incremental 0770 system system encryption=None
+
# Wait for apexd to finish activating APEXes before starting more processes.
wait_for_prop apexd.status ready
perform_apex_config
@@ -940,9 +978,12 @@
on userspace-reboot-requested
# TODO(b/135984674): reset all necessary properties here.
- setprop sys.boot_completed 0
- setprop sys.init.updatable_crashing 0
+ setprop sys.boot_completed ""
+ setprop sys.init.updatable_crashing ""
+ setprop sys.init.updatable_crashing_process_name ""
setprop apexd.status ""
+ setprop sys.user.0.ce_available ""
+ setprop sys.shutdown.requested ""
on userspace-reboot-fs-remount
# Make sure that vold is running.
diff --git a/rootdir/init.usb.rc b/rootdir/init.usb.rc
index a1888fc..02d34ba 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -19,7 +19,9 @@
updatable
seclabel u:r:adbd:s0
-on boot
+# Set default value on sys.usb.configfs early in boot sequence. It will be
+# overridden in `on boot` action of init.hardware.rc.
+on init
setprop sys.usb.configfs 0
# Used to disable USB when switching states
@@ -133,3 +135,8 @@
on property:sys.usb.typec.power_role=sink
write /sys/class/dual_role_usb/otg_default/power_role ${sys.usb.typec.power_role}
setprop sys.usb.typec.state ${sys.usb.typec.power_role}
+
+on userspace-reboot-requested
+ setprop sys.usb.config ""
+ setprop sys.usb.configfs ""
+ setprop sys.usb.state ""
diff --git a/rootdir/ld_config_backward_compatibility_check.py b/rootdir/ld_config_backward_compatibility_check.py
deleted file mode 100755
index 1a27578..0000000
--- a/rootdir/ld_config_backward_compatibility_check.py
+++ /dev/null
@@ -1,177 +0,0 @@
-#!/usr/bin/env python
-#
-# 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 glob
-import os.path
-import re
-import sys
-
-PREBUILTS_VNDK_DIR = "prebuilts/vndk"
-VENDOR_DIRECTORIES = ('/vendor', '/odm')
-
-def find_latest_vndk_snapshot_version():
- """Returns latest vndk snapshot version in current source tree.
- It will skip the test if the snapshot directories are not found.
-
- Returns:
- latest_version: string
- """
- vndk_dir_list = glob.glob(PREBUILTS_VNDK_DIR + "/v*")
- if not vndk_dir_list:
- """Exit without error because we may have source trees that do not include
- VNDK snapshot directories in it.
- """
- sys.exit(0)
- vndk_ver_list = [re.match(r".*/v(\d+)", vndk_dir).group(1)
- for vndk_dir in vndk_dir_list]
- latest_version = max(vndk_ver_list)
- if latest_version == '27':
- """Exit without error because VNDK v27 is not using ld.config.txt template
- """
- sys.exit(0)
- return latest_version
-
-def get_vendor_configuration(ld_config_file):
- """Reads the ld.config.txt file to parse the namespace configurations.
- It finds the configurations that include vendor directories.
-
- Args:
- ld_config_file: string, path (relative to build top) of the ld.config.txt
- file.
- Returns:
- configs: dict{string:[string]}, dictionary of namespace configurations.
- it has 'section + property' names as keys and the directory list
- as values.
- """
- try:
- conf_file = open(ld_config_file)
- except IOError:
- print("error: could not read %s" % ld_config_file)
- sys.exit(1)
-
- configs = dict()
- current_section = None
-
- with conf_file:
- for line in conf_file:
- # ignore comments
- found = line.find('#')
- if found != -1:
- line = line[:found]
- line = line.strip()
- if not line:
- continue
-
- if line[0] == '[' and line[-1] == ']':
- # new section started
- current_section = line[1:-1]
- continue
-
- if current_section == None:
- continue
-
- found = line.find('+=')
- opr_len = 2
- if found == -1:
- found = line.find('=')
- opr_len = 1
- if found == -1:
- continue
-
- namespace = line[:found].strip()
- if not namespace.endswith(".paths"):
- # check ".paths" only
- continue
- namespace = '[' + current_section + ']' + namespace
- values = line[found + opr_len:].strip()
- directories = values.split(':')
-
- for directory in directories:
- if any(vendor_dir in directory for vendor_dir in VENDOR_DIRECTORIES):
- if namespace in configs:
- configs[namespace].append(directory)
- else:
- configs[namespace] = [directory]
-
- return configs
-
-def get_snapshot_config(version):
- """Finds the ld.config.{version}.txt file from the VNDK snapshot directory.
- In the vndk prebuilt directory (prebuilts/vndk/v{version}), it searches
- {arch}/configs/ld.config.{version}.txt file, where {arch} is one of ('arm64',
- 'arm', 'x86_64', 'x86').
-
- Args:
- version: string, the VNDK snapshot version to search.
- Returns:
- ld_config_file: string, relative path to ld.config.{version}.txt
- """
- arch_list = ('arm64', 'arm', 'x86_64', 'x86')
- for arch in arch_list:
- ld_config_file = (PREBUILTS_VNDK_DIR
- + "/v{0}/{1}/configs/ld.config.{0}.txt".format(version, arch))
- if os.path.isfile(ld_config_file):
- return ld_config_file
- print("error: cannot find ld.config.{0}.txt file in snapshot v{0}"
- .format(version))
- sys.exit(1)
-
-def check_backward_compatibility(ld_config, vndk_snapshot_version):
- """Checks backward compatibility for current ld.config.txt file with the
- old ld.config.txt file. If any of the vendor directories in the old namespace
- configurations are missing, the test will fail. It is allowed to have new
- vendor directories in current ld.config.txt file.
-
- Args:
- ld_config: string, relative path to current ld.config.txt file.
- vndk_snapshot_version: string, the VNDK snapshot version that has an old
- ld.config.txt file to compare.
- Returns:
- result: bool, True if the current configuration is backward compatible.
- """
- current_config = get_vendor_configuration(ld_config)
- old_config = get_vendor_configuration(
- get_snapshot_config(vndk_snapshot_version))
- for namespace in old_config:
- if namespace not in current_config:
- print("error: cannot find %s which was provided in ld.config.%s.txt"
- % (namespace, vndk_snapshot_version))
- return False
- for path in old_config[namespace]:
- if not path in current_config[namespace]:
- print("error: %s for %s in ld.config.%s.txt are missing in %s"
- % (path, namespace, vndk_snapshot_version, ld_config))
- return False
- return True
-
-def main():
- if len(sys.argv) != 2:
- print ("Usage: %s target_ld_config_txt_file_name" % sys.argv[0])
- sys.exit(1)
-
- latest_vndk_snapshot_version = find_latest_vndk_snapshot_version()
- if not check_backward_compatibility(sys.argv[1],
- latest_vndk_snapshot_version):
- print("error: %s has backward incompatible changes to old "
- "vendor partition." % sys.argv[1])
- sys.exit(1)
-
- # Current ld.config.txt file is backward compatible
- sys.exit(0)
-
-if __name__ == '__main__':
- main()
diff --git a/rootdir/update_and_install_ld_config.mk b/rootdir/update_and_install_ld_config.mk
deleted file mode 100644
index 44f7b65..0000000
--- a/rootdir/update_and_install_ld_config.mk
+++ /dev/null
@@ -1,207 +0,0 @@
-#####################################################################
-# Builds linker config file, ld.config.txt, from the specified template
-# under $(LOCAL_PATH)/etc/*.
-#
-# Inputs:
-# (expected to follow an include of $(BUILD_SYSTEM)/base_rules.mk)
-# ld_config_template: template linker config file to use,
-# e.g. $(LOCAL_PATH)/etc/ld.config.txt
-# vndk_version: version of the VNDK library lists used to update the
-# template linker config file, e.g. 28
-# lib_list_from_prebuilts: should be set to 'true' if the VNDK library
-# lists should be read from /prebuilts/vndk/*
-# libz_is_llndk: should be set to 'true' if libz must be included in
-# llndk and not in vndk-sp
-# Outputs:
-# Builds and installs ld.config.$VER.txt or ld.config.vndk_lite.txt
-#####################################################################
-
-# Read inputs
-ld_config_template := $(strip $(ld_config_template))
-check_backward_compatibility := $(strip $(check_backward_compatibility))
-vndk_version := $(strip $(vndk_version))
-lib_list_from_prebuilts := $(strip $(lib_list_from_prebuilts))
-libz_is_llndk := $(strip $(libz_is_llndk))
-
-my_vndk_use_core_variant := $(TARGET_VNDK_USE_CORE_VARIANT)
-ifeq ($(lib_list_from_prebuilts),true)
-my_vndk_use_core_variant := false
-endif
-
-compatibility_check_script := \
- $(LOCAL_PATH)/ld_config_backward_compatibility_check.py
-intermediates_dir := $(call intermediates-dir-for,ETC,$(LOCAL_MODULE))
-library_lists_dir := $(intermediates_dir)
-ifeq ($(lib_list_from_prebuilts),true)
- library_lists_dir := prebuilts/vndk/v$(vndk_version)/$(TARGET_ARCH)/configs
-endif
-
-llndk_libraries_file := $(library_lists_dir)/llndk.libraries.$(vndk_version).txt
-vndksp_libraries_file := $(library_lists_dir)/vndksp.libraries.$(vndk_version).txt
-vndkcore_libraries_file := $(library_lists_dir)/vndkcore.libraries.$(vndk_version).txt
-vndkprivate_libraries_file := $(library_lists_dir)/vndkprivate.libraries.$(vndk_version).txt
-llndk_moved_to_apex_libraries_file := $(library_lists_dir)/llndkinapex.libraries.txt
-ifeq ($(my_vndk_use_core_variant),true)
-vndk_using_core_variant_libraries_file := $(library_lists_dir)/vndk_using_core_variant.libraries.$(vndk_version).txt
-endif
-
-sanitizer_runtime_libraries := $(call normalize-path-list,$(addsuffix .so,\
- $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
- $(HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
- $(UBSAN_RUNTIME_LIBRARY) \
- $(TSAN_RUNTIME_LIBRARY) \
- $(2ND_ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
- $(2ND_HWADDRESS_SANITIZER_RUNTIME_LIBRARY) \
- $(2ND_UBSAN_RUNTIME_LIBRARY) \
- $(2ND_TSAN_RUNTIME_LIBRARY)))
-# If BOARD_VNDK_VERSION is not defined, VNDK version suffix will not be used.
-vndk_version_suffix := $(if $(vndk_version),-$(vndk_version))
-
-ifneq ($(lib_list_from_prebuilts),true)
-ifeq ($(libz_is_llndk),true)
- llndk_libraries_list := $(LLNDK_LIBRARIES) libz
- vndksp_libraries_list := $(filter-out libz,$(VNDK_SAMEPROCESS_LIBRARIES))
-else
- llndk_libraries_list := $(LLNDK_LIBRARIES)
- vndksp_libraries_list := $(VNDK_SAMEPROCESS_LIBRARIES)
-endif
-
-# LLNDK libraries that has been moved to an apex package and no longer are present on
-# /system image.
-llndk_libraries_moved_to_apex_list:=$(LLNDK_MOVED_TO_APEX_LIBRARIES)
-
-# Returns the unique installed basenames of a module, or module.so if there are
-# none. The guess is to handle cases like libc, where the module itself is
-# marked uninstallable but a symlink is installed with the name libc.so.
-# $(1): list of libraries
-# $(2): suffix to to add to each library (not used for guess)
-define module-installed-files-or-guess
-$(foreach lib,$(1),$(or $(strip $(sort $(notdir $(call module-installed-files,$(lib)$(2))))),$(lib).so))
-endef
-
-# $(1): list of libraries
-# $(2): suffix to add to each library
-# $(3): output file to write the list of libraries to
-define write-libs-to-file
-$(3): PRIVATE_LIBRARIES := $(1)
-$(3): PRIVATE_SUFFIX := $(2)
-$(3):
- echo -n > $$@ && $$(foreach so,$$(call module-installed-files-or-guess,$$(PRIVATE_LIBRARIES),$$(PRIVATE_SUFFIX)),echo $$(so) >> $$@;)
-endef
-$(eval $(call write-libs-to-file,$(llndk_libraries_list),,$(llndk_libraries_file)))
-$(eval $(call write-libs-to-file,$(vndksp_libraries_list),.vendor,$(vndksp_libraries_file)))
-$(eval $(call write-libs-to-file,$(VNDK_CORE_LIBRARIES),.vendor,$(vndkcore_libraries_file)))
-$(eval $(call write-libs-to-file,$(VNDK_PRIVATE_LIBRARIES),.vendor,$(vndkprivate_libraries_file)))
-ifeq ($(my_vndk_use_core_variant),true)
-$(eval $(call write-libs-to-file,$(VNDK_USING_CORE_VARIANT_LIBRARIES),,$(vndk_using_core_variant_libraries_file)))
-endif
-endif # ifneq ($(lib_list_from_prebuilts),true)
-
-# Given a file with a list of libs, filter-out the VNDK private libraries
-# and write resulting list to a new file in "a:b:c" format
-#
-# $(1): libs file from which to filter-out VNDK private libraries
-# $(2): output file with the filtered list of lib names
-$(LOCAL_BUILT_MODULE): private-filter-out-private-libs = \
- paste -sd ":" $(1) > $(2) && \
- while read -r privatelib; do sed -i.bak "s/$$privatelib//" $(2) ; done < $(PRIVATE_VNDK_PRIVATE_LIBRARIES_FILE) && \
- sed -i.bak -e 's/::\+/:/g ; s/^:\+// ; s/:\+$$//' $(2) && \
- rm -f $(2).bak
-
-# # Given a file with a list of libs in "a:b:c" format, filter-out the LLNDK libraries migrated into apex file
-# # and write resulting list to a new file in "a:b:c" format
- $(LOCAL_BUILT_MODULE): private-filter-out-llndk-in-apex-libs = \
- for lib in $(PRIVATE_LLNDK_LIBRARIES_MOVED_TO_APEX_LIST); do sed -i.bak s/$$lib.so// $(1); done && \
- sed -i.bak -e 's/::\+/:/g ; s/^:\+// ; s/:\+$$//' $(1) && \
- rm -f $(1).bak
-
-$(LOCAL_BUILT_MODULE): PRIVATE_LLNDK_LIBRARIES_FILE := $(llndk_libraries_file)
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_SP_LIBRARIES_FILE := $(vndksp_libraries_file)
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_CORE_LIBRARIES_FILE := $(vndkcore_libraries_file)
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_PRIVATE_LIBRARIES_FILE := $(vndkprivate_libraries_file)
-$(LOCAL_BUILT_MODULE): PRIVATE_SANITIZER_RUNTIME_LIBRARIES := $(sanitizer_runtime_libraries)
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_VERSION_SUFFIX := $(vndk_version_suffix)
-$(LOCAL_BUILT_MODULE): PRIVATE_INTERMEDIATES_DIR := $(intermediates_dir)
-$(LOCAL_BUILT_MODULE): PRIVATE_COMP_CHECK_SCRIPT := $(compatibility_check_script)
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_VERSION_TAG := \#VNDK$(vndk_version)\#
-$(LOCAL_BUILT_MODULE): PRIVATE_LLNDK_LIBRARIES_MOVED_TO_APEX_LIST := $(llndk_libraries_moved_to_apex_list)
-deps := $(llndk_libraries_file) $(vndksp_libraries_file) $(vndkcore_libraries_file) \
- $(vndkprivate_libraries_file)
-ifeq ($(check_backward_compatibility),true)
-deps += $(compatibility_check_script) $(wildcard prebuilts/vndk/*/*/configs/ld.config.*.txt)
-endif
-ifeq ($(my_vndk_use_core_variant),true)
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_USING_CORE_VARIANT_LIBRARIES_FILE := $(vndk_using_core_variant_libraries_file)
-deps += $(vndk_using_core_variant_libraries_file)
-endif
-
-$(LOCAL_BUILT_MODULE): $(ld_config_template) $(deps)
- @echo "Generate: $< -> $@"
-ifeq ($(check_backward_compatibility),true)
- @echo "Checking backward compatibility..."
- $(hide) $(PRIVATE_COMP_CHECK_SCRIPT) $<
-endif
- @mkdir -p $(dir $@)
- $(call private-filter-out-private-libs,$(PRIVATE_LLNDK_LIBRARIES_FILE),$(PRIVATE_INTERMEDIATES_DIR)/llndk_filtered)
- $(call private-filter-out-llndk-in-apex-libs,$(PRIVATE_INTERMEDIATES_DIR)/llndk_filtered)
- $(hide) sed -e "s?%LLNDK_LIBRARIES%?$$(cat $(PRIVATE_INTERMEDIATES_DIR)/llndk_filtered)?g" $< >$@
- $(call private-filter-out-private-libs,$(PRIVATE_VNDK_SP_LIBRARIES_FILE),$(PRIVATE_INTERMEDIATES_DIR)/vndksp_filtered)
- $(hide) sed -i.bak -e "s?%VNDK_SAMEPROCESS_LIBRARIES%?$$(cat $(PRIVATE_INTERMEDIATES_DIR)/vndksp_filtered)?g" $@
- $(call private-filter-out-private-libs,$(PRIVATE_VNDK_CORE_LIBRARIES_FILE),$(PRIVATE_INTERMEDIATES_DIR)/vndkcore_filtered)
- $(hide) sed -i.bak -e "s?%VNDK_CORE_LIBRARIES%?$$(cat $(PRIVATE_INTERMEDIATES_DIR)/vndkcore_filtered)?g" $@
-
-ifeq ($(my_vndk_use_core_variant),true)
- $(call private-filter-out-private-libs,$(PRIVATE_VNDK_USING_CORE_VARIANT_LIBRARIES_FILE),$(PRIVATE_INTERMEDIATES_DIR)/vndk_using_core_variant_filtered)
- $(hide) sed -i.bak -e "s?%VNDK_IN_SYSTEM_NS%?,vndk_in_system?g" $@
- $(hide) sed -i.bak -e "s?%VNDK_USING_CORE_VARIANT_LIBRARIES%?$$(cat $(PRIVATE_INTERMEDIATES_DIR)/vndk_using_core_variant_filtered)?g" $@
-else
- $(hide) sed -i.bak -e "s?%VNDK_IN_SYSTEM_NS%??g" $@
- # Unlike LLNDK or VNDK-SP, VNDK_USING_CORE_VARIANT_LIBRARIES can be nothing
- # if TARGET_VNDK_USE_CORE_VARIANT is not set. In this case, we need to remove
- # the entire line in the linker config so that we are not left with a line
- # like:
- # namespace.vndk.link.vndk_in_system.shared_libs =
- $(hide) sed -i.bak -e 's?^.*= %VNDK_USING_CORE_VARIANT_LIBRARIES%$$??' $@
-endif
-
- $(hide) echo -n > $(PRIVATE_INTERMEDIATES_DIR)/private_llndk && \
- while read -r privatelib; \
- do (grep $$privatelib $(PRIVATE_LLNDK_LIBRARIES_FILE) || true) >> $(PRIVATE_INTERMEDIATES_DIR)/private_llndk ; \
- done < $(PRIVATE_VNDK_PRIVATE_LIBRARIES_FILE) && \
- paste -sd ":" $(PRIVATE_INTERMEDIATES_DIR)/private_llndk | \
- sed -i.bak -e "s?%PRIVATE_LLNDK_LIBRARIES%?$$(cat -)?g" $@
-
- $(hide) sed -i.bak -e "s?%SANITIZER_RUNTIME_LIBRARIES%?$(PRIVATE_SANITIZER_RUNTIME_LIBRARIES)?g" $@
- $(hide) sed -i.bak -e "s?%VNDK_VER%?$(PRIVATE_VNDK_VERSION_SUFFIX)?g" $@
- $(hide) sed -i.bak -e "s?%PRODUCT%?$(TARGET_COPY_OUT_PRODUCT)?g" $@
- $(hide) sed -i.bak -e "s?%SYSTEM_EXT%?$(TARGET_COPY_OUT_SYSTEM_EXT)?g" $@
- $(hide) sed -i.bak -e "s?^$(PRIVATE_VNDK_VERSION_TAG)??g" $@
- $(hide) sed -i.bak "/^\#VNDK[0-9]\{2\}\#.*$$/d" $@
- $(hide) rm -f $@.bak
-
-ld_config_template :=
-check_backward_compatibility :=
-vndk_version :=
-lib_list_from_prebuilts :=
-libz_is_llndk :=
-compatibility_check_script :=
-intermediates_dir :=
-library_lists_dir :=
-llndk_libraries_file :=
-llndk_moved_to_apex_libraries_file :=
-vndksp_libraries_file :=
-vndkcore_libraries_file :=
-vndkprivate_libraries_file :=
-deps :=
-sanitizer_runtime_libraries :=
-vndk_version_suffix :=
-llndk_libraries_list :=
-vndksp_libraries_list :=
-write-libs-to-file :=
-
-ifeq ($(my_vndk_use_core_variant),true)
-vndk_using_core_variant_libraries_file :=
-vndk_using_core_variant_libraries_list :=
-endif
-
-my_vndk_use_core_variant :=
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index ec4f6ab..b5a5fb6 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -12,6 +12,7 @@
required: [
"auditctl",
"awk",
+ "bc",
"bzip2",
"ldd",
"logwrapper",
diff --git a/trusty/storage/proxy/rpmb.c b/trusty/storage/proxy/rpmb.c
index 03b1099..7dfd0d0 100644
--- a/trusty/storage/proxy/rpmb.c
+++ b/trusty/storage/proxy/rpmb.c
@@ -361,6 +361,15 @@
return rc;
}
rpmb_fd = rc;
+
+ /* For UFS, it is prudent to check we have a sg device by calling an ioctl */
+ if (dev_type == UFS_RPMB) {
+ if ((ioctl(rpmb_fd, SG_GET_VERSION_NUM, &sg_version_num) < 0) ||
+ (sg_version_num < RPMB_MIN_SG_VERSION_NUM)) {
+ ALOGE("%s is not a sg device, or old sg driver\n", rpmb_devname);
+ return -1;
+ }
+ }
} else {
struct sockaddr_un unaddr;
struct sockaddr *addr = (struct sockaddr *)&unaddr;
@@ -382,15 +391,6 @@
}
}
- /* For UFS, it is prudent to check we hava a sg device by calling an ioctl */
- if (dev_type == UFS_RPMB) {
- if ((ioctl(rc, SG_GET_VERSION_NUM, &sg_version_num) < 0) ||
- (sg_version_num < RPMB_MIN_SG_VERSION_NUM)) {
- ALOGE("%s is not a sg device, or old sg driver\n", rpmb_devname);
- return -1;
- }
- }
- rpmb_fd = rc;
return 0;
}