Merge "[incremental] create /data/incremental in init.rc"
diff --git a/adb/SOCKET-ACTIVATION.txt b/adb/SOCKET-ACTIVATION.txt
new file mode 100644
index 0000000..4ef62ac
--- /dev/null
+++ b/adb/SOCKET-ACTIVATION.txt
@@ -0,0 +1,42 @@
+adb can be configured to work with systemd-style socket activation,
+allowing the daemon to start automatically when the adb control port
+is forwarded across a network. You need two files, placed in the usual
+systemd service directories (e.g., ~/.config/systemd/user for a user
+service).
+
+adb.service:
+
+--- START adb.service CUT HERE ---
+[Unit]
+Description=adb
+After=adb.socket
+Requires=adb.socket
+[Service]
+Type=simple
+# FD 3 is part of the systemd interface
+ExecStart=/path/to/adb server nodaemon -L acceptfd:3
+--- END adb.service CUT HERE ---
+
+--- START adb.socket CUT HERE ---
+[Unit]
+Description=adb
+PartOf=adb.service
+[Socket]
+ListenStream=127.0.0.1:5037
+Accept=no
+[Install]
+WantedBy=sockets.target
+--- END adb.socket CUT HERE ---
+
+After installing the adb service, the adb server will be started
+automatically on any connection to 127.0.0.1:5037 (the default adb
+control port), even after adb kill-server kills the server.
+
+Other "superserver" launcher systems (like macOS launchd) can be
+configured analogously. The important part is that adb be started with
+"server" and "nodaemon" command line arguments and that the listen
+address (passed to -L) name a file descriptor that's ready to
+accept(2) connections and that's already bound to the desired address
+and listening. inetd-style pre-accepted sockets do _not_ work in this
+configuration: the file descriptor passed to acceptfd must be the
+serve socket, not the accepted connection socket.
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/adb_client.cpp b/adb/client/adb_client.cpp
index d91ae35..f724cb5 100644
--- a/adb/client/adb_client.cpp
+++ b/adb/client/adb_client.cpp
@@ -222,7 +222,7 @@
int port;
std::string error;
if (!parse_tcp_socket_spec(__adb_server_socket_spec, nullptr, &port, nullptr, &error)) {
- LOG(FATAL) << "failed to parse server socket spec: " << error;
+ return {};
}
return adb_get_android_dir_path() + OS_PATH_SEPARATOR + "adb." + std::to_string(port);
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 0ffdbc2..813a8a9 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -107,6 +107,7 @@
" localfilesystem:<unix domain socket name>\n"
" dev:<character device name>\n"
" jdwp:<process pid> (remote only)\n"
+ " acceptfd:<fd> (listen only)\n"
" forward --remove LOCAL remove specific forward socket connection\n"
" forward --remove-all remove all forward socket connections\n"
" ppp TTY [PARAMETER...] run PPP over USB\n"
@@ -136,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/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 27e8c46..d17036c 100644
--- a/adb/socket_spec.cpp
+++ b/adb/socket_spec.cpp
@@ -16,6 +16,7 @@
#include "socket_spec.h"
+#include <limits>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -28,10 +29,12 @@
#include <cutils/sockets.h>
#include "adb.h"
+#include "adb_utils.h"
#include "sysdeps.h"
using namespace std::string_literals;
+using android::base::ConsumePrefix;
using android::base::StringPrintf;
#if defined(__linux__)
@@ -119,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";
@@ -131,7 +169,7 @@
return true;
}
}
- return spec.starts_with("tcp:");
+ return spec.starts_with("tcp:") || spec.starts_with("acceptfd:");
}
bool is_local_socket_spec(std::string_view spec) {
@@ -235,6 +273,9 @@
*error = "vsock is only supported on linux";
return false;
#endif // ADB_LINUX
+ } else if (address.starts_with("acceptfd:")) {
+ *error = "cannot connect to acceptfd";
+ return false;
}
for (const auto& it : kLocalSocketTypes) {
@@ -248,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;
}
@@ -269,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);
@@ -334,6 +387,46 @@
*error = "vsock is only supported on linux";
return -1;
#endif // ADB_LINUX
+ } else if (ConsumePrefix(&spec, "acceptfd:")) {
+#if ADB_WINDOWS
+ *error = "socket activation not supported under Windows";
+ return -1;
+#else
+ // We inherited the socket from some kind of launcher. It's already bound and
+ // listening. Return a copy of the FD instead of the FD itself so we implement the
+ // normal "listen" contract and can succeed more than once.
+ unsigned int fd_u;
+ if (!ParseUint(&fd_u, spec) || fd_u > std::numeric_limits<int>::max()) {
+ *error = "invalid fd";
+ return -1;
+ }
+ int fd = static_cast<int>(fd_u);
+ int flags = get_fd_flags(fd);
+ if (flags < 0) {
+ *error = android::base::StringPrintf("could not get flags of inherited fd %d: '%s'", fd,
+ strerror(errno));
+ return -1;
+ }
+ if (flags & FD_CLOEXEC) {
+ *error = android::base::StringPrintf("fd %d was not inherited from parent", fd);
+ return -1;
+ }
+
+ int dummy_sock_type;
+ socklen_t dummy_sock_type_size = sizeof(dummy_sock_type);
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &dummy_sock_type, &dummy_sock_type_size)) {
+ *error = android::base::StringPrintf("fd %d does not refer to a socket", fd);
+ return -1;
+ }
+
+ int new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+ if (new_fd < 0) {
+ *error = android::base::StringPrintf("could not dup inherited fd %d: '%s'", fd,
+ strerror(errno));
+ return -1;
+ }
+ return new_fd;
+#endif
}
for (const auto& it : kLocalSocketTypes) {
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/sysdeps.h b/adb/sysdeps.h
index 466c2ce..0c5a6b4 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -349,8 +349,15 @@
return c == '/';
}
+static __inline__ int get_fd_flags(borrowed_fd fd) {
+ return fcntl(fd.get(), F_GETFD);
+}
+
static __inline__ void close_on_exec(borrowed_fd fd) {
- fcntl(fd.get(), F_SETFD, FD_CLOEXEC);
+ int flags = get_fd_flags(fd);
+ if (flags >= 0 && (flags & FD_CLOEXEC) == 0) {
+ fcntl(fd.get(), F_SETFD, flags | FD_CLOEXEC);
+ }
}
// Open a file and return a file descriptor that may be used with unix_read(),
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/Android.bp b/base/Android.bp
index aeb8864..8351461 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -149,6 +149,7 @@
"logging_test.cpp",
"macros_test.cpp",
"mapped_file_test.cpp",
+ "no_destructor_test.cpp",
"parsedouble_test.cpp",
"parsebool_test.cpp",
"parseint_test.cpp",
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/no_destructor.h b/base/include/android-base/no_destructor.h
new file mode 100644
index 0000000..ce0dc9f
--- /dev/null
+++ b/base/include/android-base/no_destructor.h
@@ -0,0 +1,94 @@
+#pragma once
+
+/*
+ * 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>
+
+#include "android-base/macros.h"
+
+namespace android {
+namespace base {
+
+// A wrapper that makes it easy to create an object of type T with static
+// storage duration that:
+// - is only constructed on first access
+// - never invokes the destructor
+// in order to satisfy the styleguide ban on global constructors and
+// destructors.
+//
+// Runtime constant example:
+// const std::string& GetLineSeparator() {
+// // Forwards to std::string(size_t, char, const Allocator&) constructor.
+// static const base::NoDestructor<std::string> s(5, '-');
+// return *s;
+// }
+//
+// More complex initialization with a lambda:
+// const std::string& GetSessionNonce() {
+// static const base::NoDestructor<std::string> nonce([] {
+// std::string s(16);
+// crypto::RandString(s.data(), s.size());
+// return s;
+// }());
+// return *nonce;
+// }
+//
+// NoDestructor<T> stores the object inline, so it also avoids a pointer
+// indirection and a malloc. Also note that since C++11 static local variable
+// initialization is thread-safe and so is this pattern. Code should prefer to
+// use NoDestructor<T> over:
+// - A function scoped static T* or T& that is dynamically initialized.
+// - A global base::LazyInstance<T>.
+//
+// Note that since the destructor is never run, this *will* leak memory if used
+// as a stack or member variable. Furthermore, a NoDestructor<T> should never
+// have global scope as that may require a static initializer.
+template <typename T>
+class NoDestructor {
+ public:
+ // Not constexpr; just write static constexpr T x = ...; if the value should
+ // be a constexpr.
+ template <typename... Args>
+ explicit NoDestructor(Args&&... args) {
+ new (storage_) T(std::forward<Args>(args)...);
+ }
+
+ // Allows copy and move construction of the contained type, to allow
+ // construction from an initializer list, e.g. for std::vector.
+ explicit NoDestructor(const T& x) { new (storage_) T(x); }
+ explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); }
+
+ NoDestructor(const NoDestructor&) = delete;
+ NoDestructor& operator=(const NoDestructor&) = delete;
+
+ ~NoDestructor() = default;
+
+ const T& operator*() const { return *get(); }
+ T& operator*() { return *get(); }
+
+ const T* operator->() const { return get(); }
+ T* operator->() { return get(); }
+
+ const T* get() const { return reinterpret_cast<const T*>(storage_); }
+ T* get() { return reinterpret_cast<T*>(storage_); }
+
+ private:
+ alignas(T) char storage_[sizeof(T)];
+};
+
+} // namespace base
+} // namespace android
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/base/no_destructor_test.cpp b/base/no_destructor_test.cpp
new file mode 100644
index 0000000..f19468a
--- /dev/null
+++ b/base/no_destructor_test.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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 "android-base/no_destructor.h"
+
+#include <gtest/gtest.h>
+
+struct __attribute__((packed)) Bomb {
+ Bomb() : magic_(123) {}
+
+ ~Bomb() { exit(42); }
+
+ int get() const { return magic_; }
+
+ private:
+ [[maybe_unused]] char padding_;
+ int magic_;
+};
+
+TEST(no_destructor, bomb) {
+ ASSERT_EXIT(({
+ {
+ Bomb b;
+ if (b.get() != 123) exit(1);
+ }
+
+ exit(0);
+ }),
+ ::testing::ExitedWithCode(42), "");
+}
+
+TEST(no_destructor, defused) {
+ ASSERT_EXIT(({
+ {
+ android::base::NoDestructor<Bomb> b;
+ if (b->get() != 123) exit(1);
+ }
+
+ exit(0);
+ }),
+ ::testing::ExitedWithCode(0), "");
+}
+
+TEST(no_destructor, operators) {
+ android::base::NoDestructor<Bomb> b;
+ const android::base::NoDestructor<Bomb>& c = b;
+ ASSERT_EQ(123, b.get()->get());
+ ASSERT_EQ(123, b->get());
+ ASSERT_EQ(123, (*b).get());
+ ASSERT_EQ(123, c.get()->get());
+ ASSERT_EQ(123, c->get());
+ ASSERT_EQ(123, (*c).get());
+}
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/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/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 2dc47bb..5c0401e 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -91,6 +91,7 @@
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
using android::base::Basename;
+using android::base::GetBoolProperty;
using android::base::Realpath;
using android::base::StartsWith;
using android::base::unique_fd;
@@ -1359,12 +1360,24 @@
// TODO(b/143970043): return different error codes based on which step failed.
int fs_mgr_remount_userdata_into_checkpointing(Fstab* fstab) {
- auto entry = GetMountedEntryForUserdata(fstab);
- if (entry == nullptr) {
+ 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;
+ }
+ auto fstab_entry = GetMountedEntryForUserdata(fstab);
+ if (fstab_entry == nullptr) {
LERROR << "Can't find /data in fstab";
return -1;
}
- if (!entry->fs_mgr_flags.checkpoint_blk && !entry->fs_mgr_flags.checkpoint_fs) {
+ if (!fstab_entry->fs_mgr_flags.checkpoint_blk && !fstab_entry->fs_mgr_flags.checkpoint_fs) {
LINFO << "Userdata doesn't support checkpointing. Nothing to do";
return 0;
}
@@ -1373,34 +1386,52 @@
LINFO << "Checkpointing not needed. Don't remount";
return 0;
}
- if (entry->fs_mgr_flags.checkpoint_fs) {
+ bool force_umount_for_f2fs =
+ GetBoolProperty("sys.init.userdata_remount.force_umount_f2fs", false);
+ if (fstab_entry->fs_mgr_flags.checkpoint_fs && !force_umount_for_f2fs) {
// Userdata is f2fs, simply remount it.
- if (!checkpoint_manager.Update(&(*entry))) {
+ if (!checkpoint_manager.Update(fstab_entry)) {
LERROR << "Failed to remount userdata in checkpointing mode";
return -1;
}
- if (mount(entry->blk_device.c_str(), entry->mount_point.c_str(), "none",
- MS_REMOUNT | entry->flags, entry->fs_options.c_str()) != 0) {
+ if (mount(block_device.c_str(), fstab_entry->mount_point.c_str(), "none",
+ MS_REMOUNT | fstab_entry->flags, fstab_entry->fs_options.c_str()) != 0) {
PERROR << "Failed to remount userdata in checkpointing mode";
return -1;
}
} else {
- // STOPSHIP(b/143970043): support remounting for ext4 + metadata encryption.
- if (should_use_metadata_encryption(*entry)) {
- LWARNING << "Remounting into checkpointing is not supported for metadata encrypted "
- << "ext4 userdata. Proceed with caution";
- return 0;
+ LINFO << "Unmounting /data before remounting into checkpointing mode";
+ // First make sure that all the bind-mounts on top of /data are unmounted.
+ for (const auto& entry : proc_mounts) {
+ if (entry.blk_device == block_device && entry.mount_point != "/data") {
+ LINFO << "Unmounting bind-mount " << entry.mount_point;
+ if (umount2(entry.mount_point.c_str(), UMOUNT_NOFOLLOW) != 0) {
+ PWARNING << "Failed to unmount " << entry.mount_point;
+ }
+ }
}
if (umount2("/data", UMOUNT_NOFOLLOW) != 0) {
PERROR << "Failed to umount /data";
return -1;
}
DeviceMapper& dm = DeviceMapper::Instance();
- // TODO(b/143970043): need to delete every dm-device under the one userdata is mounted on.
- if (!dm.DeleteDeviceIfExists("bow")) {
- LERROR << "Failed to delete dm-bow";
- return -1;
+ while (dm.IsDmBlockDevice(block_device)) {
+ auto next_device = dm.GetParentBlockDeviceByPath(block_device);
+ auto name = dm.GetDmDeviceNameByPath(block_device);
+ if (!name) {
+ LERROR << "Failed to get dm-name for " << block_device;
+ return -1;
+ }
+ LINFO << "Deleting " << block_device << " named " << *name;
+ if (!dm.DeleteDevice(*name, 3s)) {
+ return -1;
+ }
+ if (!next_device) {
+ LERROR << "Failed to find parent device for " << block_device;
+ }
+ block_device = *next_device;
}
+ LINFO << "Remounting /data";
// TODO(b/143970043): remove this hack after fs_mgr_mount_all is refactored.
int result = fs_mgr_mount_all(fstab, MOUNT_MODE_ONLY_USERDATA);
return result == FS_MGR_MNTALL_FAIL ? -1 : 0;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index c81a079..9697a4c 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -582,8 +582,7 @@
} // namespace
void TransformFstabForDsu(Fstab* fstab, const std::vector<std::string>& dsu_partitions) {
- static constexpr char kGsiKeys[] =
- "/avb/q-gsi.avbpubkey:/avb/r-gsi.avbpubkey:/avb/s-gsi.avbpubkey";
+ static constexpr char kDsuKeysDir[] = "/avb";
// Convert userdata
// Inherit fstab properties for userdata.
FstabEntry userdata;
@@ -629,29 +628,18 @@
.fs_type = "ext4",
.flags = MS_RDONLY,
.fs_options = "barrier=1",
- .avb_keys = kGsiKeys,
+ .avb_keys = kDsuKeysDir,
};
entry.fs_mgr_flags.wait = true;
entry.fs_mgr_flags.logical = true;
entry.fs_mgr_flags.first_stage_mount = true;
- // Use the system key which may be in the vbmeta or vbmeta_system
- // TODO: b/141284191
- entry.vbmeta_partition = "vbmeta";
- fstab->emplace_back(entry);
- entry.vbmeta_partition = "vbmeta_system";
- fstab->emplace_back(entry);
} else {
// If the corresponding partition exists, transform all its Fstab
// by pointing .blk_device to the DSU partition.
for (auto&& entry : entries) {
entry->blk_device = partition;
- if (entry->avb_keys.size() > 0) {
- entry->avb_keys += ":";
- }
- // If the DSU is signed by OEM, the original Fstab already has the information
- // required by avb, otherwise the DSU is GSI and will need the avb_keys as listed
- // below.
- entry->avb_keys += kGsiKeys;
+ // AVB keys for DSU should always be under kDsuKeysDir.
+ entry->avb_keys += kDsuKeysDir;
}
// Make sure the ext4 is included to support GSI.
auto partition_ext4 =
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 0579a3d..27971da 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,66 @@
return "auto";
}
-std::string fs_mgr_overlayfs_scratch_device() {
- if (!scratch_device_cache.empty()) return scratch_device_cache;
+enum class ScratchStrategy {
+ kNone,
+ // DAP device, use logical partitions.
+ kDynamicPartition,
+ // Retrofit DAP device, use super_<other>.
+ kSuperOther,
+ // Pre-DAP device, uses the other slot.
+ kSystemOther
+};
- // Is this a multiple super device (retrofit)?
+// Return the strategy this device must use for creating a scratch partition.
+static ScratchStrategy GetScratchStrategy(std::string* backing_device = nullptr) {
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) {
+ // Note: we do not check access() here, since in first-stage init we
+ // wouldn't have registed by-name symlinks for the device as it's
+ // normally not needed. The access checks elsewhere in this function
+ // are safe because system/super are always required.
+ if (backing_device) *backing_device = path;
+ return ScratchStrategy::kSuperOther;
+ }
+ if (fs_mgr_access(super_device)) {
+ if (backing_device) *backing_device = super_device;
+ return ScratchStrategy::kDynamicPartition;
+ }
+
+ auto other_slot = fs_mgr_get_other_slot_suffix();
+ if (!other_slot.empty()) {
+ path = kPhysicalDevice + "system" + other_slot;
+ if (fs_mgr_access(path)) {
+ if (backing_device) *backing_device = path;
+ return ScratchStrategy::kSystemOther;
}
}
- return scratch_device_cache = path;
+ return ScratchStrategy::kNone;
+}
+
+// Return the scratch device if it exists.
+static std::string GetScratchDevice() {
+ std::string device;
+ ScratchStrategy strategy = GetScratchStrategy(&device);
+
+ switch (strategy) {
+ case ScratchStrategy::kSuperOther:
+ case ScratchStrategy::kSystemOther:
+ return device;
+ case ScratchStrategy::kDynamicPartition: {
+ auto& dm = DeviceMapper::Instance();
+ auto partition_name = android::base::Basename(kScratchMountPoint);
+ if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
+ dm.GetDmDevicePathByName(partition_name, &device)) {
+ return device;
+ }
+ return "";
+ }
+ default:
+ return "";
+ }
}
bool fs_mgr_overlayfs_make_scratch(const std::string& scratch_device, const std::string& mnt_type) {
@@ -878,16 +914,15 @@
}
}
-// 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(const Fstab& fstab, 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;
@@ -897,7 +932,6 @@
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 +1012,25 @@
return true;
}
+bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
+ bool* partition_exists, bool* change) {
+ auto strategy = GetScratchStrategy();
+ if (strategy == ScratchStrategy::kDynamicPartition) {
+ return CreateDynamicScratch(fstab, scratch_device, partition_exists, change);
+ }
+
+ // The scratch partition can only be landed on a physical partition if we
+ // get here. If there are no viable candidates that are R/W, just return
+ // that there is no device.
+ *scratch_device = GetScratchDevice();
+ if (scratch_device->empty()) {
+ errno = ENXIO;
+ return false;
+ }
+ *partition_exists = true;
+ return true;
+}
+
// 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;
@@ -1066,6 +1119,25 @@
return candidates;
}
+static void TryMountScratch() {
+ auto scratch_device = GetScratchDevice();
+ if (!fs_mgr_overlayfs_scratch_can_be_mounted(scratch_device)) {
+ return;
+ }
+ if (!WaitForFile(scratch_device, 10s)) {
+ return;
+ }
+ const auto mount_type = fs_mgr_overlayfs_scratch_mount_type();
+ if (!fs_mgr_overlayfs_mount_scratch(scratch_device, mount_type, true /* readonly */)) {
+ return;
+ }
+ auto has_overlayfs_dir = fs_mgr_access(kScratchMountPoint + kOverlayTopDir);
+ fs_mgr_overlayfs_umount_scratch();
+ if (has_overlayfs_dir) {
+ fs_mgr_overlayfs_mount_scratch(scratch_device, mount_type);
+ }
+}
+
bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
auto ret = false;
if (fs_mgr_overlayfs_invalid()) return ret;
@@ -1080,19 +1152,7 @@
}
if (scratch_can_be_mounted) {
scratch_can_be_mounted = false;
- auto scratch_device = fs_mgr_overlayfs_scratch_device();
- if (fs_mgr_overlayfs_scratch_can_be_mounted(scratch_device) &&
- WaitForFile(scratch_device, 10s)) {
- const auto mount_type = fs_mgr_overlayfs_scratch_mount_type();
- if (fs_mgr_overlayfs_mount_scratch(scratch_device, mount_type,
- true /* readonly */)) {
- auto has_overlayfs_dir = fs_mgr_access(kScratchMountPoint + kOverlayTopDir);
- fs_mgr_overlayfs_umount_scratch();
- if (has_overlayfs_dir) {
- fs_mgr_overlayfs_mount_scratch(scratch_device, mount_type);
- }
- }
- }
+ TryMountScratch();
}
if (fs_mgr_overlayfs_mount(mount_point)) ret = true;
}
@@ -1109,7 +1169,7 @@
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();
+ auto device = GetScratchDevice();
if (!fs_mgr_overlayfs_scratch_can_be_mounted(device)) break;
return {device};
}
@@ -1181,6 +1241,27 @@
return ret;
}
+static bool GetAndMapScratchDeviceIfNeeded(std::string* device) {
+ *device = GetScratchDevice();
+ if (!device->empty()) {
+ return true;
+ }
+
+ auto strategy = GetScratchStrategy();
+ if (strategy == ScratchStrategy::kDynamicPartition) {
+ 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,
+ };
+ return CreateLogicalPartition(params, device);
+ }
+ return false;
+}
+
// 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) {
@@ -1190,20 +1271,11 @@
// specific override entries.
auto mount_scratch = 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)) {
+ 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(
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index e7a3ff2..254fbed 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -16,11 +16,14 @@
#include "libdm/dm.h"
+#include <linux/dm-ioctl.h>
#include <sys/ioctl.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
+#include <chrono>
#include <functional>
+#include <string_view>
#include <thread>
#include <android-base/file.h>
@@ -79,14 +82,24 @@
return true;
}
-bool DeviceMapper::DeleteDeviceIfExists(const std::string& name) {
+bool DeviceMapper::DeleteDeviceIfExists(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms) {
if (GetState(name) == DmDeviceState::INVALID) {
return true;
}
- return DeleteDevice(name);
+ return DeleteDevice(name, timeout_ms);
}
-bool DeviceMapper::DeleteDevice(const std::string& name) {
+bool DeviceMapper::DeleteDeviceIfExists(const std::string& name) {
+ return DeleteDeviceIfExists(name, 0ms);
+}
+
+bool DeviceMapper::DeleteDevice(const std::string& name,
+ const std::chrono::milliseconds& timeout_ms) {
+ std::string unique_path;
+ if (!GetDeviceUniquePath(name, &unique_path)) {
+ LOG(ERROR) << "Failed to get unique path for device " << name;
+ }
struct dm_ioctl io;
InitIo(&io, name);
@@ -100,9 +113,23 @@
CHECK(io.flags & DM_UEVENT_GENERATED_FLAG)
<< "Didn't generate uevent for [" << name << "] removal";
+ if (timeout_ms <= std::chrono::milliseconds::zero()) {
+ return true;
+ }
+ if (unique_path.empty()) {
+ return false;
+ }
+ if (!WaitForFileDeleted(unique_path, timeout_ms)) {
+ LOG(ERROR) << "Timeout out waiting for " << unique_path << " to be deleted";
+ return false;
+ }
return true;
}
+bool DeviceMapper::DeleteDevice(const std::string& name) {
+ return DeleteDevice(name, 0ms);
+}
+
static std::string GenerateUuid() {
uuid_t uuid_bytes;
uuid_generate(uuid_bytes);
@@ -479,5 +506,78 @@
return std::string{spec.target_type, sizeof(spec.target_type)};
}
+static bool ExtractBlockDeviceName(const std::string& path, std::string* name) {
+ static constexpr std::string_view kDevBlockPrefix("/dev/block/");
+ if (android::base::StartsWith(path, kDevBlockPrefix)) {
+ *name = path.substr(kDevBlockPrefix.length());
+ return true;
+ }
+ return false;
+}
+
+bool DeviceMapper::IsDmBlockDevice(const std::string& path) {
+ std::string name;
+ if (!ExtractBlockDeviceName(path, &name)) {
+ return false;
+ }
+ return android::base::StartsWith(name, "dm-");
+}
+
+std::optional<std::string> DeviceMapper::GetDmDeviceNameByPath(const std::string& path) {
+ std::string name;
+ if (!ExtractBlockDeviceName(path, &name)) {
+ LOG(WARNING) << path << " is not a block device";
+ return std::nullopt;
+ }
+ if (!android::base::StartsWith(name, "dm-")) {
+ LOG(WARNING) << path << " is not a dm device";
+ return std::nullopt;
+ }
+ std::string dm_name_file = "/sys/block/" + name + "/dm/name";
+ std::string dm_name;
+ if (!android::base::ReadFileToString(dm_name_file, &dm_name)) {
+ PLOG(ERROR) << "Failed to read file " << dm_name_file;
+ return std::nullopt;
+ }
+ dm_name = android::base::Trim(dm_name);
+ return dm_name;
+}
+
+std::optional<std::string> DeviceMapper::GetParentBlockDeviceByPath(const std::string& path) {
+ std::string name;
+ if (!ExtractBlockDeviceName(path, &name)) {
+ LOG(WARNING) << path << " is not a block device";
+ return std::nullopt;
+ }
+ if (!android::base::StartsWith(name, "dm-")) {
+ // Reached bottom of the device mapper stack.
+ return std::nullopt;
+ }
+ auto slaves_dir = "/sys/block/" + name + "/slaves";
+ auto dir = std::unique_ptr<DIR, decltype(&closedir)>(opendir(slaves_dir.c_str()), closedir);
+ if (dir == nullptr) {
+ PLOG(ERROR) << "Failed to open: " << slaves_dir;
+ return std::nullopt;
+ }
+ std::string sub_device_name = "";
+ for (auto entry = readdir(dir.get()); entry; entry = readdir(dir.get())) {
+ if (entry->d_type != DT_LNK) continue;
+ if (!sub_device_name.empty()) {
+ LOG(ERROR) << "Too many slaves in " << slaves_dir;
+ return std::nullopt;
+ }
+ sub_device_name = entry->d_name;
+ }
+ if (sub_device_name.empty()) {
+ LOG(ERROR) << "No slaves in " << slaves_dir;
+ return std::nullopt;
+ }
+ 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/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index ed2fa83..b7f31bc 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -29,6 +29,7 @@
#include <thread>
#include <android-base/file.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <libdm/dm.h>
@@ -520,3 +521,87 @@
ASSERT_TRUE(target.Valid());
ASSERT_EQ(target.GetParameterString(), "AES-256-XTS abcdef0123456789 /dev/loop0 0");
}
+
+TEST(libdm, DeleteDeviceWithTimeout) {
+ unique_fd tmp(CreateTempFile("file_1", 4096));
+ ASSERT_GE(tmp, 0);
+ LoopDevice loop(tmp, 10s);
+ ASSERT_TRUE(loop.valid());
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop.device(), 0));
+ ASSERT_TRUE(table.valid());
+ TempDevice dev("libdm-test-dm-linear", table);
+ ASSERT_TRUE(dev.valid());
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+
+ std::string path;
+ ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path));
+ ASSERT_EQ(0, access(path.c_str(), F_OK));
+
+ ASSERT_TRUE(dm.DeleteDevice("libdm-test-dm-linear", 5s));
+ ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear"));
+ ASSERT_NE(0, access(path.c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(libdm, IsDmBlockDevice) {
+ unique_fd tmp(CreateTempFile("file_1", 4096));
+ ASSERT_GE(tmp, 0);
+ LoopDevice loop(tmp, 10s);
+ ASSERT_TRUE(loop.valid());
+ ASSERT_TRUE(android::base::StartsWith(loop.device(), "/dev/block"));
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop.device(), 0));
+ ASSERT_TRUE(table.valid());
+
+ TempDevice dev("libdm-test-dm-linear", table);
+ ASSERT_TRUE(dev.valid());
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ ASSERT_TRUE(dm.IsDmBlockDevice(dev.path()));
+ ASSERT_FALSE(dm.IsDmBlockDevice(loop.device()));
+}
+
+TEST(libdm, GetDmDeviceNameByPath) {
+ unique_fd tmp(CreateTempFile("file_1", 4096));
+ ASSERT_GE(tmp, 0);
+ LoopDevice loop(tmp, 10s);
+ ASSERT_TRUE(loop.valid());
+ ASSERT_TRUE(android::base::StartsWith(loop.device(), "/dev/block"));
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop.device(), 0));
+ ASSERT_TRUE(table.valid());
+
+ TempDevice dev("libdm-test-dm-linear", table);
+ ASSERT_TRUE(dev.valid());
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ // Not a dm device, GetDmDeviceNameByPath will return std::nullopt.
+ ASSERT_FALSE(dm.GetDmDeviceNameByPath(loop.device()));
+ auto name = dm.GetDmDeviceNameByPath(dev.path());
+ ASSERT_EQ("libdm-test-dm-linear", *name);
+}
+
+TEST(libdm, GetParentBlockDeviceByPath) {
+ unique_fd tmp(CreateTempFile("file_1", 4096));
+ ASSERT_GE(tmp, 0);
+ LoopDevice loop(tmp, 10s);
+ ASSERT_TRUE(loop.valid());
+ ASSERT_TRUE(android::base::StartsWith(loop.device(), "/dev/block"));
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop.device(), 0));
+ ASSERT_TRUE(table.valid());
+
+ TempDevice dev("libdm-test-dm-linear", table);
+ ASSERT_TRUE(dev.valid());
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ ASSERT_FALSE(dm.GetParentBlockDeviceByPath(loop.device()));
+ auto sub_block_device = dm.GetParentBlockDeviceByPath(dev.path());
+ ASSERT_EQ(loop.device(), *sub_block_device);
+}
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index e25ce7f..abe9c4c 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -90,6 +90,10 @@
// Returns 'true' on success, false otherwise.
bool DeleteDevice(const std::string& name);
bool DeleteDeviceIfExists(const std::string& name);
+ // Removes a device mapper device with the given name and waits for |timeout_ms| milliseconds
+ // for the corresponding block device to be deleted.
+ bool DeleteDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms);
+ bool DeleteDeviceIfExists(const std::string& name, const std::chrono::milliseconds& timeout_ms);
// Fetches and returns the complete state of the underlying device mapper
// device with given name.
@@ -201,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);
@@ -210,6 +216,19 @@
static std::string GetTargetType(const struct dm_target_spec& spec);
+ // Returns true if given path is a path to a dm block device.
+ bool IsDmBlockDevice(const std::string& path);
+
+ // Returns name of a dm-device with the given path, or std::nulloptr if given path is not a
+ // dm-device.
+ std::optional<std::string> GetDmDeviceNameByPath(const std::string& path);
+
+ // Returns a parent block device of a dm device with the given path, or std::nullopt if:
+ // * Given path doesn't correspond to a dm device.
+ // * A dm device is based on top of more than one block devices.
+ // * A failure occurred.
+ std::optional<std::string> GetParentBlockDeviceByPath(const std::string& path);
+
private:
// Maximum possible device mapper targets registered in the kernel.
// This is only used to read the list of targets from kernel so we allocate
diff --git a/fs_mgr/libdm/utility.cpp b/fs_mgr/libdm/utility.cpp
index eccf2fb..f252565 100644
--- a/fs_mgr/libdm/utility.cpp
+++ b/fs_mgr/libdm/utility.cpp
@@ -52,5 +52,15 @@
return WaitForCondition(condition, timeout_ms);
}
+bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds& timeout_ms) {
+ auto condition = [&]() -> WaitResult {
+ if (access(path.c_str(), F_OK) == 0 || errno != ENOENT) {
+ return WaitResult::Wait;
+ }
+ return WaitResult::Done;
+ };
+ return WaitForCondition(condition, timeout_ms);
+}
+
} // namespace dm
} // namespace android
diff --git a/fs_mgr/libdm/utility.h b/fs_mgr/libdm/utility.h
index f1dce9e..58fa96b 100644
--- a/fs_mgr/libdm/utility.h
+++ b/fs_mgr/libdm/utility.h
@@ -23,6 +23,7 @@
enum class WaitResult { Wait, Done, Fail };
bool WaitForFile(const std::string& path, const std::chrono::milliseconds& timeout_ms);
+bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds& timeout_ms);
bool WaitForCondition(const std::function<WaitResult()>& condition,
const std::chrono::milliseconds& timeout_ms);
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
new file mode 100644
index 0000000..8dbbf4c
--- /dev/null
+++ b/fs_mgr/libfiemap/Android.bp
@@ -0,0 +1,135 @@
+//
+// 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"],
+}
+
+cc_defaults {
+ name: "libfiemap_defaults",
+ defaults: ["fs_mgr_defaults"],
+ cflags: [
+ "-D_FILE_OFFSET_BITS=64",
+ "-Wall",
+ "-Werror",
+ ],
+
+ srcs: [
+ "fiemap_writer.cpp",
+ "image_manager.cpp",
+ "metadata.cpp",
+ "split_fiemap_writer.cpp",
+ "utility.cpp",
+ ],
+
+ static_libs: [
+ "libdm",
+ "libext2_uuid",
+ "libext4_utils",
+ "liblp",
+ "libfs_mgr",
+ ],
+
+ shared_libs: [
+ "libbase",
+ ],
+
+ header_libs: [
+ "libfiemap_headers",
+ "liblog_headers",
+ ],
+
+ export_shared_lib_headers: [
+ "libbase",
+ ],
+
+ export_header_lib_headers: [
+ "libfiemap_headers",
+ ],
+}
+
+// Open up a binder IImageManager interface.
+cc_library_static {
+ name: "libfiemap_binder",
+ defaults: ["libfiemap_defaults"],
+ srcs: [
+ "binder.cpp",
+ ],
+ whole_static_libs: [
+ "gsi_aidl_interface-cpp",
+ "libgsi",
+ ],
+ shared_libs: [
+ "libbinder",
+ ],
+}
+
+// Open up a passthrough IImageManager interface. Use libfiemap_binder whenever
+// possible. This should only be used when binder is not available.
+cc_library_static {
+ name: "libfiemap_passthrough",
+ defaults: ["libfiemap_defaults"],
+ recovery_available: true,
+ srcs: [
+ "passthrough.cpp",
+ ],
+}
+
+cc_test {
+ name: "fiemap_writer_test",
+ defaults: ["libfiemap_defaults"],
+ static_libs: [
+ "libbase",
+ "libdm",
+ "liblog",
+ ],
+
+ data: [
+ "testdata/unaligned_file",
+ "testdata/file_4k",
+ "testdata/file_32k",
+ ],
+
+ srcs: [
+ "fiemap_writer_test.cpp",
+ ],
+}
+
+cc_test {
+ name: "fiemap_image_test",
+ defaults: ["libfiemap_defaults"],
+ static_libs: [
+ "libdm",
+ "libext4_utils",
+ "libfs_mgr",
+ "liblp",
+ ],
+ shared_libs: [
+ "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..49779f4
--- /dev/null
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -0,0 +1,224 @@
+//
+// 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;
+
+ 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;
+}
+
+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..b5794d3
--- /dev/null
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -0,0 +1,795 @@
+/*
+ * 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 file_size, 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;
+ }
+
+ uint64_t available_bytes = sfs.f_bsize * sfs.f_bavail;
+ if (access(file_path.c_str(), F_OK) != 0 && available_bytes <= file_size) {
+ LOG(ERROR) << "Not enough free space in file system to create file of size : " << file_size;
+ 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, file_size, &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..fe2018d
--- /dev/null
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -0,0 +1,674 @@
+//
+// 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::DestroyLogicalPartition;
+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;
+}
+
+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..f05825c
--- /dev/null
+++ b/fs_mgr/libfiemap/image_test.cpp
@@ -0,0 +1,251 @@
+//
+// 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(), ""), "");
+}
+
+// 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::string path;
+ ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path));
+ ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-"));
+}
+
+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..5ff4628
--- /dev/null
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -0,0 +1,185 @@
+//
+// 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 <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;
+
+ // 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;
+
+ 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..597efe9
--- /dev/null
+++ b/fs_mgr/libfiemap/metadata.cpp
@@ -0,0 +1,196 @@
+//
+// 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);
+}
+
+} // namespace fiemap
+} // namespace android
diff --git a/fs_mgr/libfiemap/metadata.h b/fs_mgr/libfiemap/metadata.h
new file mode 100644
index 0000000..f0ce23e
--- /dev/null
+++ b/fs_mgr/libfiemap/metadata.h
@@ -0,0 +1,36 @@
+//
+// 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 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/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index 32702ae..bf51fe7 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -37,11 +37,9 @@
"libfstab",
],
shared_libs: [
+ "libbase",
"libcrypto",
],
- header_libs: [
- "libbase_headers",
- ],
target: {
darwin: {
enabled: false,
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index c4d7511..8770a6b 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -22,6 +22,7 @@
#include <sys/ioctl.h>
#include <sys/types.h>
+#include <algorithm>
#include <sstream>
#include <string>
#include <vector>
@@ -308,7 +309,18 @@
return nullptr;
}
- if (!ValidatePublicKeyBlob(public_key_data, Split(fstab_entry.avb_keys, ":"))) {
+ // fstab_entry.avb_keys might be either a directory containing multiple keys,
+ // or a string indicating multiple keys separated by ':'.
+ std::vector<std::string> allowed_avb_keys;
+ auto list_avb_keys_in_dir = ListFiles(fstab_entry.avb_keys);
+ if (list_avb_keys_in_dir) {
+ std::sort(list_avb_keys_in_dir->begin(), list_avb_keys_in_dir->end());
+ allowed_avb_keys = *list_avb_keys_in_dir;
+ } else {
+ allowed_avb_keys = Split(fstab_entry.avb_keys, ":");
+ }
+
+ if (!ValidatePublicKeyBlob(public_key_data, allowed_avb_keys)) {
avb_handle->status_ = AvbHandleStatus::kVerificationError;
LWARNING << "Found unknown public key used to sign " << fstab_entry.mount_point;
if (!allow_verification_error) {
diff --git a/fs_mgr/libfs_avb/tests/util_test.cpp b/fs_mgr/libfs_avb/tests/util_test.cpp
index 12b5acb..e64282b 100644
--- a/fs_mgr/libfs_avb/tests/util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/util_test.cpp
@@ -16,10 +16,12 @@
#include <unistd.h>
+#include <algorithm>
#include <future>
#include <string>
#include <thread>
+#include <android-base/strings.h>
#include <base/files/file_util.h>
#include "fs_avb_test_util.h"
@@ -29,6 +31,7 @@
using android::fs_mgr::BytesToHex;
using android::fs_mgr::FileWaitMode;
using android::fs_mgr::HexToBytes;
+using android::fs_mgr::ListFiles;
using android::fs_mgr::NibbleValue;
using android::fs_mgr::WaitForFile;
@@ -210,4 +213,102 @@
ASSERT_TRUE(base::DeleteFile(wait_path, false /* resursive */));
}
+TEST(BasicUtilTest, ListFiles) {
+ // Gets system tmp dir.
+ base::FilePath tmp_dir;
+ ASSERT_TRUE(GetTempDir(&tmp_dir));
+
+ // Creates a test dir for ListFiles testing.
+ base::FilePath test_dir;
+ ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir));
+
+ // Generates dummy files to list.
+ base::FilePath file_path_1 = test_dir.Append("1.txt");
+ ASSERT_TRUE(base::WriteFile(file_path_1, "1", 1));
+ base::FilePath file_path_2 = test_dir.Append("2.txt");
+ ASSERT_TRUE(base::WriteFile(file_path_2, "22", 2));
+ base::FilePath file_path_3 = test_dir.Append("3.txt");
+ ASSERT_TRUE(base::WriteFile(file_path_3, "333", 3));
+
+ // List files for comparison.
+ auto result = ListFiles(test_dir.value());
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.has_value());
+ auto files = result.value();
+ EXPECT_EQ(3UL, files.size());
+ // Sort them offline for comparison.
+ std::sort(files.begin(), files.end());
+ EXPECT_EQ(file_path_1.value(), files[0]);
+ EXPECT_EQ(file_path_2.value(), files[1]);
+ EXPECT_EQ(file_path_3.value(), files[2]);
+
+ ASSERT_TRUE(base::DeleteFile(test_dir, true /* resursive */));
+}
+
+TEST(BasicUtilTest, ListFilesShouldDiscardSymlink) {
+ // Gets system tmp dir.
+ base::FilePath tmp_dir;
+ ASSERT_TRUE(GetTempDir(&tmp_dir));
+
+ // Creates a test dir for ListFiles testing.
+ base::FilePath test_dir;
+ ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir));
+
+ // Generates dummy files to list.
+ base::FilePath file_path_1 = test_dir.Append("1.txt");
+ ASSERT_TRUE(base::WriteFile(file_path_1, "1", 1));
+ base::FilePath file_path_2 = test_dir.Append("2.txt");
+ ASSERT_TRUE(base::WriteFile(file_path_2, "22", 2));
+ // Creates a symlink and checks it won't be returned by ListFiles.
+ base::FilePath file_path_3 = test_dir.Append("3.txt");
+ base::FilePath non_existent_target = test_dir.Append("non_existent_target.txt");
+ ASSERT_TRUE(base::CreateSymbolicLink(non_existent_target, file_path_3));
+
+ // List files for comparison.
+ auto result = ListFiles(test_dir.value());
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.has_value());
+ auto files = result.value();
+ EXPECT_EQ(2UL, files.size()); // Should not include the symlink file.
+ // Sort them offline for comparison.
+ std::sort(files.begin(), files.end());
+ EXPECT_EQ(file_path_1.value(), files[0]);
+ EXPECT_EQ(file_path_2.value(), files[1]);
+
+ ASSERT_TRUE(base::DeleteFile(test_dir, true /* resursive */));
+}
+
+TEST(BasicUtilTest, ListFilesOpenDirFailure) {
+ // Gets system tmp dir.
+ base::FilePath tmp_dir;
+ ASSERT_TRUE(GetTempDir(&tmp_dir));
+
+ // Generates dummy files to list.
+ base::FilePath no_such_dir = tmp_dir.Append("not_such_dir");
+
+ auto fail = ListFiles(no_such_dir.value());
+ ASSERT_FALSE(fail);
+ EXPECT_EQ(ENOENT, fail.error().code());
+ EXPECT_TRUE(android::base::StartsWith(fail.error().message(), "Failed to opendir: "));
+}
+
+TEST(BasicUtilTest, ListFilesEmptyDir) {
+ // Gets system tmp dir.
+ base::FilePath tmp_dir;
+ ASSERT_TRUE(GetTempDir(&tmp_dir));
+
+ // Creates a test dir for ListFiles testing.
+ base::FilePath test_dir;
+ ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir));
+
+ // List files without sorting.
+ auto result = ListFiles(test_dir.value());
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result.has_value());
+ auto files = result.value();
+ EXPECT_EQ(0UL, files.size());
+
+ ASSERT_TRUE(base::DeleteFile(test_dir, true /* resursive */));
+}
+
} // namespace fs_avb_host_test
diff --git a/fs_mgr/libfs_avb/util.cpp b/fs_mgr/libfs_avb/util.cpp
index d214b5b..7783d04 100644
--- a/fs_mgr/libfs_avb/util.cpp
+++ b/fs_mgr/libfs_avb/util.cpp
@@ -16,10 +16,13 @@
#include "util.h"
+#include <dirent.h>
#include <sys/ioctl.h>
+#include <sys/types.h>
#include <thread>
+#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <linux/fs.h>
@@ -122,5 +125,23 @@
return ioctl(fd, BLKROSET, &ON) == 0;
}
+Result<std::vector<std::string>> ListFiles(const std::string& dir) {
+ struct dirent* de;
+ std::vector<std::string> files;
+
+ std::unique_ptr<DIR, int (*)(DIR*)> dirp(opendir(dir.c_str()), closedir);
+ if (!dirp) {
+ return ErrnoError() << "Failed to opendir: " << dir;
+ }
+
+ while ((de = readdir(dirp.get()))) {
+ if (de->d_type != DT_REG) continue;
+ std::string full_path = android::base::StringPrintf("%s/%s", dir.c_str(), de->d_name);
+ files.emplace_back(std::move(full_path));
+ }
+
+ return files;
+}
+
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/libfs_avb/util.h b/fs_mgr/libfs_avb/util.h
index 7763da5..427ab7c 100644
--- a/fs_mgr/libfs_avb/util.h
+++ b/fs_mgr/libfs_avb/util.h
@@ -18,6 +18,7 @@
#include <chrono>
#include <string>
+#include <vector>
#ifdef HOST_TEST
#include <base/logging.h>
@@ -25,6 +26,11 @@
#include <android-base/logging.h>
#endif
+#include <android-base/result.h>
+
+using android::base::ErrnoError;
+using android::base::Result;
+
#define FS_AVB_TAG "[libfs_avb]"
// Logs a message to kernel
@@ -60,5 +66,8 @@
bool SetBlockDeviceReadOnly(const std::string& blockdev);
+// Returns a list of file under the dir, no order is guaranteed.
+Result<std::vector<std::string>> ListFiles(const std::string& dir);
+
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 54350a5..7e7f393 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)) {
@@ -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..851f041 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -318,6 +318,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 +327,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..d3c9874 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
@@ -212,8 +215,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 +373,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..30c17e4 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.";
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 1d72c70..30d01a6 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -121,6 +121,34 @@
],
}
+cc_library_static {
+ name: "libsnapshot_test_helpers",
+ defaults: ["libsnapshot_defaults"],
+ export_include_dirs: [
+ "include_test",
+ ],
+ srcs: [
+ "test_helpers.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.boot@1.1",
+ "libcrypto",
+ ],
+ export_shared_lib_headers: [
+ "android.hardware.boot@1.1",
+ ],
+ header_libs: [
+ "libstorage_literals_headers",
+ ],
+ export_header_lib_headers: [
+ "libstorage_literals_headers",
+ ],
+ static_libs: [
+ "libgtest",
+ "libgmock",
+ ],
+}
+
cc_test {
name: "libsnapshot_test",
defaults: ["libsnapshot_defaults"],
@@ -144,6 +172,7 @@
"libgmock",
"liblp",
"libsnapshot",
+ "libsnapshot_test_helpers",
"libsparse",
"libz",
],
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/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
similarity index 100%
rename from fs_mgr/libsnapshot/test_helpers.h
rename to fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index eae6c35..9da3f05 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -18,9 +18,10 @@
#include <liblp/builder.h>
#include <liblp/property_fetcher.h>
+#include <libsnapshot/test_helpers.h>
+
#include "dm_snapshot_internals.h"
#include "partition_cow_creator.h"
-#include "test_helpers.h"
#include "utility.h"
using namespace android::fs_mgr;
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_metadata_updater_test.cpp b/fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp
index 4fd8759..337be4f 100644
--- a/fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp
@@ -24,7 +24,7 @@
#include <liblp/builder.h>
#include <storage_literals/storage_literals.h>
-#include "test_helpers.h"
+#include <libsnapshot/test_helpers.h>
using namespace android::storage_literals;
using android::fs_mgr::LpMetadata;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 9e5fef3..0f5af14 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -38,7 +38,7 @@
#include <storage_literals/storage_literals.h>
#include <android/snapshot/snapshot.pb.h>
-#include "test_helpers.h"
+#include <libsnapshot/test_helpers.h>
#include "utility.h"
namespace android {
@@ -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);
@@ -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/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 2d62347..f7f25af 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "test_helpers.h"
+#include <libsnapshot/test_helpers.h>
#include <android-base/file.h>
#include <android-base/logging.h>
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 997b2bc..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 \
@@ -73,9 +72,8 @@
LOCAL_REQUIRED_MODULES := \
adb_debug.prop \
-# Set up the same mount points on the ramdisk that system-as-root contains.
+# Set up the directories that first stage init mounts on.
LOCAL_POST_INSTALL_CMD := mkdir -p \
- $(TARGET_RAMDISK_OUT)/apex \
$(TARGET_RAMDISK_OUT)/debug_ramdisk \
$(TARGET_RAMDISK_OUT)/dev \
$(TARGET_RAMDISK_OUT)/mnt \
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 1028330..62a19ab 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -360,57 +360,61 @@
return {};
}
-// mkdir <path> [mode] [owner] [group] [<option> ...]
-static Result<void> do_mkdir(const BuiltinArguments& args) {
- auto options = ParseMkdir(args.args);
- if (!options) return options.error();
+static Result<void> make_dir_with_options(const MkdirOptions& options) {
std::string ref_basename;
- if (options->ref_option == "ref") {
+ if (options.ref_option == "ref") {
ref_basename = fscrypt_key_ref;
- } else if (options->ref_option == "per_boot_ref") {
+ } else if (options.ref_option == "per_boot_ref") {
ref_basename = fscrypt_key_per_boot_ref;
} else {
- return Error() << "Unknown key option: '" << options->ref_option << "'";
+ return Error() << "Unknown key option: '" << options.ref_option << "'";
}
struct stat mstat;
- if (lstat(options->target.c_str(), &mstat) != 0) {
+ if (lstat(options.target.c_str(), &mstat) != 0) {
if (errno != ENOENT) {
- return ErrnoError() << "lstat() failed on " << options->target;
+ return ErrnoError() << "lstat() failed on " << options.target;
}
- if (!make_dir(options->target, options->mode)) {
- return ErrnoErrorIgnoreEnoent() << "mkdir() failed on " << options->target;
+ if (!make_dir(options.target, options.mode)) {
+ return ErrnoErrorIgnoreEnoent() << "mkdir() failed on " << options.target;
}
- if (lstat(options->target.c_str(), &mstat) != 0) {
- return ErrnoError() << "lstat() failed on new " << options->target;
+ if (lstat(options.target.c_str(), &mstat) != 0) {
+ return ErrnoError() << "lstat() failed on new " << options.target;
}
}
if (!S_ISDIR(mstat.st_mode)) {
- return Error() << "Not a directory on " << options->target;
+ return Error() << "Not a directory on " << options.target;
}
- bool needs_chmod = (mstat.st_mode & ~S_IFMT) != options->mode;
- if ((options->uid != static_cast<uid_t>(-1) && options->uid != mstat.st_uid) ||
- (options->gid != static_cast<gid_t>(-1) && options->gid != mstat.st_gid)) {
- if (lchown(options->target.c_str(), options->uid, options->gid) == -1) {
- return ErrnoError() << "lchown failed on " << options->target;
+ bool needs_chmod = (mstat.st_mode & ~S_IFMT) != options.mode;
+ if ((options.uid != static_cast<uid_t>(-1) && options.uid != mstat.st_uid) ||
+ (options.gid != static_cast<gid_t>(-1) && options.gid != mstat.st_gid)) {
+ if (lchown(options.target.c_str(), options.uid, options.gid) == -1) {
+ return ErrnoError() << "lchown failed on " << options.target;
}
// chown may have cleared S_ISUID and S_ISGID, chmod again
needs_chmod = true;
}
if (needs_chmod) {
- if (fchmodat(AT_FDCWD, options->target.c_str(), options->mode, AT_SYMLINK_NOFOLLOW) == -1) {
- return ErrnoError() << "fchmodat() failed on " << options->target;
+ if (fchmodat(AT_FDCWD, options.target.c_str(), options.mode, AT_SYMLINK_NOFOLLOW) == -1) {
+ return ErrnoError() << "fchmodat() failed on " << options.target;
}
}
if (fscrypt_is_native()) {
- if (!FscryptSetDirectoryPolicy(ref_basename, options->fscrypt_action, options->target)) {
+ if (!FscryptSetDirectoryPolicy(ref_basename, options.fscrypt_action, options.target)) {
return reboot_into_recovery(
- {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + options->target});
+ {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + options.target});
}
}
return {};
}
+// mkdir <path> [mode] [owner] [group] [<option> ...]
+static Result<void> do_mkdir(const BuiltinArguments& args) {
+ auto options = ParseMkdir(args.args);
+ if (!options) return options.error();
+ return make_dir_with_options(*options);
+}
+
/* umount <path> */
static Result<void> do_umount(const BuiltinArguments& args) {
if (umount(args[1].c_str()) < 0) {
@@ -1172,7 +1176,7 @@
return {};
}
-static Result<void> do_parse_apex_configs(const BuiltinArguments& args) {
+static Result<void> parse_apex_configs() {
glob_t glob_result;
static constexpr char glob_pattern[] = "/apex/*/etc/*.rc";
const int ret = glob(glob_pattern, GLOB_MARK, nullptr, &glob_result);
@@ -1181,7 +1185,7 @@
return Error() << "glob pattern '" << glob_pattern << "' failed";
}
std::vector<std::string> configs;
- Parser parser = CreateServiceOnlyParser(ServiceList::GetInstance());
+ Parser parser = CreateServiceOnlyParser(ServiceList::GetInstance(), true);
for (size_t i = 0; i < glob_result.gl_pathc; i++) {
std::string path = glob_result.gl_pathv[i];
// Filter-out /apex/<name>@<ver> paths. The paths are bind-mounted to
@@ -1211,6 +1215,45 @@
}
}
+/*
+ * Creates a directory under /data/misc/apexdata/ for each APEX.
+ */
+static Result<void> create_apex_data_dirs() {
+ auto dirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir("/apex"), closedir);
+ if (!dirp) {
+ return ErrnoError() << "Unable to open apex directory";
+ }
+ struct dirent* entry;
+ while ((entry = readdir(dirp.get())) != nullptr) {
+ if (entry->d_type != DT_DIR) continue;
+
+ const char* name = entry->d_name;
+ // skip any starting with "."
+ if (name[0] == '.') continue;
+
+ if (strchr(name, '@') != nullptr) continue;
+
+ auto path = "/data/misc/apexdata/" + std::string(name);
+ auto system_uid = DecodeUid("system");
+ auto options =
+ MkdirOptions{path, 0700, *system_uid, *system_uid, FscryptAction::kNone, "ref"};
+ make_dir_with_options(options);
+ }
+ return {};
+}
+
+static Result<void> do_perform_apex_config(const BuiltinArguments& args) {
+ auto create_dirs = create_apex_data_dirs();
+ if (!create_dirs) {
+ return create_dirs.error();
+ }
+ auto parse_configs = parse_apex_configs();
+ if (!parse_configs) {
+ return parse_configs.error();
+ }
+ return {};
+}
+
static Result<void> do_enter_default_mount_ns(const BuiltinArguments& args) {
if (SwitchToDefaultMountNamespace()) {
return {};
@@ -1271,7 +1314,7 @@
// mount and umount are run in the same context as mount_all for symmetry.
{"mount_all", {1, kMax, {false, do_mount_all}}},
{"mount", {3, kMax, {false, do_mount}}},
- {"parse_apex_configs", {0, 0, {false, do_parse_apex_configs}}},
+ {"perform_apex_config", {0, 0, {false, do_perform_apex_config}}},
{"umount", {1, 1, {false, do_umount}}},
{"umount_all", {1, 1, {false, do_umount_all}}},
{"readahead", {1, 2, {true, do_readahead}}},
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index ac44796..bd71cb5 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -204,10 +204,6 @@
// part of the product partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/product", 0755));
- // /apex is used to mount APEXes
- CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
- "mode=0755,uid=0,gid=0"));
-
// /debug_ramdisk is used to preserve additional files from the debug ramdisk
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index 3acc3cc..22de846 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -191,7 +191,7 @@
}
auto errors = std::vector<std::string>{};
- ParsePropertyInfoFile(file_contents, property_infos, &errors);
+ ParsePropertyInfoFile(file_contents, true, property_infos, &errors);
for (const auto& error : errors) {
LOG(ERROR) << "Could not read line from '" << filename << "': " << error;
}
diff --git a/init/init.cpp b/init/init.cpp
index c457de6..5f97e44 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -121,11 +121,12 @@
}
// parser that only accepts new services
-Parser CreateServiceOnlyParser(ServiceList& service_list) {
+Parser CreateServiceOnlyParser(ServiceList& service_list, bool from_apex) {
Parser parser;
- parser.AddSectionParser("service", std::make_unique<ServiceParser>(
- &service_list, subcontext.get(), std::nullopt));
+ parser.AddSectionParser("service",
+ std::make_unique<ServiceParser>(&service_list, subcontext.get(),
+ std::nullopt, from_apex));
return parser;
}
@@ -511,10 +512,24 @@
static void UmountDebugRamdisk() {
if (umount("/debug_ramdisk") != 0) {
- LOG(ERROR) << "Failed to umount /debug_ramdisk";
+ PLOG(ERROR) << "Failed to umount /debug_ramdisk";
}
}
+static void MountExtraFilesystems() {
+#define CHECKCALL(x) \
+ if ((x) != 0) PLOG(FATAL) << #x " failed.";
+
+ // /apex is used to mount APEXes
+ CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
+ "mode=0755,uid=0,gid=0"));
+
+ // /linkerconfig is used to keep generated linker configuration
+ CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
+ "mode=0755,uid=0,gid=0"));
+#undef CHECKCALL
+}
+
static void RecordStageBoottimes(const boot_clock::time_point& second_stage_start_time) {
int64_t first_stage_start_time_ns = -1;
if (auto first_stage_start_time_str = getenv(kEnvFirstStageStartedAt);
@@ -655,6 +670,9 @@
UmountDebugRamdisk();
}
+ // Mount extra filesystems required during second stage init
+ MountExtraFilesystems();
+
// Now set up SELinux for second stage.
SelinuxSetupKernelLogging();
SelabelInitialize();
diff --git a/init/init.h b/init/init.h
index 0805940..4bbca6f 100644
--- a/init/init.h
+++ b/init/init.h
@@ -29,7 +29,7 @@
namespace init {
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
-Parser CreateServiceOnlyParser(ServiceList& service_list);
+Parser CreateServiceOnlyParser(ServiceList& service_list, bool from_apex);
bool start_waiting_for_property(const char *name, const char *value);
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index 940fb6b..648b3bb 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,7 @@
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;
- }
- }
- if (!success) {
- PLOG(ERROR) << "Failed to bind mount the ART APEX to " << kArtApexMountPath;
- }
- return success;
+ return true;
}
static android::base::unique_fd bootstrap_ns_fd;
@@ -172,6 +172,11 @@
// the bootstrap namespace get APEXes from the read-only partition.
if (!(MakePrivate("/apex"))) return false;
+ // /linkerconfig is a private mountpoint to give a different linker configuration
+ // based on the mount namespace. Subdirectory will be bind-mounted based on current mount
+ // namespace
+ if (!(MakePrivate("/linkerconfig"))) return false;
+
bootstrap_ns_fd.reset(OpenMountNamespace());
bootstrap_ns_id = GetMountNamespaceId();
diff --git a/init/persistent_properties.cpp b/init/persistent_properties.cpp
index baa9ad4..1758cfa 100644
--- a/init/persistent_properties.cpp
+++ b/init/persistent_properties.cpp
@@ -31,10 +31,11 @@
#include "util.h"
+using android::base::Dirname;
using android::base::ReadFdToString;
using android::base::StartsWith;
-using android::base::WriteStringToFd;
using android::base::unique_fd;
+using android::base::WriteStringToFd;
namespace android {
namespace init {
@@ -191,6 +192,18 @@
unlink(temp_filename.c_str());
return Error(saved_errno) << "Unable to rename persistent property file";
}
+
+ // rename() is atomic with regards to the kernel's filesystem buffers, but the parent
+ // directories must be fsync()'ed otherwise, the rename is not necessarily written to storage.
+ // Note in this case, that the source and destination directories are the same, so only one
+ // fsync() is required.
+ auto dir = Dirname(persistent_property_filename);
+ auto dir_fd = unique_fd{open(dir.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)};
+ if (dir_fd < 0) {
+ return ErrnoError() << "Unable to open persistent properties directory for fsync()";
+ }
+ fsync(dir_fd);
+
return {};
}
diff --git a/init/property_service.cpp b/init/property_service.cpp
index adf8929..5b35ad2 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -925,7 +925,8 @@
}
auto errors = std::vector<std::string>{};
- ParsePropertyInfoFile(file_contents, property_infos, &errors);
+ bool require_prefix_or_exact = SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__;
+ ParsePropertyInfoFile(file_contents, require_prefix_or_exact, property_infos, &errors);
// Individual parsing errors are reported but do not cause a failed boot, which is what
// returning false would do here.
for (const auto& error : errors) {
diff --git a/init/reboot.cpp b/init/reboot.cpp
index e9d918e..0e61234 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -797,6 +797,14 @@
if (!SwitchToBootstrapMountNamespaceIfNeeded()) {
return Error() << "Failed to switch to bootstrap namespace";
}
+ // Remove services that were defined in an APEX.
+ ServiceList::GetInstance().RemoveServiceIf([](const std::unique_ptr<Service>& s) -> bool {
+ if (s->is_from_apex()) {
+ LOG(INFO) << "Removing service '" << s->name() << "' because it's defined in an APEX";
+ return true;
+ }
+ return false;
+ });
// Re-enable services
for (const auto& s : were_enabled) {
LOG(INFO) << "Re-enabling service '" << s->name() << "'";
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 2fc8f6d..852d6ca 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -531,6 +531,8 @@
selinux_android_restorecon("/dev/device-mapper", 0);
selinux_android_restorecon("/apex", 0);
+
+ selinux_android_restorecon("/linkerconfig", 0);
}
int SelinuxKlogCallback(int type, const char* fmt, ...) {
diff --git a/init/service.cpp b/init/service.cpp
index cc97d94..a97935e 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -131,13 +131,13 @@
bool Service::is_exec_service_running_ = false;
Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
- const std::vector<std::string>& args)
- : Service(name, 0, 0, 0, {}, 0, "", subcontext_for_restart_commands, args) {}
+ const std::vector<std::string>& args, bool from_apex)
+ : Service(name, 0, 0, 0, {}, 0, "", subcontext_for_restart_commands, args, from_apex) {}
Service::Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
const std::vector<gid_t>& supp_gids, int namespace_flags,
const std::string& seclabel, Subcontext* subcontext_for_restart_commands,
- const std::vector<std::string>& args)
+ const std::vector<std::string>& args, bool from_apex)
: name_(name),
classnames_({"default"}),
flags_(flags),
@@ -155,7 +155,8 @@
"onrestart", {}),
oom_score_adjust_(DEFAULT_OOM_SCORE_ADJUST),
start_order_(0),
- args_(args) {}
+ args_(args),
+ from_apex_(from_apex) {}
void Service::NotifyStateChange(const std::string& new_state) const {
if ((flags_ & SVC_TEMPORARY) != 0) {
@@ -325,6 +326,7 @@
<< (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_);
}
}
} else {
@@ -763,7 +765,7 @@
}
return std::make_unique<Service>(name, flags, *uid, *gid, supp_gids, namespace_flags, seclabel,
- nullptr, str_args);
+ nullptr, str_args, false);
}
} // namespace init
diff --git a/init/service.h b/init/service.h
index f842b3c..cf3f0c2 100644
--- a/init/service.h
+++ b/init/service.h
@@ -65,11 +65,12 @@
public:
Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
- const std::vector<std::string>& args);
+ const std::vector<std::string>& args, bool from_apex = false);
Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
const std::vector<gid_t>& supp_gids, int namespace_flags, const std::string& seclabel,
- Subcontext* subcontext_for_restart_commands, const std::vector<std::string>& args);
+ Subcontext* subcontext_for_restart_commands, const std::vector<std::string>& args,
+ bool from_apex = false);
static Result<std::unique_ptr<Service>> MakeTemporaryOneshotService(
const std::vector<std::string>& args);
@@ -128,6 +129,7 @@
const std::vector<std::string>& args() const { return args_; }
bool is_updatable() const { return updatable_; }
bool is_post_data() const { return post_data_; }
+ bool is_from_apex() const { return from_apex_; }
private:
void NotifyStateChange(const std::string& new_state) const;
@@ -199,6 +201,8 @@
bool running_at_post_data_reset_ = false;
std::optional<std::string> on_failure_reboot_target_;
+
+ bool from_apex_ = false;
};
} // namespace init
diff --git a/init/service_list.h b/init/service_list.h
index ee2c702..1838624 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -34,6 +34,11 @@
void AddService(std::unique_ptr<Service> service);
void RemoveService(const Service& svc);
+ template <class UnaryPredicate>
+ void RemoveServiceIf(UnaryPredicate predicate) {
+ services_.erase(std::remove_if(services_.begin(), services_.end(), predicate),
+ services_.end());
+ }
template <typename T, typename F = decltype(&Service::name)>
Service* FindService(T value, F function = &Service::name) const {
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 154d1dd..1d431e3 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -569,7 +569,7 @@
}
}
- service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args);
+ service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args, from_apex_);
return {};
}
diff --git a/init/service_parser.h b/init/service_parser.h
index b1281f5..7bb0cc0 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -31,11 +31,13 @@
public:
ServiceParser(
ServiceList* service_list, Subcontext* subcontext,
- const std::optional<InterfaceInheritanceHierarchyMap>& interface_inheritance_hierarchy)
+ const std::optional<InterfaceInheritanceHierarchyMap>& interface_inheritance_hierarchy,
+ bool from_apex = false)
: service_list_(service_list),
subcontext_(subcontext),
interface_inheritance_hierarchy_(interface_inheritance_hierarchy),
- service_(nullptr) {}
+ service_(nullptr),
+ from_apex_(from_apex) {}
Result<void> ParseSection(std::vector<std::string>&& args, const std::string& filename,
int line) override;
Result<void> ParseLineSection(std::vector<std::string>&& args, int line) override;
@@ -89,6 +91,7 @@
std::optional<InterfaceInheritanceHierarchyMap> interface_inheritance_hierarchy_;
std::unique_ptr<Service> service_;
std::string filename_;
+ bool from_apex_ = false;
};
} // namespace init
diff --git a/liblog/Android.bp b/liblog/Android.bp
index 91bd52c..de0c636 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",
@@ -25,7 +24,6 @@
]
liblog_host_sources = [
"fake_log_device.cpp",
- "fake_writer.cpp",
]
liblog_target_sources = [
"event_tag_map.cpp",
diff --git a/liblog/fake_log_device.cpp b/liblog/fake_log_device.cpp
index f61bbdc..fb3b9bc 100644
--- a/liblog/fake_log_device.cpp
+++ b/liblog/fake_log_device.cpp
@@ -23,18 +23,20 @@
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
-#if !defined(_WIN32)
-#include <pthread.h>
-#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#include <mutex>
+
#include <android/log.h>
+#include <log/log_id.h>
+#include <log/logprint.h>
#include "log_portability.h"
+#include "logger.h"
#define kMaxTagLen 16 /* from the long-dead utils/Log.cpp */
@@ -46,37 +48,21 @@
#define TRACE(...) ((void)0)
#endif
-/* from the long-dead utils/Log.cpp */
-typedef enum {
- FORMAT_OFF = 0,
- FORMAT_BRIEF,
- FORMAT_PROCESS,
- FORMAT_TAG,
- FORMAT_THREAD,
- FORMAT_RAW,
- FORMAT_TIME,
- FORMAT_THREADTIME,
- FORMAT_LONG
-} LogFormat;
+static void FakeClose();
+static int FakeWrite(log_id_t log_id, struct timespec* ts, struct iovec* vec, size_t nr);
-/*
- * Log driver state.
- */
+struct android_log_transport_write fakeLoggerWrite = {
+ .close = FakeClose,
+ .write = FakeWrite,
+};
+
typedef struct LogState {
- /* the fake fd that's seen by the user */
- int fakeFd;
-
- /* a printable name for this fake device */
- char debugName[sizeof("/dev/log/security")];
-
- /* nonzero if this is a binary log */
- int isBinary;
-
+ bool initialized = false;
/* global minimum priority */
- int globalMinPriority;
+ int global_min_priority;
/* output format */
- LogFormat outputFormat;
+ AndroidLogPrintFormat output_format;
/* tags and priorities */
struct {
@@ -85,82 +71,8 @@
} tagSet[kTagSetSize];
} LogState;
-#if !defined(_WIN32)
-/*
- * 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.
- */
-static pthread_mutex_t fakeLogDeviceLock = PTHREAD_MUTEX_INITIALIZER;
-
-static void lock() {
- /*
- * 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(&fakeLogDeviceLock);
-}
-
-static void unlock() {
- pthread_mutex_unlock(&fakeLogDeviceLock);
-}
-
-#else // !defined(_WIN32)
-
-#define lock() ((void)0)
-#define unlock() ((void)0)
-
-#endif // !defined(_WIN32)
-
-/*
- * File descriptor management.
- */
-#define FAKE_FD_BASE 10000
-#define MAX_OPEN_LOGS 8
-static LogState openLogTable[MAX_OPEN_LOGS];
-
-/*
- * Allocate an fd and associate a new LogState with it.
- * The fd is available via the fakeFd field of the return value.
- */
-static LogState* createLogState() {
- size_t i;
-
- for (i = 0; i < (sizeof(openLogTable) / sizeof(openLogTable[0])); i++) {
- if (openLogTable[i].fakeFd == 0) {
- openLogTable[i].fakeFd = FAKE_FD_BASE + i;
- return &openLogTable[i];
- }
- }
- return NULL;
-}
-
-/*
- * Translate an fd to a LogState.
- */
-static LogState* fdToLogState(int fd) {
- if (fd >= FAKE_FD_BASE && fd < FAKE_FD_BASE + MAX_OPEN_LOGS) {
- return &openLogTable[fd - FAKE_FD_BASE];
- }
- return NULL;
-}
-
-/*
- * Unregister the fake fd and free the memory it pointed to.
- */
-static void deleteFakeFd(int fd) {
- LogState* ls;
-
- lock();
-
- ls = fdToLogState(fd);
- if (ls != NULL) {
- memset(&openLogTable[fd - FAKE_FD_BASE], 0, sizeof(openLogTable[0]));
- }
-
- unlock();
-}
+static LogState log_state;
+static std::mutex fake_log_mutex;
/*
* Configure logging based on ANDROID_LOG_TAGS environment variable. We
@@ -175,19 +87,11 @@
* We also want to check ANDROID_PRINTF_LOG to determine how the output
* will look.
*/
-static void configureInitialState(const char* pathName, LogState* logState) {
- static const int kDevLogLen = sizeof("/dev/log/") - 1;
-
- strncpy(logState->debugName, pathName, sizeof(logState->debugName));
- logState->debugName[sizeof(logState->debugName) - 1] = '\0';
-
- /* identify binary logs */
- if (!strcmp(pathName + kDevLogLen, "events") || !strcmp(pathName + kDevLogLen, "security")) {
- logState->isBinary = 1;
- }
+void InitializeLogStateLocked() {
+ log_state.initialized = true;
/* global min priority defaults to "info" level */
- logState->globalMinPriority = ANDROID_LOG_INFO;
+ log_state.global_min_priority = ANDROID_LOG_INFO;
/*
* This is based on the the long-dead utils/Log.cpp code.
@@ -265,11 +169,11 @@
}
if (tagName[0] == 0) {
- logState->globalMinPriority = minPrio;
+ log_state.global_min_priority = minPrio;
TRACE("+++ global min prio %d\n", logState->globalMinPriority);
} else {
- logState->tagSet[entry].minPriority = minPrio;
- strcpy(logState->tagSet[entry].tag, tagName);
+ log_state.tagSet[entry].minPriority = minPrio;
+ strcpy(log_state.tagSet[entry].tag, tagName);
TRACE("+++ entry %d: %s:%d\n", entry, logState->tagSet[entry].tag,
logState->tagSet[entry].minPriority);
entry++;
@@ -281,7 +185,7 @@
* Taken from the long-dead utils/Log.cpp
*/
const char* fstr = getenv("ANDROID_PRINTF_LOG");
- LogFormat format;
+ AndroidLogPrintFormat format;
if (fstr == NULL) {
format = FORMAT_BRIEF;
} else {
@@ -300,10 +204,10 @@
else if (strcmp(fstr, "long") == 0)
format = FORMAT_PROCESS;
else
- format = (LogFormat)atoi(fstr); // really?!
+ format = (AndroidLogPrintFormat)atoi(fstr); // really?!
}
- logState->outputFormat = format;
+ log_state.output_format = format;
}
/*
@@ -348,7 +252,7 @@
*
* Log format parsing taken from the long-dead utils/Log.cpp.
*/
-static void showLog(LogState* state, int logPrio, const char* tag, const char* msg) {
+static void ShowLog(int logPrio, const char* tag, const char* msg) {
#if !defined(_WIN32)
struct tm tmBuf;
#endif
@@ -391,7 +295,7 @@
*/
size_t prefixLen, suffixLen;
- switch (state->outputFormat) {
+ switch (log_state.output_format) {
case FORMAT_TAG:
prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), "%c/%-8s: ", priChar, tag);
strcpy(suffixBuf, "\n");
@@ -548,35 +452,28 @@
* tag (N bytes -- null-terminated ASCII string)
* message (N bytes -- null-terminated ASCII string)
*/
-ssize_t fakeLogWritev(int fd, const struct iovec* vector, int count) {
- LogState* state;
-
+static int FakeWrite(log_id_t log_id, struct timespec*, struct iovec* vector, size_t count) {
/* Make sure that no-one frees the LogState while we're using it.
* Also guarantees that only one thread is in showLog() at a given
* time (if it matters).
*/
- lock();
+ auto lock = std::lock_guard{fake_log_mutex};
- state = fdToLogState(fd);
- if (state == NULL) {
- errno = EBADF;
- unlock();
- return -1;
+ if (!log_state.initialized) {
+ InitializeLogStateLocked();
}
- if (state->isBinary) {
- TRACE("%s: ignoring binary log\n", state->debugName);
- unlock();
+ 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));
int len = 0;
- for (int i = 0; i < count; ++i) {
+ for (size_t i = 0; i < count; ++i) {
len += vector[i].iov_len;
}
return len;
}
if (count != 3) {
- TRACE("%s: writevLog with count=%d not expected\n", state->debugName, count);
- unlock();
+ TRACE("%s: writevLog with count=%d not expected\n", android_log_id_to_name(log_id), count);
return -1;
}
@@ -586,32 +483,30 @@
const char* msg = (const char*)vector[2].iov_base;
/* see if this log tag is configured */
- int i;
- int minPrio = state->globalMinPriority;
- for (i = 0; i < kTagSetSize; i++) {
- if (state->tagSet[i].minPriority == ANDROID_LOG_UNKNOWN)
+ int minPrio = log_state.global_min_priority;
+ for (size_t i = 0; i < kTagSetSize; i++) {
+ if (log_state.tagSet[i].minPriority == ANDROID_LOG_UNKNOWN)
break; /* reached end of configured values */
- if (strcmp(state->tagSet[i].tag, tag) == 0) {
- minPrio = state->tagSet[i].minPriority;
+ if (strcmp(log_state.tagSet[i].tag, tag) == 0) {
+ minPrio = log_state.tagSet[i].minPriority;
break;
}
}
if (logPrio >= minPrio) {
- showLog(state, logPrio, tag, msg);
+ ShowLog(logPrio, tag, msg);
}
- unlock();
int len = 0;
- for (i = 0; i < count; ++i) {
+ for (size_t i = 0; i < count; ++i) {
len += vector[i].iov_len;
}
return len;
}
/*
- * Free up our state and close the fake descriptor.
+ * Reset out state.
*
* The logger API has no means or need to 'stop' or 'close' using the logs,
* and as such, there is no way for that 'stop' or 'close' to translate into
@@ -623,31 +518,10 @@
* call is in the exit handler. Logging can continue in the exit handler to
* help debug HOST tools ...
*/
-int fakeLogClose(int fd) {
- deleteFakeFd(fd);
- return 0;
-}
+static void FakeClose() {
+ auto lock = std::lock_guard{fake_log_mutex};
-/*
- * Open a log output device and return a fake fd.
- */
-int fakeLogOpen(const char* pathName) {
- LogState* logState;
- int fd = -1;
-
- lock();
-
- logState = createLogState();
- if (logState != NULL) {
- configureInitialState(pathName, logState);
- fd = logState->fakeFd;
- } else {
- errno = ENFILE;
- }
-
- unlock();
-
- return fd;
+ memset(&log_state, 0, sizeof(log_state));
}
int __android_log_is_loggable(int prio, const char*, int def) {
diff --git a/liblog/fake_writer.cpp b/liblog/fake_writer.cpp
deleted file mode 100644
index f1ddff1..0000000
--- a/liblog/fake_writer.cpp
+++ /dev/null
@@ -1,105 +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.
- */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <log/log.h>
-
-#include "fake_log_device.h"
-#include "log_portability.h"
-#include "logger.h"
-
-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);
-
-static int logFds[(int)LOG_ID_MAX] = {-1, -1, -1, -1, -1, -1};
-
-struct android_log_transport_write fakeLoggerWrite = {
- .name = "fake",
- .logMask = 0,
- .context.priv = &logFds,
- .available = fakeAvailable,
- .open = fakeOpen,
- .close = fakeClose,
- .write = fakeWrite,
-};
-
-static int fakeAvailable(log_id_t) {
- return 0;
-}
-
-static int fakeOpen() {
- int i;
-
- for (i = 0; i < LOG_ID_MAX; i++) {
- /*
- * Known maximum size string, plus an 8 character margin to deal with
- * possible independent changes to android_log_id_to_name().
- */
- char buf[sizeof("/dev/log_security") + 8];
- if (logFds[i] >= 0) {
- continue;
- }
- snprintf(buf, sizeof(buf), "/dev/log_%s", android_log_id_to_name(static_cast<log_id_t>(i)));
- logFds[i] = fakeLogOpen(buf);
- if (logFds[i] < 0) {
- fprintf(stderr, "fakeLogOpen(%s) failed\n", buf);
- }
- }
- return 0;
-}
-
-static void fakeClose() {
- int i;
-
- for (i = 0; i < LOG_ID_MAX; i++) {
- fakeLogClose(logFds[i]);
- logFds[i] = -1;
- }
-}
-
-static int fakeWrite(log_id_t log_id, struct timespec*, struct iovec* vec, size_t nr) {
- ssize_t ret;
- size_t i;
- int logFd, len;
-
- if (/*(int)log_id >= 0 &&*/ (int)log_id >= (int)LOG_ID_MAX) {
- return -EINVAL;
- }
-
- len = 0;
- for (i = 0; i < nr; ++i) {
- len += vec[i].iov_len;
- }
-
- if (len > LOGGER_ENTRY_MAX_PAYLOAD) {
- len = LOGGER_ENTRY_MAX_PAYLOAD;
- }
-
- logFd = logFds[(int)log_id];
- ret = TEMP_FAILURE_RETRY(fakeLogWritev(logFd, vec, nr));
- if (ret < 0) {
- ret = -errno;
- } else if (ret > len) {
- ret = len;
- }
-
- return ret;
-}
diff --git a/liblog/include/log/log_read.h b/liblog/include/log/log_read.h
index 6601072..18c1c33 100644
--- a/liblog/include/log/log_read.h
+++ b/liblog/include/log/log_read.h
@@ -139,8 +139,7 @@
char* buf, size_t len);
ssize_t android_logger_get_prune_list(struct logger_list* logger_list,
char* buf, size_t len);
-int android_logger_set_prune_list(struct logger_list* logger_list, char* buf,
- size_t len);
+int android_logger_set_prune_list(struct logger_list* logger_list, const char* buf, size_t len);
#define ANDROID_LOG_RDONLY O_RDONLY
#define ANDROID_LOG_WRONLY O_WRONLY
diff --git a/liblog/logd_reader.cpp b/liblog/logd_reader.cpp
index 96e7a61..6865c14 100644
--- a/liblog/logd_reader.cpp
+++ b/liblog/logd_reader.cpp
@@ -33,6 +33,8 @@
#include <time.h>
#include <unistd.h>
+#include <string>
+
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
@@ -249,22 +251,14 @@
return SendLogdControlMessage(buf, len);
}
-int android_logger_set_prune_list(struct logger_list* logger_list, char* buf, size_t len) {
+int android_logger_set_prune_list(struct logger_list* logger_list, const char* buf, size_t len) {
if (logger_list->mode & ANDROID_LOG_PSTORE) {
return -EINVAL;
}
- const char cmd[] = "setPruneList ";
- const size_t cmdlen = sizeof(cmd) - 1;
+ std::string cmd = "setPruneList " + std::string{buf, len};
- if (strlen(buf) > (len - cmdlen)) {
- return -ENOMEM; /* KISS */
- }
- memmove(buf + cmdlen, buf, len - cmdlen);
- buf[len - 1] = '\0';
- memcpy(buf, cmd, cmdlen);
-
- return check_log_success(buf, SendLogdControlMessage(buf, len));
+ return check_log_success(cmd.data(), SendLogdControlMessage(cmd.data(), cmd.size()));
}
static int logdOpen(struct logger_list* logger_list) {
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 394fa93..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",
@@ -96,3 +97,11 @@
"vts",
],
}
+
+cc_test_host {
+ name: "liblog-host-test",
+ static_libs: ["liblog"],
+ shared_libs: ["libbase"],
+ srcs: ["liblog_host_test.cpp"],
+ isolated: true,
+}
diff --git a/liblog/tests/liblog_host_test.cpp b/liblog/tests/liblog_host_test.cpp
new file mode 100644
index 0000000..377550f
--- /dev/null
+++ b/liblog/tests/liblog_host_test.cpp
@@ -0,0 +1,195 @@
+/*
+ * 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 <log/log.h>
+#include <private/android_logger.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+using android::base::StringPrintf;
+using android::base::StringReplace;
+
+void GenerateLogContent() {
+ __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_VERBOSE, "tag", "verbose main");
+ __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_INFO, "tag", "info main");
+ __android_log_buf_print(LOG_ID_MAIN, ANDROID_LOG_ERROR, "tag", "error main");
+
+ __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, "tag", "verbose radio");
+ __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, "tag", "info radio");
+ __android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, "tag", "error radio");
+
+ __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, "tag", "verbose system");
+ __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, "tag", "info system");
+ __android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, "tag", "error system");
+
+ __android_log_buf_print(LOG_ID_CRASH, ANDROID_LOG_VERBOSE, "tag", "verbose crash");
+ __android_log_buf_print(LOG_ID_CRASH, ANDROID_LOG_INFO, "tag", "info crash");
+ __android_log_buf_print(LOG_ID_CRASH, ANDROID_LOG_ERROR, "tag", "error crash");
+}
+
+std::string GetPidString() {
+ int pid = getpid();
+ return StringPrintf("%5d", pid);
+}
+
+TEST(liblog, default_write) {
+ setenv("ANDROID_PRINTF_LOG", "brief", true);
+ CapturedStderr captured_stderr;
+
+ GenerateLogContent();
+
+ std::string expected_output = StringReplace(R"init(I/tag (<pid>): info main
+E/tag (<pid>): error main
+I/tag (<pid>): info radio
+E/tag (<pid>): error radio
+I/tag (<pid>): info system
+E/tag (<pid>): error system
+I/tag (<pid>): info crash
+E/tag (<pid>): error crash
+)init",
+ "<pid>", GetPidString(), true);
+
+ EXPECT_EQ(expected_output, captured_stderr.str());
+}
+
+TEST(liblog, format) {
+ setenv("ANDROID_PRINTF_LOG", "process", true);
+ CapturedStderr captured_stderr;
+
+ GenerateLogContent();
+
+ std::string expected_output = StringReplace(R"init(I(<pid>) info main (tag)
+E(<pid>) error main (tag)
+I(<pid>) info radio (tag)
+E(<pid>) error radio (tag)
+I(<pid>) info system (tag)
+E(<pid>) error system (tag)
+I(<pid>) info crash (tag)
+E(<pid>) error crash (tag)
+)init",
+ "<pid>", GetPidString(), true);
+
+ EXPECT_EQ(expected_output, captured_stderr.str());
+ captured_stderr.Stop();
+ captured_stderr.Reset();
+ captured_stderr.Start();
+
+ // Changing the environment after starting writing doesn't change the format.
+ setenv("ANDROID_PRINTF_LOG", "brief", true);
+ GenerateLogContent();
+ EXPECT_EQ(expected_output, captured_stderr.str());
+ captured_stderr.Stop();
+ captured_stderr.Reset();
+ captured_stderr.Start();
+
+ // However calling __android_log_close() does reset logging and allow changing the format.
+ __android_log_close();
+ GenerateLogContent();
+
+ expected_output = StringReplace(R"init(I/tag (<pid>): info main
+E/tag (<pid>): error main
+I/tag (<pid>): info radio
+E/tag (<pid>): error radio
+I/tag (<pid>): info system
+E/tag (<pid>): error system
+I/tag (<pid>): info crash
+E/tag (<pid>): error crash
+)init",
+ "<pid>", GetPidString(), true);
+
+ EXPECT_EQ(expected_output, captured_stderr.str());
+}
+
+TEST(liblog, filter) {
+ setenv("ANDROID_PRINTF_LOG", "brief", true);
+ setenv("ANDROID_LOG_TAGS", "*:w verbose_tag:v debug_tag:d", true);
+ CapturedStderr captured_stderr;
+
+ auto generate_logs = [](log_id_t log_id) {
+ // Check that we show verbose logs when requesting for a given tag.
+ __android_log_buf_print(log_id, ANDROID_LOG_VERBOSE, "verbose_tag", "verbose verbose_tag");
+ __android_log_buf_print(log_id, ANDROID_LOG_ERROR, "verbose_tag", "error verbose_tag");
+
+ // Check that we don't show verbose logs when explicitly requesting debug+ for a given tag.
+ __android_log_buf_print(log_id, ANDROID_LOG_VERBOSE, "debug_tag", "verbose debug_tag");
+ __android_log_buf_print(log_id, ANDROID_LOG_DEBUG, "debug_tag", "debug debug_tag");
+ __android_log_buf_print(log_id, ANDROID_LOG_ERROR, "debug_tag", "error debug_tag");
+
+ // Check that we don't show info logs when requesting globally warn+.
+ __android_log_buf_print(log_id, ANDROID_LOG_INFO, "default_tag", "info default_tag");
+ __android_log_buf_print(log_id, ANDROID_LOG_WARN, "default_tag", "warn default_tag");
+ __android_log_buf_print(log_id, ANDROID_LOG_ERROR, "default_tag", "error default_tag");
+ };
+
+ auto expected_output = StringReplace(R"init(V/verbose_tag(<pid>): verbose verbose_tag
+E/verbose_tag(<pid>): error verbose_tag
+D/debug_tag(<pid>): debug debug_tag
+E/debug_tag(<pid>): error debug_tag
+W/default_tag(<pid>): warn default_tag
+E/default_tag(<pid>): error default_tag
+)init",
+ "<pid>", GetPidString(), true);
+
+ auto test_all_logs = [&] {
+ for (auto log_id : {LOG_ID_MAIN, LOG_ID_SYSTEM, LOG_ID_RADIO, LOG_ID_CRASH}) {
+ generate_logs(log_id);
+ EXPECT_EQ(expected_output, captured_stderr.str());
+ captured_stderr.Stop();
+ captured_stderr.Reset();
+ captured_stderr.Start();
+ }
+ };
+
+ test_all_logs();
+
+ // Changing the environment after starting writing doesn't change the filter.
+ setenv("ANDROID_LOG_TAGS", "*:e", true);
+ test_all_logs();
+
+ // However calling __android_log_close() does reset logging and allow changing the format.
+ __android_log_close();
+ expected_output = StringReplace(R"init(E/verbose_tag(<pid>): error verbose_tag
+E/debug_tag(<pid>): error debug_tag
+E/default_tag(<pid>): error default_tag
+)init",
+ "<pid>", GetPidString(), true);
+ test_all_logs();
+}
+
+TEST(liblog, kernel_no_write) {
+ CapturedStderr captured_stderr;
+ __android_log_buf_print(LOG_ID_KERNEL, ANDROID_LOG_ERROR, "tag", "kernel error");
+ EXPECT_EQ("", captured_stderr.str());
+}
+
+TEST(liblog, binary_no_write) {
+ CapturedStderr captured_stderr;
+ __android_log_buf_print(LOG_ID_EVENTS, ANDROID_LOG_ERROR, "tag", "error events");
+ __android_log_buf_print(LOG_ID_STATS, ANDROID_LOG_ERROR, "tag", "error stats");
+ __android_log_buf_print(LOG_ID_SECURITY, ANDROID_LOG_ERROR, "tag", "error security");
+
+ __android_log_bswrite(0x12, "events");
+ __android_log_stats_bwrite(0x34, "stats", strlen("stats"));
+ __android_log_security_bswrite(0x56, "security");
+
+ EXPECT_EQ("", captured_stderr.str());
+}
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/libstats/Android.bp b/libstats/Android.bp
index f5ee1da..89c4048 100644
--- a/libstats/Android.bp
+++ b/libstats/Android.bp
@@ -1,39 +1,4 @@
-//
-// 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",
- ],
-}
+subdirs = [
+ "socket",
+ "socket_q",
+]
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
new file mode 100644
index 0000000..b7c07b6
--- /dev/null
+++ b/libstats/socket/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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_event.c",
+ "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/socket/include/stats_event.h b/libstats/socket/include/stats_event.h
new file mode 100644
index 0000000..89cb420
--- /dev/null
+++ b/libstats/socket/include/stats_event.h
@@ -0,0 +1,137 @@
+/*
+ * 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
+
+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);
+void 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, uint8_t* buf, size_t numBytes);
+
+// Buf must be null-terminated.
+void stats_event_write_string8(struct stats_event* event, const char* buf);
+
+// Tags must be null-terminated.
+void stats_event_write_attribution_chain(struct stats_event* event, uint32_t* uids,
+ const char** 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);
+uint8_t* stats_event_get_buffer(struct stats_event* event, size_t* size);
+uint32_t stats_event_get_errors(struct stats_event* event);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_STATS_LOG_STATS_EVENT_H
diff --git a/libstats/include/stats_event_list.h b/libstats/socket/include/stats_event_list.h
similarity index 100%
rename from libstats/include/stats_event_list.h
rename to libstats/socket/include/stats_event_list.h
diff --git a/libstats/socket/stats_event.c b/libstats/socket/stats_event.c
new file mode 100644
index 0000000..35081dc
--- /dev/null
+++ b/libstats/socket/stats_event.c
@@ -0,0 +1,333 @@
+/*
+ * 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 "include/stats_event_list.h"
+
+#define STATS_EVENT_TAG 1937006964
+#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[MAX_EVENT_PAYLOAD];
+ 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;
+ uint32_t tag;
+ 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));
+
+ memset(event->buf, 0, MAX_EVENT_PAYLOAD);
+ event->buf[0] = OBJECT_TYPE;
+ event->atomId = 0;
+ event->errors = 0;
+ event->tag = STATS_EVENT_TAG;
+ 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);
+}
+
+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, 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 (event->errors) {
+ 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, 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);
+}
+
+// Buf is assumed to be encoded using UTF8
+void stats_event_write_string8(struct stats_event* event, const char* buf) {
+ if (event->errors) return;
+
+ start_field(event, STRING_TYPE);
+ append_string(event, buf);
+}
+
+// Tags are assumed to be encoded using UTF8
+void stats_event_write_attribution_chain(struct stats_event* event, uint32_t* uids,
+ const char** 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_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);
+ }
+
+ event->built = true;
+}
+
+void stats_event_write(struct stats_event* event) {
+ stats_event_build(event);
+
+ // Prepare iovecs for write to statsd.
+ struct iovec vecs[2];
+ vecs[0].iov_base = &event->tag;
+ vecs[0].iov_len = sizeof(event->tag);
+ vecs[1].iov_base = &event->buf;
+ vecs[1].iov_len = event->size;
+ write_to_statsd(vecs, 2);
+}
diff --git a/libstats/stats_event_list.c b/libstats/socket/stats_event_list.c
similarity index 100%
rename from libstats/stats_event_list.c
rename to libstats/socket/stats_event_list.c
diff --git a/libstats/statsd_writer.c b/libstats/socket/statsd_writer.c
similarity index 98%
rename from libstats/statsd_writer.c
rename 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/libstats/socket_q/Android.bp b/libstats/socket_q/Android.bp
new file mode 100644
index 0000000..6c0c65c
--- /dev/null
+++ b/libstats/socket_q/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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 Q and earlier.
+// This library is only meant to be used by libstatssocket_compat.
+// ============================================================================
+cc_library {
+ name: "libstatssocket_q",
+ 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/include/stats_event_list.h b/libstats/socket_q/include/stats_event_list.h
similarity index 100%
copy from libstats/include/stats_event_list.h
copy to libstats/socket_q/include/stats_event_list.h
diff --git a/libstats/stats_event_list.c b/libstats/socket_q/stats_event_list.c
similarity index 100%
copy from libstats/stats_event_list.c
copy to libstats/socket_q/stats_event_list.c
diff --git a/libstats/statsd_writer.c b/libstats/socket_q/statsd_writer.c
similarity index 98%
copy from libstats/statsd_writer.c
copy to libstats/socket_q/statsd_writer.c
index 073b67f..04d3b46 100644
--- a/libstats/statsd_writer.c
+++ b/libstats/socket_q/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_q/statsd_writer.h
similarity index 100%
copy from libstats/statsd_writer.h
copy to libstats/socket_q/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/Maps.cpp b/libunwindstack/Maps.cpp
index 250e600..0ab68db 100644
--- a/libunwindstack/Maps.cpp
+++ b/libunwindstack/Maps.cpp
@@ -139,6 +139,9 @@
if (start == info->start && end == info->end && flags == info->flags && *name == info->name) {
// No need to check
search_map_idx = old_map_idx + 1;
+ if (new_map_idx + 1 < maps_.size()) {
+ maps_[new_map_idx + 1]->prev_map = info.get();
+ }
maps_[new_map_idx] = nullptr;
total_entries--;
break;
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/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/Errors.cpp b/libutils/Errors.cpp
index 2dfd138..74f3bef 100644
--- a/libutils/Errors.cpp
+++ b/libutils/Errors.cpp
@@ -45,7 +45,7 @@
#undef STATUS_CASE
}
- return std::to_string(s) + ' ' + strerror(-s);
+ return std::to_string(s) + " (" + strerror(-s) + ")";
}
} // namespace android
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/logcat/logcat.cpp b/logcat/logcat.cpp
index c0e11d3..7b18438 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -17,6 +17,7 @@
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
+#include <error.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
@@ -103,15 +104,6 @@
bool debug_ = false;
};
-// logd prefixes records with a length field
-#define RECORD_LENGTH_FIELD_SIZE_BYTES sizeof(uint32_t)
-
-enum helpType { HELP_FALSE, HELP_TRUE, HELP_FORMAT };
-
-// if show_help is set, newline required in fmt statement to transition to usage
-static void LogcatPanic(enum helpType showHelp, const char* fmt, ...) __printflike(2, 3)
- __attribute__((__noreturn__));
-
#ifndef F2FS_IOC_SET_PIN_FILE
#define F2FS_IOCTL_MAGIC 0xf5
#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
@@ -170,7 +162,7 @@
output_fd_.reset(openLogFile(output_file_name_, log_rotate_size_kb_));
if (!output_fd_.ok()) {
- LogcatPanic(HELP_FALSE, "couldn't open output file");
+ error(EXIT_FAILURE, errno, "Couldn't open output file");
}
out_byte_count_ = 0;
@@ -209,7 +201,7 @@
bytesWritten = android_log_printLogLine(logformat_.get(), output_fd_.get(), &entry);
if (bytesWritten < 0) {
- LogcatPanic(HELP_FALSE, "output error");
+ error(EXIT_FAILURE, 0, "Output error.");
}
}
}
@@ -229,7 +221,7 @@
if (dprintf(output_fd_.get(), "--------- %s %s\n",
printed_start_[log_id] ? "switch to" : "beginning of",
android_log_id_to_name(log_id)) < 0) {
- LogcatPanic(HELP_FALSE, "output error");
+ error(EXIT_FAILURE, errno, "Output error");
}
}
last_printed_id_ = log_id;
@@ -259,18 +251,16 @@
output_fd_.reset(openLogFile(output_file_name_, log_rotate_size_kb_));
if (!output_fd_.ok()) {
- LogcatPanic(HELP_FALSE, "couldn't open output file");
+ error(EXIT_FAILURE, errno, "Couldn't open output file");
}
struct stat statbuf;
if (fstat(output_fd_.get(), &statbuf) == -1) {
- output_fd_.reset();
- LogcatPanic(HELP_FALSE, "couldn't get output file stat\n");
+ error(EXIT_FAILURE, errno, "Couldn't get output file stat");
}
if ((size_t)statbuf.st_size > SIZE_MAX || statbuf.st_size < 0) {
- output_fd_.reset();
- LogcatPanic(HELP_FALSE, "invalid output file stat\n");
+ error(EXIT_FAILURE, 0, "Invalid output file stat.");
}
out_byte_count_ = statbuf.st_size;
@@ -427,27 +417,6 @@
return std::make_pair(value, multipliers[i]);
}
-static void LogcatPanic(enum helpType showHelp, const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
- va_end(args);
-
- switch (showHelp) {
- case HELP_TRUE:
- show_help();
- break;
- case HELP_FORMAT:
- show_format_help();
- break;
- case HELP_FALSE:
- default:
- break;
- }
-
- exit(EXIT_FAILURE);
-}
-
static char* parseTime(log_time& t, const char* cp) {
char* ep = t.strptime(cp, "%m-%d %H:%M:%S.%q");
if (ep) return ep;
@@ -612,13 +581,12 @@
// only long options
if (long_options[option_index].name == pid_str) {
if (pid != 0) {
- LogcatPanic(HELP_TRUE, "Only supports one PID argument.\n");
+ error(EXIT_FAILURE, 0, "Only one --pid argument can be provided.");
}
- // ToDo: determine runtime PID_MAX?
if (!ParseUint(optarg, &pid) || pid < 1) {
- LogcatPanic(HELP_TRUE, "%s %s out of range\n",
- long_options[option_index].name, optarg);
+ error(EXIT_FAILURE, 0, "%s %s out of range.",
+ long_options[option_index].name, optarg);
}
break;
}
@@ -628,8 +596,8 @@
// ToDo: implement API that supports setting a wrap timeout
size_t dummy = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
if (optarg && (!ParseUint(optarg, &dummy) || dummy < 1)) {
- LogcatPanic(HELP_TRUE, "%s %s out of range\n",
- long_options[option_index].name, optarg);
+ error(EXIT_FAILURE, 0, "%s %s out of range.",
+ long_options[option_index].name, optarg);
}
if (dummy != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) {
fprintf(stderr, "WARNING: %s %u seconds, ignoring %zu\n",
@@ -678,13 +646,13 @@
if (strspn(optarg, "0123456789") != strlen(optarg)) {
char* cp = parseTime(tail_time, optarg);
if (!cp) {
- LogcatPanic(HELP_FALSE, "-%c \"%s\" not in time format\n", c, optarg);
+ error(EXIT_FAILURE, 0, "-%c '%s' not in time format.", c, optarg);
}
if (*cp) {
char ch = *cp;
*cp = '\0';
- fprintf(stderr, "WARNING: -%c \"%s\"\"%c%s\" time truncated\n", c, optarg,
- ch, cp + 1);
+ fprintf(stderr, "WARNING: -%c '%s' '%c%s' time truncated\n", c, optarg, ch,
+ cp + 1);
*cp = ch;
}
} else {
@@ -705,8 +673,8 @@
case 'm': {
if (!ParseUint(optarg, &max_count_) || max_count_ < 1) {
- LogcatPanic(HELP_FALSE, "-%c \"%s\" isn't an integer greater than zero\n", c,
- optarg);
+ error(EXIT_FAILURE, 0, "-%c '%s' isn't an integer greater than zero.", c,
+ optarg);
}
} break;
@@ -719,7 +687,7 @@
case 'G': {
if (!ParseByteCount(optarg, &setLogSize) || setLogSize < 1) {
- LogcatPanic(HELP_FALSE, "ERROR: -G <num><multiplier>\n");
+ error(EXIT_FAILURE, 0, "-G must be specified as <num><multiplier>.");
}
} break;
@@ -743,7 +711,8 @@
} else {
log_id_t log_id = android_name_to_log_id(buffer.c_str());
if (log_id >= LOG_ID_MAX) {
- LogcatPanic(HELP_TRUE, "unknown buffer %s\n", buffer.c_str());
+ error(EXIT_FAILURE, 0, "Unknown buffer '%s' listed for -b.",
+ buffer.c_str());
}
if (log_id == LOG_ID_SECURITY) {
security_buffer_selected = true;
@@ -767,13 +736,13 @@
case 'r':
if (!ParseUint(optarg, &log_rotate_size_kb_) || log_rotate_size_kb_ < 1) {
- LogcatPanic(HELP_TRUE, "Invalid parameter \"%s\" to -r\n", optarg);
+ error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -r.", optarg);
}
break;
case 'n':
if (!ParseUint(optarg, &max_rotated_logs_) || max_rotated_logs_ < 1) {
- LogcatPanic(HELP_TRUE, "Invalid parameter \"%s\" to -n\n", optarg);
+ error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -n.", optarg);
}
break;
@@ -785,7 +754,7 @@
for (const auto& arg : Split(optarg, delimiters)) {
int err = SetLogFormat(arg.c_str());
if (err < 0) {
- LogcatPanic(HELP_FORMAT, "Invalid parameter \"%s\" to -v\n", arg.c_str());
+ error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -v.", arg.c_str());
}
if (err) hasSetLogFormat = true;
}
@@ -882,20 +851,25 @@
break;
case ':':
- LogcatPanic(HELP_TRUE, "Option -%c needs an argument\n", optopt);
+ error(EXIT_FAILURE, 0, "Option '%s' needs an argument.", argv[optind - 1]);
+ break;
case 'h':
show_help();
show_format_help();
return EXIT_SUCCESS;
+ case '?':
+ error(EXIT_FAILURE, 0, "Unknown option '%s'.", argv[optind - 1]);
+ break;
+
default:
- LogcatPanic(HELP_TRUE, "Unrecognized Option %c\n", optopt);
+ error(EXIT_FAILURE, 0, "Unknown getopt_long() result '%c'.", c);
}
}
if (max_count_ && got_t) {
- LogcatPanic(HELP_TRUE, "Cannot use -m (--max-count) and -t together\n");
+ error(EXIT_FAILURE, 0, "Cannot use -m (--max-count) and -t together.");
}
if (print_it_anyways_ && (!regex_ || !max_count_)) {
// One day it would be nice if --print -v color and --regex <expr>
@@ -915,12 +889,12 @@
}
if (log_rotate_size_kb_ != 0 && !output_file_name_) {
- LogcatPanic(HELP_TRUE, "-r requires -f as well\n");
+ error(EXIT_FAILURE, 0, "-r requires -f as well.");
}
if (setId != 0) {
if (!output_file_name_) {
- LogcatPanic(HELP_TRUE, "--id='%s' requires -f as well\n", setId);
+ error(EXIT_FAILURE, 0, "--id='%s' requires -f as well.", setId);
}
std::string file_name = StringPrintf("%s.id", output_file_name_);
@@ -952,7 +926,7 @@
if (forceFilters.size()) {
int err = android_log_addFilterString(logformat_.get(), forceFilters.c_str());
if (err < 0) {
- LogcatPanic(HELP_FALSE, "Invalid filter expression in logcat args\n");
+ error(EXIT_FAILURE, 0, "Invalid filter expression in logcat args.");
}
} else if (argc == optind) {
// Add from environment variable
@@ -962,7 +936,7 @@
int err = android_log_addFilterString(logformat_.get(), env_tags_orig);
if (err < 0) {
- LogcatPanic(HELP_TRUE, "Invalid filter expression in ANDROID_LOG_TAGS\n");
+ error(EXIT_FAILURE, 0, "Invalid filter expression in ANDROID_LOG_TAGS.");
}
}
} else {
@@ -970,18 +944,53 @@
for (int i = optind ; i < argc ; i++) {
int err = android_log_addFilterString(logformat_.get(), argv[i]);
if (err < 0) {
- LogcatPanic(HELP_TRUE, "Invalid filter expression '%s'\n", argv[i]);
+ error(EXIT_FAILURE, 0, "Invalid filter expression '%s'.", argv[i]);
}
}
}
if (mode & ANDROID_LOG_PSTORE) {
+ if (output_file_name_) {
+ error(EXIT_FAILURE, 0, "-c is ambiguous with both -f and -L specified.");
+ }
+ if (setLogSize || getLogSize || printStatistics || getPruneList || setPruneList) {
+ error(EXIT_FAILURE, 0, "-L is incompatible with -g/-G, -S, and -p/-P.");
+ }
if (clearLog) {
unlink("/sys/fs/pstore/pmsg-ramoops-0");
return EXIT_SUCCESS;
}
+ }
+
+ if (output_file_name_) {
if (setLogSize || getLogSize || printStatistics || getPruneList || setPruneList) {
- LogcatPanic(HELP_TRUE, "-L is incompatible with -g/-G, -S, and -p/-P");
+ error(EXIT_FAILURE, 0, "-f is incompatible with -g/-G, -S, and -p/-P.");
+ }
+
+ if (clearLog || setId) {
+ int max_rotation_count_digits =
+ max_rotated_logs_ > 0 ? (int)(floor(log10(max_rotated_logs_) + 1)) : 0;
+
+ for (int i = max_rotated_logs_; i >= 0; --i) {
+ std::string file;
+
+ if (!i) {
+ file = output_file_name_;
+ } else {
+ file = StringPrintf("%s.%.*d", output_file_name_, max_rotation_count_digits, i);
+ }
+
+ int err = unlink(file.c_str());
+
+ if (err < 0 && errno != ENOENT) {
+ fprintf(stderr, "failed to delete log file '%s': %s\n", file.c_str(),
+ strerror(errno));
+ }
+ }
+ }
+
+ if (clearLog) {
+ return EXIT_SUCCESS;
}
}
@@ -1009,35 +1018,8 @@
continue;
}
- if (clearLog || setId) {
- if (output_file_name_) {
- int max_rotation_count_digits =
- max_rotated_logs_ > 0 ? (int)(floor(log10(max_rotated_logs_) + 1)) : 0;
-
- for (int i = max_rotated_logs_; i >= 0; --i) {
- std::string file;
-
- if (!i) {
- file = output_file_name_;
- } else {
- file = StringPrintf("%s.%.*d", output_file_name_, max_rotation_count_digits,
- i);
- }
-
- if (!file.length()) {
- perror("while clearing log files");
- ReportErrorName(buffer_name, security_buffer_selected, &clear_failures);
- break;
- }
-
- int err = unlink(file.c_str());
-
- if (err < 0 && errno != ENOENT) {
- perror("while clearing log files");
- ReportErrorName(buffer_name, security_buffer_selected, &clear_failures);
- }
- }
- } else if (android_logger_clear(logger)) {
+ if (clearLog) {
+ if (android_logger_clear(logger)) {
ReportErrorName(buffer_name, security_buffer_selected, &clear_failures);
}
}
@@ -1070,85 +1052,70 @@
// report any errors in the above loop and exit
if (!open_device_failures.empty()) {
- LogcatPanic(HELP_FALSE, "Unable to open log device%s '%s'\n",
- open_device_failures.size() > 1 ? "s" : "",
- Join(open_device_failures, ",").c_str());
+ error(EXIT_FAILURE, 0, "Unable to open log device%s '%s'.",
+ open_device_failures.size() > 1 ? "s" : "", Join(open_device_failures, ",").c_str());
}
if (!clear_failures.empty()) {
- LogcatPanic(HELP_FALSE, "failed to clear the '%s' log%s\n",
- Join(clear_failures, ",").c_str(), clear_failures.size() > 1 ? "s" : "");
+ error(EXIT_FAILURE, 0, "failed to clear the '%s' log%s.", Join(clear_failures, ",").c_str(),
+ clear_failures.size() > 1 ? "s" : "");
}
if (!set_size_failures.empty()) {
- LogcatPanic(HELP_FALSE, "failed to set the '%s' log size%s\n",
- Join(set_size_failures, ",").c_str(), set_size_failures.size() > 1 ? "s" : "");
+ error(EXIT_FAILURE, 0, "failed to set the '%s' log size%s.",
+ Join(set_size_failures, ",").c_str(), set_size_failures.size() > 1 ? "s" : "");
}
if (!get_size_failures.empty()) {
- LogcatPanic(HELP_FALSE, "failed to get the readable '%s' log size%s\n",
- Join(get_size_failures, ",").c_str(), get_size_failures.size() > 1 ? "s" : "");
+ error(EXIT_FAILURE, 0, "failed to get the readable '%s' log size%s.",
+ Join(get_size_failures, ",").c_str(), get_size_failures.size() > 1 ? "s" : "");
}
if (setPruneList) {
size_t len = strlen(setPruneList);
- // extra 32 bytes are needed by android_logger_set_prune_list
- size_t bLen = len + 32;
- char* buf = nullptr;
- if (asprintf(&buf, "%-*s", (int)(bLen - 1), setPruneList) > 0) {
- buf[len] = '\0';
- if (android_logger_set_prune_list(logger_list.get(), buf, bLen)) {
- LogcatPanic(HELP_FALSE, "failed to set the prune list");
- }
- free(buf);
- } else {
- LogcatPanic(HELP_FALSE, "failed to set the prune list (alloc)");
+ if (android_logger_set_prune_list(logger_list.get(), setPruneList, len)) {
+ error(EXIT_FAILURE, 0, "Failed to set the prune list.");
}
return EXIT_SUCCESS;
}
if (printStatistics || getPruneList) {
- size_t len = 8192;
- char* buf;
+ std::string buf(8192, '\0');
+ size_t ret_length = 0;
+ int retry = 32;
- for (int retry = 32; (retry >= 0) && ((buf = new char[len]));
- delete[] buf, buf = nullptr, --retry) {
+ for (; retry >= 0; --retry) {
if (getPruneList) {
- android_logger_get_prune_list(logger_list.get(), buf, len);
+ android_logger_get_prune_list(logger_list.get(), buf.data(), buf.size());
} else {
- android_logger_get_statistics(logger_list.get(), buf, len);
+ android_logger_get_statistics(logger_list.get(), buf.data(), buf.size());
}
- buf[len - 1] = '\0';
- if (atol(buf) < 3) {
- delete[] buf;
- buf = nullptr;
+
+ ret_length = atol(buf.c_str());
+ if (ret_length < 3) {
+ error(EXIT_FAILURE, 0, "Failed to read data.");
+ }
+
+ if (ret_length < buf.size()) {
break;
}
- size_t ret = atol(buf) + 1;
- if (ret <= len) {
- len = ret;
- break;
- }
- len = ret;
+
+ buf.resize(ret_length + 1);
}
- if (!buf) {
- LogcatPanic(HELP_FALSE, "failed to read data");
+ if (retry < 0) {
+ error(EXIT_FAILURE, 0, "Failed to read data.");
}
- // remove trailing FF
- char* cp = buf + len - 1;
- *cp = '\0';
- bool truncated = *--cp != '\f';
- if (!truncated) *cp = '\0';
-
- // squash out the byte count
- cp = buf;
- if (!truncated) {
- while (isdigit(*cp)) ++cp;
- if (*cp == '\n') ++cp;
+ buf.resize(ret_length);
+ if (buf.back() == '\f') {
+ buf.pop_back();
}
- len = strlen(cp);
+ // Remove the byte count prefix
+ const char* cp = buf.c_str();
+ while (isdigit(*cp)) ++cp;
+ if (*cp == '\n') ++cp;
+
+ size_t len = strlen(cp);
TEMP_FAILURE_RETRY(write(output_fd_.get(), cp, len));
- delete[] buf;
return EXIT_SUCCESS;
}
@@ -1160,30 +1127,29 @@
struct log_msg log_msg;
int ret = android_logger_list_read(logger_list.get(), &log_msg);
if (!ret) {
- LogcatPanic(HELP_FALSE, R"init(read: unexpected EOF!
+ error(EXIT_FAILURE, 0, R"init(Unexpected EOF!
This means that either logd crashed, or more likely, this instance of logcat was unable to read log
messages as quickly as they were being produced.
-If you have enabled significant logging, look into using the -G option to increase log buffer sizes.
-)init");
+If you have enabled significant logging, look into using the -G option to increase log buffer sizes.)init");
}
if (ret < 0) {
if (ret == -EAGAIN) break;
if (ret == -EIO) {
- LogcatPanic(HELP_FALSE, "read: unexpected EOF!\n");
+ error(EXIT_FAILURE, 0, "Unexpected EOF!");
}
if (ret == -EINVAL) {
- LogcatPanic(HELP_FALSE, "read: unexpected length.\n");
+ error(EXIT_FAILURE, 0, "Unexpected length.");
}
- LogcatPanic(HELP_FALSE, "logcat read failure\n");
+ error(EXIT_FAILURE, errno, "Logcat read failure");
}
if (log_msg.id() > LOG_ID_MAX) {
- LogcatPanic(HELP_FALSE, "read: unexpected log id (%d) over LOG_ID_MAX (%d)",
- log_msg.id(), LOG_ID_MAX);
+ error(EXIT_FAILURE, 0, "Unexpected log id (%d) over LOG_ID_MAX (%d).", log_msg.id(),
+ LOG_ID_MAX);
}
PrintDividers(log_msg.id(), printDividers);
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 7a843d8..694b5fa 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -19,6 +19,7 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <math.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
@@ -186,14 +187,26 @@
: LogCommand("getStatistics"), mBuf(*buf) {
}
-static std::string package_string(const std::string& str) {
- // Calculate total buffer size prefix, count is the string length w/o nul
- char fmt[32];
- for (size_t l = str.length(), y = 0, x = 6; y != x;
- y = x, x = strlen(fmt) - 2) {
- snprintf(fmt, sizeof(fmt), "%zu\n%%s\n\f", l + x);
+// This returns a string with a length prefix with the format <length>\n<data>\n\f. The length
+// prefix includes the length of the prefix itself.
+static std::string PackageString(const std::string& str) {
+ size_t overhead_length = 3; // \n \n \f.
+
+ // Number of digits needed to represent length(str + overhead_length).
+ size_t str_size_digits = 1 + static_cast<size_t>(log10(str.size() + overhead_length));
+ // Number of digits needed to represent the total size.
+ size_t total_size_digits =
+ 1 + static_cast<size_t>(log10(str.size() + overhead_length + str_size_digits));
+
+ // If adding the size prefix causes a new digit to be required to represent the new total
+ // size, add it to the 'overhead_length'. This can only happen once, since each new digit
+ // allows for 10x the previous size to be recorded.
+ if (total_size_digits != str_size_digits) {
+ overhead_length++;
}
- return android::base::StringPrintf(fmt, str.c_str());
+
+ size_t total_size = str.size() + overhead_length + str_size_digits;
+ return android::base::StringPrintf("%zu\n%s\n\f", total_size, str.c_str());
}
int CommandListener::GetStatisticsCmd::runCommand(SocketClient* cli, int argc,
@@ -228,8 +241,7 @@
}
}
- cli->sendMsg(
- package_string(mBuf.formatStatistics(uid, pid, logMask)).c_str());
+ cli->sendMsg(PackageString(mBuf.formatStatistics(uid, pid, logMask)).c_str());
return 0;
}
@@ -240,7 +252,7 @@
int CommandListener::GetPruneListCmd::runCommand(SocketClient* cli,
int /*argc*/, char** /*argv*/) {
setname();
- cli->sendMsg(package_string(mBuf.formatPrune()).c_str());
+ cli->sendMsg(PackageString(mBuf.formatPrune()).c_str());
return 0;
}
@@ -316,12 +328,11 @@
cli->sendMsg("can not mix id= with either format= or name=");
return 0;
}
- cli->sendMsg(package_string(mBuf.formatEntry(atoi(id), uid)).c_str());
+ cli->sendMsg(PackageString(mBuf.formatEntry(atoi(id), uid)).c_str());
return 0;
}
- cli->sendMsg(
- package_string(mBuf.formatGetEventTag(uid, name, format)).c_str());
+ cli->sendMsg(PackageString(mBuf.formatGetEventTag(uid, name, format)).c_str());
return 0;
}
diff --git a/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h b/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
index 439813d..dfb1d11 100644
--- a/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
+++ b/property_service/libpropertyinfoserializer/include/property_info_serializer/property_info_serializer.h
@@ -14,8 +14,7 @@
// limitations under the License.
//
-#ifndef PROPERTY_INFO_SERIALIZER_H
-#define PROPERTY_INFO_SERIALIZER_H
+#pragma once
#include <string>
#include <vector>
@@ -41,11 +40,9 @@
const std::string& default_context, const std::string& default_type,
std::string* serialized_trie, std::string* error);
-void ParsePropertyInfoFile(const std::string& file_contents,
+void ParsePropertyInfoFile(const std::string& file_contents, bool require_prefix_or_exact,
std::vector<PropertyInfoEntry>* property_infos,
std::vector<std::string>* errors);
} // namespace properties
} // namespace android
-
-#endif
diff --git a/property_service/libpropertyinfoserializer/property_info_file.cpp b/property_service/libpropertyinfoserializer/property_info_file.cpp
index 2cdc62d..771a9ce 100644
--- a/property_service/libpropertyinfoserializer/property_info_file.cpp
+++ b/property_service/libpropertyinfoserializer/property_info_file.cpp
@@ -56,7 +56,8 @@
return false;
}
-bool ParsePropertyInfoLine(const std::string& line, PropertyInfoEntry* out, std::string* error) {
+bool ParsePropertyInfoLine(const std::string& line, bool require_prefix_or_exact,
+ PropertyInfoEntry* out, std::string* error) {
auto tokenizer = SpaceTokenizer(line);
auto property = tokenizer.GetNext();
@@ -72,7 +73,7 @@
}
// It is not an error to not find exact_match or a type, as older files will not contain them.
- auto exact_match = tokenizer.GetNext();
+ auto match_operation = tokenizer.GetNext();
// We reformat type to be space deliminated regardless of the input whitespace for easier storage
// and subsequent parsing.
auto type_strings = std::vector<std::string>{};
@@ -82,18 +83,27 @@
type = tokenizer.GetNext();
}
+ bool exact_match = false;
+ if (match_operation == "exact") {
+ exact_match = true;
+ } else if (match_operation != "prefix" && match_operation != "" && require_prefix_or_exact) {
+ *error = "Match operation '" + match_operation +
+ "' is not valid: must be either 'prefix' or 'exact'";
+ return false;
+ }
+
if (!type_strings.empty() && !IsTypeValid(type_strings)) {
*error = "Type '" + Join(type_strings, " ") + "' is not valid";
return false;
}
- *out = {property, context, Join(type_strings, " "), exact_match == "exact"};
+ *out = {property, context, Join(type_strings, " "), exact_match};
return true;
}
} // namespace
-void ParsePropertyInfoFile(const std::string& file_contents,
+void ParsePropertyInfoFile(const std::string& file_contents, bool require_prefix_or_exact,
std::vector<PropertyInfoEntry>* property_infos,
std::vector<std::string>* errors) {
// Do not clear property_infos to allow this function to be called on multiple files, with
@@ -108,7 +118,8 @@
auto property_info_entry = PropertyInfoEntry{};
auto parse_error = std::string{};
- if (!ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) {
+ if (!ParsePropertyInfoLine(trimmed_line, require_prefix_or_exact, &property_info_entry,
+ &parse_error)) {
errors->emplace_back(parse_error);
continue;
}
diff --git a/property_service/property_info_checker/property_info_checker.cpp b/property_service/property_info_checker/property_info_checker.cpp
index 52c4383..61b368e 100644
--- a/property_service/property_info_checker/property_info_checker.cpp
+++ b/property_service/property_info_checker/property_info_checker.cpp
@@ -153,7 +153,7 @@
}
auto errors = std::vector<std::string>{};
- ParsePropertyInfoFile(file_contents, &property_info_entries, &errors);
+ ParsePropertyInfoFile(file_contents, true, &property_info_entries, &errors);
if (!errors.empty()) {
for (const auto& error : errors) {
std::cerr << "Could not read line from '" << filename << "': " << error << std::endl;
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 994d9ae..2dbdb60 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -72,11 +72,12 @@
#
# 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 $(BOARD_ROOT_EXTRA_FOLDERS)); \
+ 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; \
ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
- ln -sf /sys/kernel/debug $(TARGET_ROOT_OUT)/d; \
+ ln -sfn /sys/kernel/debug $(TARGET_ROOT_OUT)/d; \
ln -sf /storage/self/primary $(TARGET_ROOT_OUT)/sdcard
ifdef BOARD_USES_VENDORIMAGE
LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/vendor
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/init.rc b/rootdir/init.rc
index 0327bd0..803d44a 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -38,12 +38,9 @@
# Allow up to 32K FDs per process
setrlimit nofile 32768 32768
- # Create directory to keep ld.config.txt
- mkdir /dev/linkerconfig 0755
-
# Generate ld.config.txt for early executed processes
- exec -- /system/bin/linkerconfig --target /dev/linkerconfig/ld.config.txt
- chmod 444 /dev/linkerconfig/ld.config.txt
+ exec -- /system/bin/linkerconfig --target /linkerconfig/ld.config.txt
+ chmod 444 /linkerconfig/ld.config.txt
start ueventd
@@ -579,6 +576,8 @@
mkdir /data/misc/profman 0770 system shell
mkdir /data/misc/gcov 0770 root root
mkdir /data/misc/installd 0700 root root
+ mkdir /data/misc/apexdata 0700 root root
+ mkdir /data/misc/apexrollback 0700 root root
mkdir /data/preloads 0775 system system encryption=None
@@ -651,12 +650,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
@@ -671,7 +693,9 @@
# Wait for apexd to finish activating APEXes before starting more processes.
wait_for_prop apexd.status ready
- parse_apex_configs
+ perform_apex_config
+
+ exec_start derive_sdk
init_user0
@@ -772,6 +796,11 @@
write /sys/fs/f2fs/${dev.mnt.blk.data}/cp_interval 200
write /sys/fs/f2fs/${dev.mnt.blk.data}/gc_urgent_sleep_time 50
+ # limit discard size to 128MB in order to avoid long IO latency
+ # for filesystem tuning first (dm or sda)
+ # Note that, if dm-<num> is used, sda/mmcblk0 should be tuned in vendor/init.rc
+ write /sys/devices/virtual/block/${dev.mnt.blk.data}/queue/discard_max_bytes 134217728
+
# Permissions for System Server and daemons.
chown system system /sys/power/autosleep
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/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/proxy.c b/trusty/storage/proxy/proxy.c
index c61f7d0..5f56408 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -46,6 +46,8 @@
return MMC_RPMB;
} else if (!strcmp(dev_type_name, "virt")) {
return VIRT_RPMB;
+ } else if (!strcmp(dev_type_name, "sock")) {
+ return SOCK_RPMB;
} else {
return UNKNOWN_RPMB;
}
diff --git a/trusty/storage/proxy/rpmb.c b/trusty/storage/proxy/rpmb.c
index 29827e2..0bd9e68 100644
--- a/trusty/storage/proxy/rpmb.c
+++ b/trusty/storage/proxy/rpmb.c
@@ -21,6 +21,8 @@
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <unistd.h>
#include <linux/major.h>
@@ -192,7 +194,7 @@
msg->result = STORAGE_ERR_GENERIC;
goto err_response;
}
- } else if (dev_type == VIRT_RPMB) {
+ } else if ((dev_type == VIRT_RPMB) || (dev_type == SOCK_RPMB)) {
size_t payload_size = req->reliable_write_size + req->write_size;
rc = send_virt_rpmb_req(rpmb_fd, read_buf, req->read_size, req->payload, payload_size);
if (rc < 0) {
@@ -234,12 +236,33 @@
int rc;
dev_type = open_dev_type;
- rc = open(rpmb_devname, O_RDWR, 0);
- if (rc < 0) {
- ALOGE("unable (%d) to open rpmb device '%s': %s\n", errno, rpmb_devname, strerror(errno));
- return rc;
+ if (dev_type != SOCK_RPMB) {
+ rc = open(rpmb_devname, O_RDWR, 0);
+ if (rc < 0) {
+ ALOGE("unable (%d) to open rpmb device '%s': %s\n", errno, rpmb_devname, strerror(errno));
+ return rc;
+ }
+ rpmb_fd = rc;
+ } else {
+ struct sockaddr_un unaddr;
+ struct sockaddr *addr = (struct sockaddr *)&unaddr;
+ rc = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (rc < 0) {
+ ALOGE("unable (%d) to create socket: %s\n", errno, strerror(errno));
+ return rc;
+ }
+ rpmb_fd = rc;
+
+ memset(&unaddr, 0, sizeof(unaddr));
+ unaddr.sun_family = AF_UNIX;
+ // TODO if it overflowed, bail rather than connecting?
+ strncpy(unaddr.sun_path, rpmb_devname, sizeof(unaddr.sun_path)-1);
+ rc = connect(rpmb_fd, addr, sizeof(unaddr));
+ if (rc < 0) {
+ ALOGE("unable (%d) to connect to rpmb socket '%s': %s\n", errno, rpmb_devname, strerror(errno));
+ return rc;
+ }
}
- rpmb_fd = rc;
return 0;
}
diff --git a/trusty/storage/proxy/rpmb.h b/trusty/storage/proxy/rpmb.h
index 4c330c9..09af3c5 100644
--- a/trusty/storage/proxy/rpmb.h
+++ b/trusty/storage/proxy/rpmb.h
@@ -18,7 +18,7 @@
#include <stdint.h>
#include <trusty/interface/storage.h>
-enum dev_type { UNKNOWN_RPMB, MMC_RPMB, VIRT_RPMB };
+enum dev_type { UNKNOWN_RPMB, MMC_RPMB, VIRT_RPMB, SOCK_RPMB };
int rpmb_open(const char* rpmb_devname, enum dev_type dev_type);
int rpmb_send(struct storage_msg* msg, const void* r, size_t req_len);
diff --git a/trusty/utils/rpmb_dev/Android.bp b/trusty/utils/rpmb_dev/Android.bp
new file mode 100644
index 0000000..e923e82
--- /dev/null
+++ b/trusty/utils/rpmb_dev/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+
+cc_binary {
+ name: "rpmb_dev",
+ vendor: true,
+
+ srcs: [
+ "rpmb_dev.c",
+ ],
+ shared_libs: [
+ "libc",
+ "liblog",
+ "libcrypto",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ init_rc: [
+ "rpmb_dev.rc",
+ ],
+}
diff --git a/trusty/utils/rpmb_dev/rpmb.h b/trusty/utils/rpmb_dev/rpmb.h
new file mode 100644
index 0000000..ab7e8d8
--- /dev/null
+++ b/trusty/utils/rpmb_dev/rpmb.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __RPMB_H__
+#define __RPMB_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct rpmb_key {
+ uint8_t byte[32];
+};
+
+struct rpmb_state;
+
+#define RPMB_BUF_SIZE 256
+
+/* provides */
+int rpmb_init(struct rpmb_state** statep,
+ void* mmc_handle,
+ const struct rpmb_key* key);
+void rpmb_uninit(struct rpmb_state* statep);
+int rpmb_read(struct rpmb_state* state,
+ void* buf,
+ uint16_t addr,
+ uint16_t count);
+/* count must be 1 or 2, addr must be aligned */
+int rpmb_write(struct rpmb_state* state,
+ const void* buf,
+ uint16_t addr,
+ uint16_t count,
+ bool sync);
+
+/* needs */
+int rpmb_send(void* mmc_handle,
+ void* reliable_write_buf,
+ size_t reliable_write_size,
+ void* write_buf,
+ size_t write_buf_size,
+ void* read_buf,
+ size_t read_buf_size,
+ bool sync);
+
+#endif
diff --git a/trusty/utils/rpmb_dev/rpmb_dev.c b/trusty/utils/rpmb_dev/rpmb_dev.c
new file mode 100644
index 0000000..af97eba
--- /dev/null
+++ b/trusty/utils/rpmb_dev/rpmb_dev.c
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define LOG_TAG "rpmb_mock"
+
+#include "rpmb_protocol.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <log/log.h>
+#include <openssl/hmac.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+/* verbose is an int for getopt */
+static int verbose = false;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+HMAC_CTX* HMAC_CTX_new(void) {
+ HMAC_CTX* ctx = malloc(sizeof(*ctx));
+ if (ctx != NULL) {
+ HMAC_CTX_init(ctx);
+ }
+ return ctx;
+}
+
+void HMAC_CTX_free(HMAC_CTX* ctx) {
+ if (ctx != NULL) {
+ HMAC_CTX_cleanup(ctx);
+ free(ctx);
+ }
+}
+
+#endif
+
+#define MAX_WRITE_COUNTER (0xffffffff)
+
+struct rpmb_data_header {
+ uint32_t write_counter;
+ uint16_t max_block;
+ uint8_t pad1;
+ uint8_t key_programmed;
+ struct rpmb_key key;
+ uint8_t pad[512 - 4 - 2 - 1 - 1 - sizeof(struct rpmb_key)];
+};
+
+#define MAX_PACKET_COUNT (8)
+
+struct rpmb_dev_state {
+ struct rpmb_data_header header;
+ struct rpmb_packet cmd[MAX_PACKET_COUNT];
+ struct rpmb_packet res[MAX_PACKET_COUNT];
+ uint16_t cmd_count;
+ uint16_t res_count;
+ int data_fd;
+};
+
+/* TODO: move to common location */
+static int rpmb_mac(struct rpmb_key key, struct rpmb_packet* packet, size_t packet_count,
+ struct rpmb_key* mac) {
+ size_t i;
+ int hmac_ret;
+ unsigned int md_len;
+ HMAC_CTX* hmac_ctx;
+
+ hmac_ctx = HMAC_CTX_new();
+ hmac_ret = HMAC_Init_ex(hmac_ctx, &key, sizeof(key), EVP_sha256(), NULL);
+ if (!hmac_ret) {
+ ALOGE("HMAC_Init_ex failed\n");
+ goto err;
+ }
+ for (i = 0; i < packet_count; i++) {
+ hmac_ret = HMAC_Update(hmac_ctx, packet[i].data, 284);
+ if (!hmac_ret) {
+ ALOGE("HMAC_Update failed\n");
+ goto err;
+ }
+ }
+ hmac_ret = HMAC_Final(hmac_ctx, mac->byte, &md_len);
+ if (md_len != sizeof(mac->byte)) {
+ ALOGE("bad md_len %d != %zd\n", md_len, sizeof(mac->byte));
+ exit(1);
+ }
+ if (!hmac_ret) {
+ ALOGE("HMAC_Final failed\n");
+ goto err;
+ }
+
+err:
+ HMAC_CTX_free(hmac_ctx);
+ return hmac_ret ? 0 : -1;
+}
+
+static int rpmb_file_seek(struct rpmb_dev_state* s, uint16_t addr) {
+ int ret;
+ int pos = addr * RPMB_PACKET_DATA_SIZE + sizeof(s->header);
+ ret = lseek(s->data_fd, pos, SEEK_SET);
+ if (ret != pos) {
+ ALOGE("rpmb_dev: seek to %d failed, got %d\n", pos, ret);
+ return -1;
+ }
+ return 0;
+}
+
+static uint16_t rpmb_dev_program_key(struct rpmb_dev_state* s) {
+ int ret;
+
+ if (s->header.key_programmed) {
+ return RPMB_RES_WRITE_FAILURE;
+ }
+
+ s->header.key = s->cmd[0].key_mac;
+ s->header.key_programmed = 1;
+
+ ret = lseek(s->data_fd, 0, SEEK_SET);
+ if (ret) {
+ ALOGE("rpmb_dev: Failed to seek rpmb data file\n");
+ return RPMB_RES_WRITE_FAILURE;
+ }
+
+ ret = write(s->data_fd, &s->header, sizeof(s->header));
+ if (ret != sizeof(s->header)) {
+ ALOGE("rpmb_dev: Failed to write rpmb key: %d, %s\n", ret, strerror(errno));
+
+ return RPMB_RES_WRITE_FAILURE;
+ }
+
+ return RPMB_RES_OK;
+}
+
+static uint16_t rpmb_dev_get_counter(struct rpmb_dev_state* s) {
+ s->res[0].write_counter = rpmb_u32(s->header.write_counter);
+
+ return RPMB_RES_OK;
+}
+
+static uint16_t rpmb_dev_data_write(struct rpmb_dev_state* s) {
+ uint16_t addr = rpmb_get_u16(s->cmd[0].address);
+ uint16_t block_count = s->cmd_count;
+ uint32_t write_counter;
+ int ret;
+
+ if (s->header.write_counter == MAX_WRITE_COUNTER) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Write counter expired\n");
+ }
+ return RPMB_RES_WRITE_FAILURE;
+ }
+
+ write_counter = rpmb_get_u32(s->cmd[0].write_counter);
+ if (s->header.write_counter != write_counter) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Invalid write counter %u. Expected: %u\n", write_counter,
+ s->header.write_counter);
+ }
+ return RPMB_RES_COUNT_FAILURE;
+ }
+
+ ret = rpmb_file_seek(s, addr);
+ if (ret) {
+ ALOGE("rpmb_dev: Failed to seek rpmb data file\n");
+ return RPMB_RES_WRITE_FAILURE;
+ }
+
+ for (int i = 0; i < block_count; i++) {
+ ret = write(s->data_fd, s->cmd[i].data, RPMB_PACKET_DATA_SIZE);
+ if (ret != RPMB_PACKET_DATA_SIZE) {
+ ALOGE("rpmb_dev: Failed to write rpmb data file: %d, %s\n", ret, strerror(errno));
+ return RPMB_RES_WRITE_FAILURE;
+ }
+ }
+
+ s->header.write_counter++;
+
+ ret = lseek(s->data_fd, 0, SEEK_SET);
+ if (ret) {
+ ALOGE("rpmb_dev: Failed to seek rpmb data file\n");
+ return RPMB_RES_WRITE_FAILURE;
+ }
+
+ ret = write(s->data_fd, &s->header.write_counter, sizeof(s->header.write_counter));
+ if (ret != sizeof(s->header.write_counter)) {
+ ALOGE("rpmb_dev: Failed to write rpmb write counter: %d, %s\n", ret, strerror(errno));
+
+ return RPMB_RES_WRITE_FAILURE;
+ }
+
+ s->res[0].write_counter = rpmb_u32(s->header.write_counter);
+ return RPMB_RES_OK;
+}
+
+static uint16_t rpmb_dev_data_read(struct rpmb_dev_state* s) {
+ uint16_t addr;
+ uint16_t block_count;
+ int ret;
+
+ addr = rpmb_get_u16(s->cmd[0].address);
+ block_count = s->res_count;
+
+ rpmb_file_seek(s, addr);
+
+ for (int i = 0; i < block_count; i++) {
+ ret = read(s->data_fd, s->res[i].data, RPMB_PACKET_DATA_SIZE);
+ if (ret != 0 && ret != RPMB_PACKET_DATA_SIZE) {
+ ALOGE("rpmb_dev: Failed to read rpmb data file: %d, %s\n", ret, strerror(errno));
+ return RPMB_RES_READ_FAILURE;
+ }
+ }
+
+ return RPMB_RES_OK;
+}
+
+struct rpmb_dev_cmd {
+ uint16_t (*func)(struct rpmb_dev_state* s);
+ uint16_t resp;
+ bool key_mac_is_key;
+ bool check_mac;
+ bool check_result_read;
+ bool check_key_programmed;
+ bool check_addr;
+ bool multi_packet_cmd;
+ bool multi_packet_res;
+ bool res_mac;
+};
+
+static struct rpmb_dev_cmd rpmb_dev_cmd_table[] = {
+ [RPMB_REQ_PROGRAM_KEY] =
+ {
+ .func = rpmb_dev_program_key,
+ .resp = RPMB_RESP_PROGRAM_KEY,
+ .key_mac_is_key = true,
+ .check_result_read = true,
+ },
+ [RPMB_REQ_GET_COUNTER] =
+ {
+ .func = rpmb_dev_get_counter,
+ .resp = RPMB_RESP_GET_COUNTER,
+ .check_key_programmed = true,
+ .res_mac = true,
+ },
+ [RPMB_REQ_DATA_WRITE] =
+ {
+ .func = rpmb_dev_data_write,
+ .resp = RPMB_RESP_DATA_WRITE,
+ .check_mac = true,
+ .check_result_read = true,
+ .check_key_programmed = true,
+ .check_addr = true,
+ .multi_packet_cmd = true,
+ .res_mac = true,
+ },
+ [RPMB_REQ_DATA_READ] =
+ {
+ .func = rpmb_dev_data_read,
+ .resp = RPMB_RESP_DATA_READ,
+ .check_addr = true,
+ .multi_packet_res = true,
+ .res_mac = true,
+ },
+};
+
+#define countof(arr) (sizeof(arr) / sizeof(arr[0]))
+
+static void rpmb_dev_process_cmd(struct rpmb_dev_state* s) {
+ assert(s->cmd_count > 0);
+ assert(s->res_count > 0);
+ uint16_t req_resp = rpmb_get_u16(s->cmd[0].req_resp);
+ uint16_t addr = rpmb_get_u16(s->cmd[0].address);
+ uint16_t sub_req;
+ uint16_t cmd_index = req_resp < countof(rpmb_dev_cmd_table) ? req_resp : 0;
+ struct rpmb_dev_cmd* cmd = &rpmb_dev_cmd_table[cmd_index];
+ uint16_t result = RPMB_RES_GENERAL_FAILURE;
+ struct rpmb_key mac;
+ uint16_t block_count = 0;
+
+ if (cmd->check_result_read) {
+ sub_req = rpmb_get_u16(s->cmd[s->cmd_count - 1].req_resp);
+ if (sub_req != RPMB_REQ_RESULT_READ) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, missing result read request, got %d, cmd_count %d\n",
+ req_resp, sub_req, s->cmd_count);
+ }
+ goto err;
+ }
+ assert(s->cmd_count > 1);
+ s->cmd_count--;
+ }
+
+ if (cmd->check_mac) {
+ if (rpmb_mac(s->header.key, s->cmd, s->cmd_count, &mac) != 0) {
+ ALOGE("rpmb_dev: failed to caclulate mac\n");
+ goto err;
+ }
+ } else if (cmd->key_mac_is_key) {
+ mac = s->cmd[s->cmd_count - 1].key_mac;
+ } else {
+ memset(mac.byte, 0, sizeof(mac.byte));
+ }
+
+ if (memcmp(&mac, s->cmd[s->cmd_count - 1].key_mac.byte, sizeof(mac))) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, invalid MAC, cmd_count %d\n", req_resp, s->cmd_count);
+ }
+ if (cmd->check_mac) {
+ result = RPMB_RES_AUTH_FAILURE;
+ }
+ goto err;
+ }
+
+ if (cmd->multi_packet_cmd) {
+ block_count = s->cmd_count;
+ }
+ if (cmd->multi_packet_res) {
+ block_count = s->res_count;
+ }
+
+ if (cmd->check_addr && (addr + block_count > s->header.max_block + 1)) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, invalid addr: 0x%x count 0x%x, Out of bounds. Max addr "
+ "0x%x\n",
+ req_resp, addr, block_count, s->header.max_block + 1);
+ }
+ result = RPMB_RES_ADDR_FAILURE;
+ goto err;
+ }
+ if (!cmd->check_addr && addr) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, invalid addr: 0x%x != 0\n", req_resp, addr);
+ }
+ goto err;
+ }
+
+ for (int i = 1; i < s->cmd_count; i++) {
+ sub_req = rpmb_get_u16(s->cmd[i].req_resp);
+ if (sub_req != req_resp) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, sub-request mismatch, %d, at %d\n", req_resp, i,
+ sub_req);
+ }
+ goto err;
+ }
+ }
+ if (!cmd->multi_packet_cmd && s->cmd_count != 1) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, bad cmd count %d, expected 1\n", req_resp, s->cmd_count);
+ }
+ goto err;
+ }
+ if (!cmd->multi_packet_res && s->res_count != 1) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, bad res count %d, expected 1\n", req_resp, s->res_count);
+ }
+ goto err;
+ }
+
+ if (cmd->check_key_programmed && !s->header.key_programmed) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Request %d, key is not programmed\n", req_resp);
+ }
+ s->res[0].result = rpmb_u16(RPMB_RES_NO_AUTH_KEY);
+ return;
+ }
+
+ if (!cmd->func) {
+ if (verbose) {
+ ALOGE("rpmb_dev: Unsupported request: %d\n", req_resp);
+ }
+ goto err;
+ }
+
+ result = cmd->func(s);
+
+err:
+ if (s->header.write_counter == MAX_WRITE_COUNTER) {
+ result |= RPMB_RES_WRITE_COUNTER_EXPIRED;
+ }
+
+ for (int i = 0; i < s->res_count; i++) {
+ s->res[i].nonce = s->cmd[0].nonce;
+ s->res[i].address = rpmb_u16(addr);
+ s->res[i].block_count = rpmb_u16(block_count);
+ s->res[i].result = rpmb_u16(result);
+ s->res[i].req_resp = rpmb_u16(cmd->resp);
+ }
+ if (cmd->res_mac) {
+ rpmb_mac(s->header.key, s->res, s->res_count, &s->res[s->res_count - 1].key_mac);
+ }
+}
+
+/*
+ * Receives data until one of the following is true:
+ * - The buffer is full (return will be len)
+ * - The connection closed (return > 0, < len)
+ * - An error occurred (return will be the negative error code from recv)
+ */
+ssize_t recv_until(int sock, void* dest_in, size_t len) {
+ size_t bytes_recvd = 0;
+ char* dest = dest_in;
+ while (bytes_recvd < len) {
+ ssize_t ret = recv(sock, dest, len - bytes_recvd, 0);
+ if (ret < 0) {
+ return ret;
+ }
+ dest += ret;
+ bytes_recvd += ret;
+ if (ret == 0) {
+ break;
+ }
+ }
+ return bytes_recvd;
+}
+
+/*
+ * Handles an incoming connection to the rpmb daemon.
+ * Returns 0 if the client disconnects without violating the protocol.
+ * Returns a negative value if we terminated the connection abnormally.
+ *
+ * Arguments:
+ * conn_sock - an fd to send/recv on
+ * s - an initialized rpmb device
+ */
+int handle_conn(struct rpmb_dev_state* s, int conn_sock) {
+ int ret;
+
+ while (true) {
+ memset(s->res, 0, sizeof(s->res));
+ ret = recv_until(conn_sock, &s->res_count, sizeof(s->res_count));
+
+ /*
+ * Disconnected while not in the middle of anything.
+ */
+ if (ret <= 0) {
+ return 0;
+ }
+
+ if (s->res_count > MAX_PACKET_COUNT) {
+ ALOGE("rpmb_dev: Receive count too large: %d\n", s->res_count);
+ return -1;
+ }
+ if (s->res_count <= 0) {
+ ALOGE("rpmb_dev: Receive count too small: %d\n", s->res_count);
+ return -1;
+ }
+
+ ret = recv_until(conn_sock, &s->cmd_count, sizeof(s->cmd_count));
+ if (ret != sizeof(s->cmd_count)) {
+ ALOGE("rpmb_dev: Failed to read cmd_count");
+ return -1;
+ }
+
+ if (s->cmd_count == 0) {
+ ALOGE("rpmb_dev: Must contain at least one command\n");
+ return -1;
+ }
+
+ if (s->cmd_count > MAX_PACKET_COUNT) {
+ ALOGE("rpmb_dev: Command count is too large\n");
+ return -1;
+ }
+
+ size_t cmd_size = s->cmd_count * sizeof(s->cmd[0]);
+ ret = recv_until(conn_sock, s->cmd, cmd_size);
+ if (ret != (int)cmd_size) {
+ ALOGE("rpmb_dev: Failed to read command: "
+ "cmd_size: %zu ret: %d, %s\n",
+ cmd_size, ret, strerror(errno));
+ return -1;
+ }
+
+ rpmb_dev_process_cmd(s);
+
+ size_t resp_size = sizeof(s->res[0]) * s->res_count;
+ ret = send(conn_sock, s->res, resp_size, 0);
+ if (ret != (int)resp_size) {
+ ALOGE("rpmb_dev: Failed to send response: %d, %s\n", ret, strerror(errno));
+ return -1;
+ }
+ }
+}
+
+void usage(const char* argv0) {
+ fprintf(stderr, "Usage: %s [-d|--dev] <datafile> [--sock] <socket_path>\n", argv0);
+ fprintf(stderr, "or: %s [-d|--dev] <datafile> [--size <size>] [--key key]\n", argv0);
+}
+
+int main(int argc, char** argv) {
+ struct rpmb_dev_state s;
+ int ret;
+ int cmdres_sock;
+ struct sockaddr_un cmdres_sockaddr;
+ const char* data_file_name = NULL;
+ const char* socket_path = NULL;
+ int open_flags;
+ int init = false;
+
+ struct option long_options[] = {{"size", required_argument, 0, 0},
+ {"key", required_argument, 0, 0},
+ {"sock", required_argument, 0, 0},
+ {"dev", required_argument, 0, 'd'},
+ {"init", no_argument, &init, true},
+ {"verbose", no_argument, &verbose, true},
+ {0, 0, 0, 0}};
+
+ memset(&s.header, 0, sizeof(s.header));
+
+ while (1) {
+ int c;
+ int option_index = 0;
+ c = getopt_long(argc, argv, "d:", long_options, &option_index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ /* long args */
+ case 0:
+ switch (option_index) {
+ /* size */
+ case 0:
+ s.header.max_block = atoi(optarg) - 1;
+ break;
+ /* key */
+ case 1:
+ for (size_t i = 0; i < sizeof(s.header.key.byte); i++) {
+ if (!optarg) {
+ break;
+ }
+ s.header.key.byte[i] = strtol(optarg, &optarg, 16);
+ s.header.key_programmed = 1;
+ }
+ break;
+ /* sock */
+ case 2:
+ socket_path = optarg;
+ break;
+ }
+ break;
+ /* dev */
+ case 'd':
+ data_file_name = optarg;
+ break;
+ default:
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /*
+ * We always need a data file, and at exactly one of --init or --sock
+ * must be specified.
+ */
+ if (!data_file_name || (!init == !socket_path)) {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ /*
+ * If the file is already initialized, exit early.
+ */
+ if (init && !access(data_file_name, F_OK)) {
+ return EXIT_SUCCESS;
+ }
+
+ open_flags = O_RDWR;
+ if (init) {
+ open_flags |= O_CREAT | O_TRUNC;
+ }
+ s.data_fd = open(data_file_name, open_flags, S_IWUSR | S_IRUSR);
+ if (s.data_fd < 0) {
+ ALOGE("rpmb_dev: Failed to open rpmb data file, %s: %s\n", data_file_name, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if (init) {
+ /* Create new rpmb data file */
+ if (s.header.max_block == 0) {
+ s.header.max_block = 512 - 1;
+ }
+ ret = write(s.data_fd, &s.header, sizeof(s.header));
+ if (ret != sizeof(s.header)) {
+ ALOGE("rpmb_dev: Failed to write rpmb data file: %d, %s\n", ret, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
+ }
+
+ ret = read(s.data_fd, &s.header, sizeof(s.header));
+ if (ret != sizeof(s.header)) {
+ ALOGE("rpmb_dev: Failed to read rpmb data file: %d, %s\n", ret, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ cmdres_sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (cmdres_sock < 0) {
+ ALOGE("rpmb_dev: Failed to create command/response socket: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ cmdres_sockaddr.sun_family = AF_UNIX;
+ strncpy(cmdres_sockaddr.sun_path, socket_path, sizeof(cmdres_sockaddr.sun_path));
+
+ ret = bind(cmdres_sock, (struct sockaddr*)&cmdres_sockaddr, sizeof(struct sockaddr_un));
+ if (ret < 0) {
+ ALOGE("rpmb_dev: Failed to bind command/response socket: %s: %s\n", socket_path,
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ ret = listen(cmdres_sock, 1);
+ if (ret < 0) {
+ ALOGE("rpmb_dev: Failed to listen on command/response socket: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ while (true) {
+ int conn_sock = accept(cmdres_sock, NULL, NULL);
+ if (conn_sock < 0) {
+ ALOGE("rpmb_dev: Could not accept connection: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+ ret = handle_conn(&s, conn_sock);
+ close(conn_sock);
+ if (ret) {
+ ALOGE("rpmb_dev: Connection terminated: %d", ret);
+ }
+ }
+}
diff --git a/trusty/utils/rpmb_dev/rpmb_dev.rc b/trusty/utils/rpmb_dev/rpmb_dev.rc
new file mode 100644
index 0000000..9f60e81
--- /dev/null
+++ b/trusty/utils/rpmb_dev/rpmb_dev.rc
@@ -0,0 +1,29 @@
+# RPMB Mock
+on post-fs-data
+ mkdir /data/vendor/ss
+ chown root system /data/vendor/ss
+ chmod 0770 /data/vendor/ss
+ rm /data/vendor/ss/rpmb_sock
+ start rpmb_mock_init
+ start rpmb_mock
+
+ # Storage proxy
+ start storageproxyd
+
+service storageproxyd /vendor/bin/storageproxyd -d /dev/trusty-ipc-dev0 \
+ -r /data/vendor/ss/rpmb_sock -p /data/vendor/ss -t sock
+ class main
+ disabled
+ user root
+
+service rpmb_mock_init /vendor/bin/rpmb_dev --dev /data/vendor/ss/RPMB_DATA --init --key "ea df 64 44 ea 65 5d 1c 87 27 d4 20 71 0d 53 42 dd 73 a3 38 63 e1 d7 94 c3 72 a6 ea e0 64 64 e6" --size 2048
+ disabled
+ user system
+ group system
+ oneshot
+
+service rpmb_mock /vendor/bin/rpmb_dev --dev /data/vendor/ss/RPMB_DATA --sock /data/vendor/ss/rpmb_sock
+ class main
+ disabled
+ user system
+ group system
diff --git a/trusty/utils/rpmb_dev/rpmb_protocol.h b/trusty/utils/rpmb_dev/rpmb_protocol.h
new file mode 100644
index 0000000..bfcb806
--- /dev/null
+++ b/trusty/utils/rpmb_dev/rpmb_protocol.h
@@ -0,0 +1,127 @@
+/*
+ * 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 <stdint.h>
+
+#include "rpmb.h" /* For struct rpmb_key */
+
+#define MMC_READ_MULTIPLE_BLOCK 18
+#define MMC_WRITE_MULTIPLE_BLOCK 25
+#define MMC_RELIABLE_WRITE_FLAG (1 << 31)
+
+#define MMC_RSP_PRESENT (1 << 0)
+#define MMC_RSP_CRC (1 << 2)
+#define MMC_RSP_OPCODE (1 << 4)
+#define MMC_CMD_ADTC (1 << 5)
+#define MMC_RSP_SPI_S1 (1 << 7)
+#define MMC_RSP_R1 (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE)
+#define MMC_RSP_SPI_R1 (MMC_RSP_SPI_S1)
+
+struct rpmb_nonce {
+ uint8_t byte[16];
+};
+
+struct rpmb_u16 {
+ uint8_t byte[2];
+};
+
+struct rpmb_u32 {
+ uint8_t byte[4];
+};
+
+#define RPMB_PACKET_DATA_SIZE (256)
+
+struct rpmb_packet {
+ uint8_t pad[196];
+ struct rpmb_key key_mac;
+ uint8_t data[RPMB_PACKET_DATA_SIZE];
+ struct rpmb_nonce nonce;
+ struct rpmb_u32 write_counter;
+ struct rpmb_u16 address;
+ struct rpmb_u16 block_count;
+ struct rpmb_u16 result;
+ struct rpmb_u16 req_resp;
+};
+
+enum rpmb_request {
+ RPMB_REQ_PROGRAM_KEY = 0x0001,
+ RPMB_REQ_GET_COUNTER = 0x0002,
+ RPMB_REQ_DATA_WRITE = 0x0003,
+ RPMB_REQ_DATA_READ = 0x0004,
+ RPMB_REQ_RESULT_READ = 0x0005,
+};
+
+enum rpmb_response {
+ RPMB_RESP_PROGRAM_KEY = 0x0100,
+ RPMB_RESP_GET_COUNTER = 0x0200,
+ RPMB_RESP_DATA_WRITE = 0x0300,
+ RPMB_RESP_DATA_READ = 0x0400,
+};
+
+enum rpmb_result {
+ RPMB_RES_OK = 0x0000,
+ RPMB_RES_GENERAL_FAILURE = 0x0001,
+ RPMB_RES_AUTH_FAILURE = 0x0002,
+ RPMB_RES_COUNT_FAILURE = 0x0003,
+ RPMB_RES_ADDR_FAILURE = 0x0004,
+ RPMB_RES_WRITE_FAILURE = 0x0005,
+ RPMB_RES_READ_FAILURE = 0x0006,
+ RPMB_RES_NO_AUTH_KEY = 0x0007,
+
+ RPMB_RES_WRITE_COUNTER_EXPIRED = 0x0080,
+};
+
+static inline struct rpmb_u16 rpmb_u16(uint16_t val) {
+ struct rpmb_u16 ret = {{
+ (uint8_t)(val >> 8),
+ (uint8_t)(val >> 0),
+ }};
+ return ret;
+}
+
+static inline struct rpmb_u32 rpmb_u32(uint32_t val) {
+ struct rpmb_u32 ret = {{
+ (uint8_t)(val >> 24),
+ (uint8_t)(val >> 16),
+ (uint8_t)(val >> 8),
+ (uint8_t)(val >> 0),
+ }};
+ return ret;
+}
+
+static inline uint16_t rpmb_get_u16(struct rpmb_u16 u16) {
+ size_t i;
+ uint16_t val;
+
+ val = 0;
+ for (i = 0; i < sizeof(u16.byte); i++)
+ val = val << 8 | u16.byte[i];
+
+ return val;
+}
+
+static inline uint32_t rpmb_get_u32(struct rpmb_u32 u32) {
+ size_t i;
+ uint32_t val;
+
+ val = 0;
+ for (i = 0; i < sizeof(u32.byte); i++)
+ val = val << 8 | u32.byte[i];
+
+ return val;
+}