Merge "init: first stage init tie stdout and stderr to /dev/kmsg"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 66d0c92..a3bd44f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,9 @@
"name": "libbase_test"
},
{
+ "name": "libpackagelistparser_test"
+ },
+ {
"name": "libprocinfo_test"
},
{
diff --git a/adb/Android.bp b/adb/Android.bp
index 47dafff..06cfcbf 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -27,6 +27,7 @@
"-DADB_HOST=1", // overridden by adbd_defaults
"-DALLOW_ADBD_ROOT=0", // overridden by adbd_defaults
"-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1",
+ "-DENABLE_FASTDEPLOY=1", // enable fast deploy
],
cpp_std: "experimental",
@@ -270,22 +271,33 @@
"client/console.cpp",
"client/adb_install.cpp",
"client/line_printer.cpp",
+ "client/fastdeploy.cpp",
+ "client/fastdeploycallbacks.cpp",
"shell_service_protocol.cpp",
],
+ generated_headers: [
+ "bin2c_fastdeployagent",
+ "bin2c_fastdeployagentscript"
+ ],
+
static_libs: [
"libadb_host",
+ "libandroidfw",
"libbase",
"libcutils",
"libcrypto_utils",
"libcrypto",
+ "libfastdeploy_host",
"libdiagnose_usb",
"liblog",
"libmdnssd",
+ "libprotobuf-cpp-lite",
"libusb",
"libutils",
"liblog",
- "libcutils",
+ "libziparchive",
+ "libz",
],
stl: "libc++_static",
@@ -295,10 +307,6 @@
// will violate ODR
shared_libs: [],
- required: [
- "deploypatchgenerator",
- ],
-
// Archive adb, adb.exe.
dist: {
targets: [
@@ -658,3 +666,90 @@
},
},
}
+
+// Note: using pipe for xxd to control the variable name generated
+// the default name used by xxd is the path to the input file.
+java_genrule {
+ name: "bin2c_fastdeployagent",
+ out: ["deployagent.inc"],
+ srcs: [":deployagent"],
+ cmd: "(echo 'unsigned char kDeployAgent[] = {' && xxd -i <$(in) && echo '};') > $(out)",
+}
+
+genrule {
+ name: "bin2c_fastdeployagentscript",
+ out: ["deployagentscript.inc"],
+ srcs: ["fastdeploy/deployagent/deployagent.sh"],
+ cmd: "(echo 'unsigned char kDeployAgentScript[] = {' && xxd -i <$(in) && echo '};') > $(out)",
+}
+
+cc_library_host_static {
+ name: "libfastdeploy_host",
+ defaults: ["adb_defaults"],
+ srcs: [
+ "fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp",
+ "fastdeploy/deploypatchgenerator/patch_utils.cpp",
+ "fastdeploy/proto/ApkEntry.proto",
+ ],
+ static_libs: [
+ "libadb_host",
+ "libandroidfw",
+ "libbase",
+ "libcutils",
+ "libcrypto_utils",
+ "libcrypto",
+ "libdiagnose_usb",
+ "liblog",
+ "libmdnssd",
+ "libusb",
+ "libutils",
+ "libziparchive",
+ "libz",
+ ],
+ stl: "libc++_static",
+ proto: {
+ type: "lite",
+ export_proto_headers: true,
+ },
+ target: {
+ windows: {
+ enabled: true,
+ shared_libs: ["AdbWinApi"],
+ },
+ },
+}
+
+cc_test_host {
+ name: "fastdeploy_test",
+ defaults: ["adb_defaults"],
+ srcs: [
+ "fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp",
+ "fastdeploy/deploypatchgenerator/patch_utils_test.cpp",
+ ],
+ static_libs: [
+ "libadb_host",
+ "libandroidfw",
+ "libbase",
+ "libcutils",
+ "libcrypto_utils",
+ "libcrypto",
+ "libdiagnose_usb",
+ "libfastdeploy_host",
+ "liblog",
+ "libmdnssd",
+ "libprotobuf-cpp-lite",
+ "libusb",
+ "libutils",
+ "libziparchive",
+ "libz",
+ ],
+ target: {
+ windows: {
+ enabled: true,
+ shared_libs: ["AdbWinApi"],
+ },
+ },
+ data: [
+ "fastdeploy/testdata/rotating_cube-release.apk",
+ ],
+}
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 24d4292..7dff1b8 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1243,11 +1243,7 @@
// TODO: Switch handle_forward_request to string_view.
std::string service_str(service);
if (handle_forward_request(
- service_str.c_str(),
- [=](std::string* error) {
- return acquire_one_transport(type, serial, transport_id, nullptr, error);
- },
- reply_fd)) {
+ service_str.c_str(), [=](std::string* error) { return s->transport; }, reply_fd)) {
return HostRequestResult::Handled;
}
diff --git a/adb/adb_utils.cpp b/adb/adb_utils.cpp
index cf5fbc8..d1910f1 100644
--- a/adb/adb_utils.cpp
+++ b/adb/adb_utils.cpp
@@ -320,6 +320,10 @@
}
std::string GetLogFilePath() {
+ // https://issuetracker.google.com/112588493
+ const char* path = getenv("ANDROID_ADB_LOG_PATH");
+ if (path) return path;
+
#if defined(_WIN32)
const char log_name[] = "adb.log";
WCHAR temp_path[MAX_PATH];
diff --git a/adb/client/adb_client.cpp b/adb/client/adb_client.cpp
index 7e408a8..d91ae35 100644
--- a/adb/client/adb_client.cpp
+++ b/adb/client/adb_client.cpp
@@ -149,7 +149,8 @@
return false;
}
-static int _adb_connect(std::string_view service, TransportId* transport, std::string* error) {
+static int _adb_connect(std::string_view service, TransportId* transport, std::string* error,
+ bool force_switch = false) {
LOG(DEBUG) << "_adb_connect: " << service;
if (service.empty() || service.size() > MAX_PAYLOAD) {
*error = android::base::StringPrintf("bad service name length (%zd)", service.size());
@@ -164,7 +165,7 @@
return -2;
}
- if (!service.starts_with("host")) {
+ if (!service.starts_with("host") || force_switch) {
std::optional<TransportId> transport_result = switch_socket_transport(fd.get(), error);
if (!transport_result) {
return -1;
@@ -323,7 +324,8 @@
return result;
}
-int adb_connect(TransportId* transport, std::string_view service, std::string* error) {
+int adb_connect(TransportId* transport, std::string_view service, std::string* error,
+ bool force_switch_device) {
LOG(DEBUG) << "adb_connect: service: " << service;
// Query the adb server's version.
@@ -336,7 +338,7 @@
return 0;
}
- unique_fd fd(_adb_connect(service, transport, error));
+ unique_fd fd(_adb_connect(service, transport, error, force_switch_device));
if (fd == -1) {
D("_adb_connect error: %s", error->c_str());
} else if(fd == -2) {
@@ -398,9 +400,15 @@
}
bool adb_get_feature_set(FeatureSet* feature_set, std::string* error) {
- std::string result;
- if (adb_query(format_host_command("features"), &result, error)) {
- *feature_set = StringToFeatureSet(result);
+ static FeatureSet* features = nullptr;
+ if (!features) {
+ std::string result;
+ if (adb_query(format_host_command("features"), &result, error)) {
+ features = new FeatureSet(StringToFeatureSet(result));
+ }
+ }
+ if (features) {
+ *feature_set = *features;
return true;
}
feature_set->clear();
diff --git a/adb/client/adb_client.h b/adb/client/adb_client.h
index fe1e584..ba53041 100644
--- a/adb/client/adb_client.h
+++ b/adb/client/adb_client.h
@@ -34,7 +34,11 @@
int adb_connect(std::string_view service, std::string* _Nonnull error);
// Same as above, except returning the TransportId for the service that we've connected to.
-int adb_connect(TransportId* _Nullable id, std::string_view service, std::string* _Nonnull error);
+// force_switch_device forces the function to attempt to select a device, even if the service
+// string appears to be a host: service (for use with host services that are device specific, like
+// forward).
+int adb_connect(TransportId* _Nullable id, std::string_view service, std::string* _Nonnull error,
+ bool force_switch_device = false);
// Kill the currently running adb server, if it exists.
bool adb_kill_server();
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 16fa215..a6e8998 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -16,6 +16,7 @@
#include "adb_install.h"
+#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -40,18 +41,31 @@
static constexpr int kFastDeployMinApi = 24;
#endif
+namespace {
+
+enum InstallMode {
+ INSTALL_DEFAULT,
+ INSTALL_PUSH,
+ INSTALL_STREAM,
+};
+
+}
+
static bool can_use_feature(const char* feature) {
FeatureSet features;
std::string error;
if (!adb_get_feature_set(&features, &error)) {
fprintf(stderr, "error: %s\n", error.c_str());
- return true;
+ return false;
}
return CanUseFeature(features, feature);
}
-static bool use_legacy_install() {
- return !can_use_feature(kFeatureCmd);
+static InstallMode best_install_mode() {
+ if (can_use_feature(kFeatureCmd)) {
+ return INSTALL_STREAM;
+ }
+ return INSTALL_PUSH;
}
static bool is_apex_supported() {
@@ -112,7 +126,7 @@
}
int uninstall_app(int argc, const char** argv) {
- if (use_legacy_install()) {
+ if (best_install_mode() == INSTALL_PUSH) {
return uninstall_app_legacy(argc, argv);
}
return uninstall_app_streamed(argc, argv);
@@ -200,32 +214,49 @@
return 1;
}
+#ifdef __linux__
+ posix_fadvise(local_fd.get(), 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+#endif
+
+ const bool use_abb = can_use_feature(kFeatureAbb);
std::string error;
- std::string cmd = "exec:cmd package";
+ std::vector<std::string> cmd_args = {use_abb ? "package" : "exec:cmd package"};
+ cmd_args.reserve(argc + 3);
// don't copy the APK name, but, copy the rest of the arguments as-is
while (argc-- > 1) {
- cmd += " " + escape_arg(std::string(*argv++));
+ if (use_abb) {
+ cmd_args.push_back(*argv++);
+ } else {
+ cmd_args.push_back(escape_arg(*argv++));
+ }
}
// add size parameter [required for streaming installs]
// do last to override any user specified value
- cmd += " " + android::base::StringPrintf("-S %" PRIu64, static_cast<uint64_t>(sb.st_size));
+ cmd_args.push_back("-S");
+ cmd_args.push_back(
+ android::base::StringPrintf("%" PRIu64, static_cast<uint64_t>(sb.st_size)));
if (is_apex) {
- cmd += " --apex";
+ cmd_args.push_back("--apex");
}
- unique_fd remote_fd(adb_connect(cmd, &error));
+ unique_fd remote_fd;
+ if (use_abb) {
+ remote_fd = send_abb_exec_command(cmd_args, &error);
+ } else {
+ remote_fd.reset(adb_connect(android::base::Join(cmd_args, " "), &error));
+ }
if (remote_fd < 0) {
fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
return 1;
}
- char buf[BUFSIZ];
copy_to_file(local_fd.get(), remote_fd.get());
- read_status_line(remote_fd.get(), buf, sizeof(buf));
+ char buf[BUFSIZ];
+ read_status_line(remote_fd.get(), buf, sizeof(buf));
if (!strncmp("Success", buf, 7)) {
fputs(buf, stdout);
return 0;
@@ -256,8 +287,7 @@
int result = -1;
std::vector<const char*> apk_file = {argv[last_apk]};
- std::string apk_dest =
- "/data/local/tmp/" + android::base::Basename(argv[last_apk]);
+ std::string apk_dest = "/data/local/tmp/" + android::base::Basename(argv[last_apk]);
if (use_fastdeploy == true) {
#if defined(ENABLE_FASTDEPLOY)
@@ -292,11 +322,7 @@
int install_app(int argc, const char** argv) {
std::vector<int> processedArgIndicies;
- enum installMode {
- INSTALL_DEFAULT,
- INSTALL_PUSH,
- INSTALL_STREAM
- } installMode = INSTALL_DEFAULT;
+ InstallMode installMode = INSTALL_DEFAULT;
bool use_fastdeploy = false;
bool is_reinstall = false;
bool use_localagent = false;
@@ -337,14 +363,10 @@
}
if (installMode == INSTALL_DEFAULT) {
- if (use_legacy_install()) {
- installMode = INSTALL_PUSH;
- } else {
- installMode = INSTALL_STREAM;
- }
+ installMode = best_install_mode();
}
- if (installMode == INSTALL_STREAM && use_legacy_install() == true) {
+ if (installMode == INSTALL_STREAM && best_install_mode() == INSTALL_PUSH) {
error_exit("Attempting to use streaming install on unsupported device");
}
@@ -409,7 +431,8 @@
android::base::EndsWithIgnoreCase(file, ".dm") ||
android::base::EndsWithIgnoreCase(file, ".fsv_sig")) {
struct stat sb;
- if (stat(file, &sb) != -1) total_size += sb.st_size;
+ if (stat(file, &sb) == -1) perror_exit("failed to stat \"%s\"", file);
+ total_size += sb.st_size;
first_apk = i;
} else {
break;
@@ -419,7 +442,7 @@
if (first_apk == -1) error_exit("need APK file on command line");
std::string install_cmd;
- if (use_legacy_install()) {
+ if (best_install_mode() == INSTALL_PUSH) {
install_cmd = "exec:pm";
} else {
install_cmd = "exec:cmd package";
@@ -459,13 +482,13 @@
}
// Valid session, now stream the APKs
- int success = 1;
+ bool success = true;
for (int i = first_apk; i < argc; i++) {
const char* file = argv[i];
struct stat sb;
if (stat(file, &sb) == -1) {
- fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
- success = 0;
+ fprintf(stderr, "adb: failed to stat \"%s\": %s\n", file, strerror(errno));
+ success = false;
goto finalize_session;
}
@@ -476,8 +499,8 @@
unique_fd local_fd(adb_open(file, O_RDONLY | O_CLOEXEC));
if (local_fd < 0) {
- fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
- success = 0;
+ fprintf(stderr, "adb: failed to open \"%s\": %s\n", file, strerror(errno));
+ success = false;
goto finalize_session;
}
@@ -485,7 +508,7 @@
unique_fd remote_fd(adb_connect(cmd, &error));
if (remote_fd < 0) {
fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
- success = 0;
+ success = false;
goto finalize_session;
}
@@ -493,15 +516,15 @@
read_status_line(remote_fd.get(), buf, sizeof(buf));
if (strncmp("Success", buf, 7)) {
- fprintf(stderr, "adb: failed to write %s\n", file);
+ fprintf(stderr, "adb: failed to write \"%s\"\n", file);
fputs(buf, stderr);
- success = 0;
+ success = false;
goto finalize_session;
}
}
finalize_session:
- // Commit session if we streamed everything okay; otherwise abandon
+ // Commit session if we streamed everything okay; otherwise abandon.
std::string service = android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
success ? "commit" : "abandon", session_id);
{
@@ -512,14 +535,16 @@
}
read_status_line(fd.get(), buf, sizeof(buf));
}
+ if (!success) return EXIT_FAILURE;
- if (!strncmp("Success", buf, 7)) {
- fputs(buf, stdout);
- return 0;
+ if (strncmp("Success", buf, 7)) {
+ fprintf(stderr, "adb: failed to finalize session\n");
+ fputs(buf, stderr);
+ return EXIT_FAILURE;
}
- fprintf(stderr, "adb: failed to finalize session\n");
- fputs(buf, stderr);
- return EXIT_FAILURE;
+
+ fputs(buf, stdout);
+ return EXIT_SUCCESS;
}
int install_multi_package(int argc, const char** argv) {
@@ -542,7 +567,7 @@
if (first_package == -1) error_exit("need APK or APEX files on command line");
- if (use_legacy_install()) {
+ if (best_install_mode() == INSTALL_PUSH) {
fprintf(stderr, "adb: multi-package install is not supported on this device\n");
return EXIT_FAILURE;
}
@@ -739,6 +764,20 @@
}
int delete_device_file(const std::string& filename) {
- std::string cmd = "rm -f " + escape_arg(filename);
- return send_shell_command(cmd);
+ // http://b/17339227 "Sideloading a Readonly File Results in a Prompt to
+ // Delete" caused us to add `-f` here, to avoid the equivalent of the `-i`
+ // prompt that you get from BSD rm (used in Android 5) if you have a
+ // non-writable file and stdin is a tty (which is true for old versions of
+ // adbd).
+ //
+ // Unfortunately, `rm -f` requires Android 4.3, so that workaround broke
+ // earlier Android releases. This was reported as http://b/37704384 "adb
+ // install -r passes invalid argument to rm on Android 4.1" and
+ // http://b/37035817 "ADB Fails: rm failed for -f, No such file or
+ // directory".
+ //
+ // Testing on a variety of devices and emulators shows that redirecting
+ // stdin is sufficient to avoid the pseudo-`-i`, and works on toolbox,
+ // BSD, and toybox versions of rm.
+ return send_shell_command("rm " + escape_arg(filename) + " </dev/null");
}
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 11a3dfd..599e0e6 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -96,9 +96,8 @@
" version show version num\n"
"\n"
"networking:\n"
- " connect HOST[:PORT] connect to a device via TCP/IP [default port=5555]\n"
- " disconnect [HOST[:PORT]]\n"
- " disconnect from given TCP/IP device [default port=5555], or all\n"
+ " connect HOST[:PORT] connect to a device via TCP/IP\n"
+ " disconnect [[HOST]:PORT] disconnect from given TCP/IP device, or all\n"
" forward --list list all forward socket connections\n"
" forward [--no-rebind] LOCAL REMOTE\n"
" forward socket connection using:\n"
@@ -130,7 +129,7 @@
" -a: preserve file timestamp and mode\n"
" sync [all|data|odm|oem|product|system|system_ext|vendor]\n"
" sync a local build from $ANDROID_PRODUCT_OUT to the device (default all)\n"
- " -l: list but don't copy\n"
+ " -l: list files that would be copied, but don't copy them\n"
"\n"
"shell:\n"
" shell [-e ESCAPE] [-n] [-Tt] [-x] [COMMAND...]\n"
@@ -154,6 +153,7 @@
" -d: allow version code downgrade (debuggable packages only)\n"
" -p: partial application install (install-multiple only)\n"
" -g: grant all runtime permissions\n"
+ " --abi ABI: override platform's default ABI\n"
" --instant: cause the app to be installed as an ephemeral install app\n"
" --no-streaming: always push APK to device and invoke Package Manager as separate steps\n"
" --streaming: force streaming APK directly into Package Manager\n"
@@ -165,6 +165,7 @@
#ifndef _WIN32
" --local-agent: locate agent files from local source build (instead of SDK location)\n"
#endif
+ " (See also `adb shell pm help` for more options.)\n"
//TODO--installlog <filename>
" uninstall [-k] PACKAGE\n"
" remove this app package from the device\n"
@@ -356,7 +357,7 @@
}
void copy_to_file(int inFd, int outFd) {
- std::vector<char> buf(32 * 1024);
+ std::vector<char> buf(64 * 1024);
int len;
long total = 0;
int old_stdin_mode = -1;
@@ -1615,13 +1616,13 @@
return adb_query_command(query);
}
else if (!strcmp(argv[0], "connect")) {
- if (argc != 2) error_exit("usage: adb connect <host>[:<port>]");
+ if (argc != 2) error_exit("usage: adb connect HOST[:PORT>]");
std::string query = android::base::StringPrintf("host:connect:%s", argv[1]);
return adb_query_command(query);
}
else if (!strcmp(argv[0], "disconnect")) {
- if (argc > 2) error_exit("usage: adb disconnect [<host>[:<port>]]");
+ if (argc > 2) error_exit("usage: adb disconnect [HOST[:PORT]]");
std::string query = android::base::StringPrintf("host:disconnect:%s",
(argc == 2) ? argv[1] : "");
@@ -1738,41 +1739,33 @@
// Determine the <host-prefix> for this command.
std::string host_prefix;
if (reverse) {
- host_prefix = "reverse";
+ host_prefix = "reverse:";
} else {
- if (serial) {
- host_prefix = android::base::StringPrintf("host-serial:%s", serial);
- } else if (transport_type == kTransportUsb) {
- host_prefix = "host-usb";
- } else if (transport_type == kTransportLocal) {
- host_prefix = "host-local";
- } else {
- host_prefix = "host";
- }
+ host_prefix = "host:";
}
std::string cmd, error_message;
if (strcmp(argv[0], "--list") == 0) {
if (argc != 1) error_exit("--list doesn't take any arguments");
- return adb_query_command(host_prefix + ":list-forward");
+ return adb_query_command(host_prefix + "list-forward");
} else if (strcmp(argv[0], "--remove-all") == 0) {
if (argc != 1) error_exit("--remove-all doesn't take any arguments");
- cmd = host_prefix + ":killforward-all";
+ cmd = "killforward-all";
} else if (strcmp(argv[0], "--remove") == 0) {
// forward --remove <local>
if (argc != 2) error_exit("--remove requires an argument");
- cmd = host_prefix + ":killforward:" + argv[1];
+ cmd = std::string("killforward:") + argv[1];
} else if (strcmp(argv[0], "--no-rebind") == 0) {
// forward --no-rebind <local> <remote>
if (argc != 3) error_exit("--no-rebind takes two arguments");
if (forward_targets_are_valid(argv[1], argv[2], &error_message)) {
- cmd = host_prefix + ":forward:norebind:" + argv[1] + ";" + argv[2];
+ cmd = std::string("forward:norebind:") + argv[1] + ";" + argv[2];
}
} else {
// forward <local> <remote>
if (argc != 2) error_exit("forward takes two arguments");
if (forward_targets_are_valid(argv[0], argv[1], &error_message)) {
- cmd = host_prefix + ":forward:" + argv[0] + ";" + argv[1];
+ cmd = std::string("forward:") + argv[0] + ";" + argv[1];
}
}
@@ -1780,7 +1773,7 @@
error_exit("error: %s", error_message.c_str());
}
- unique_fd fd(adb_connect(cmd, &error_message));
+ unique_fd fd(adb_connect(nullptr, host_prefix + cmd, &error_message, true));
if (fd < 0 || !adb_status(fd.get(), &error_message)) {
error_exit("error: %s", error_message.c_str());
}
@@ -1891,7 +1884,10 @@
} else if (!strcmp(argv[0], "track-jdwp")) {
return adb_connect_command("track-jdwp");
} else if (!strcmp(argv[0], "track-devices")) {
- return adb_connect_command("host:track-devices");
+ if (argc > 2 || (argc == 2 && strcmp(argv[1], "-l"))) {
+ error_exit("usage: adb track-devices [-l]");
+ }
+ return adb_connect_command(argc == 2 ? "host:track-devices-l" : "host:track-devices");
} else if (!strcmp(argv[0], "raw")) {
if (argc != 2) {
error_exit("usage: adb raw SERVICE");
diff --git a/adb/client/commandline.h b/adb/client/commandline.h
index 6cfd4f7..cd5933a 100644
--- a/adb/client/commandline.h
+++ b/adb/client/commandline.h
@@ -17,7 +17,11 @@
#ifndef COMMANDLINE_H
#define COMMANDLINE_H
+#include <android-base/strings.h>
+
#include "adb.h"
+#include "adb_client.h"
+#include "adb_unique_fd.h"
// Callback used to handle the standard streams (stdout and stderr) sent by the
// device's upon receiving a command.
@@ -105,4 +109,17 @@
const std::string& command, bool disable_shell_protocol = false,
StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK);
+// Connects to the device "abb" service with |command| and returns the fd.
+template <typename ContainerT>
+unique_fd send_abb_exec_command(const ContainerT& command_args, std::string* error) {
+ std::string service_string = "abb_exec:" + android::base::Join(command_args, ABB_ARG_DELIMETER);
+
+ unique_fd fd(adb_connect(service_string, error));
+ if (fd < 0) {
+ fprintf(stderr, "adb: failed to run abb_exec. Error: %s\n", error->c_str());
+ return unique_fd{};
+ }
+ return fd;
+}
+
#endif // COMMANDLINE_H
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
index f4e8664..fbae219 100644
--- a/adb/client/fastdeploy.cpp
+++ b/adb/client/fastdeploy.cpp
@@ -27,6 +27,9 @@
#include "androidfw/ZipFileRO.h"
#include "client/file_sync_client.h"
#include "commandline.h"
+#include "deployagent.inc" // Generated include via build rule.
+#include "deployagentscript.inc" // Generated include via build rule.
+#include "fastdeploy/deploypatchgenerator/deploy_patch_generator.h"
#include "fastdeploycallbacks.h"
#include "sysdeps.h"
@@ -35,6 +38,8 @@
static constexpr long kRequiredAgentVersion = 0x00000002;
static constexpr const char* kDeviceAgentPath = "/data/local/tmp/";
+static constexpr const char* kDeviceAgentFile = "/data/local/tmp/deployagent.jar";
+static constexpr const char* kDeviceAgentScript = "/data/local/tmp/deployagent";
static bool g_use_localagent = false;
@@ -71,46 +76,32 @@
g_use_localagent = use_localagent;
}
-// local_path - must start with a '/' and be relative to $ANDROID_PRODUCT_OUT
-static std::string get_agent_component_host_path(const char* local_path, const char* sdk_path) {
- std::string adb_dir = android::base::GetExecutableDirectory();
- if (adb_dir.empty()) {
- error_exit("Could not determine location of adb!");
- }
-
- if (g_use_localagent) {
- const char* product_out = getenv("ANDROID_PRODUCT_OUT");
- if (product_out == nullptr) {
- error_exit("Could not locate %s because $ANDROID_PRODUCT_OUT is not defined",
- local_path);
- }
- return android::base::StringPrintf("%s%s", product_out, local_path);
- } else {
- return adb_dir + sdk_path;
- }
-}
-
static bool deploy_agent(bool checkTimeStamps) {
std::vector<const char*> srcs;
- std::string jar_path =
- get_agent_component_host_path("/system/framework/deployagent.jar", "/deployagent.jar");
- std::string script_path =
- get_agent_component_host_path("/system/bin/deployagent", "/deployagent");
- srcs.push_back(jar_path.c_str());
- srcs.push_back(script_path.c_str());
-
- if (do_sync_push(srcs, kDeviceAgentPath, checkTimeStamps)) {
- // on windows the shell script might have lost execute permission
- // so need to set this explicitly
- const char* kChmodCommandPattern = "chmod 777 %sdeployagent";
- std::string chmodCommand =
- android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentPath);
- int ret = send_shell_command(chmodCommand);
- if (ret != 0) {
- error_exit("Error executing %s returncode: %d", chmodCommand.c_str(), ret);
- }
- } else {
- error_exit("Error pushing agent files to device");
+ // TODO: Deploy agent from bin2c directly instead of writing to disk first.
+ TemporaryFile tempAgent;
+ android::base::WriteFully(tempAgent.fd, kDeployAgent, sizeof(kDeployAgent));
+ srcs.push_back(tempAgent.path);
+ if (!do_sync_push(srcs, kDeviceAgentFile, checkTimeStamps)) {
+ error_exit("Failed to push fastdeploy agent to device.");
+ }
+ srcs.clear();
+ // TODO: Deploy agent from bin2c directly instead of writing to disk first.
+ TemporaryFile tempAgentScript;
+ android::base::WriteFully(tempAgentScript.fd, kDeployAgentScript, sizeof(kDeployAgentScript));
+ srcs.push_back(tempAgentScript.path);
+ if (!do_sync_push(srcs, kDeviceAgentScript, checkTimeStamps)) {
+ error_exit("Failed to push fastdeploy agent script to device.");
+ }
+ srcs.clear();
+ // on windows the shell script might have lost execute permission
+ // so need to set this explicitly
+ const char* kChmodCommandPattern = "chmod 777 %s";
+ std::string chmodCommand =
+ android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentScript);
+ int ret = send_shell_command(chmodCommand);
+ if (ret != 0) {
+ error_exit("Error executing %s returncode: %d", chmodCommand.c_str(), ret);
}
return true;
@@ -238,34 +229,15 @@
}
}
-static std::string get_patch_generator_command() {
- if (g_use_localagent) {
- // This should never happen on a Windows machine
- const char* host_out = getenv("ANDROID_HOST_OUT");
- if (host_out == nullptr) {
- error_exit(
- "Could not locate deploypatchgenerator.jar because $ANDROID_HOST_OUT "
- "is not defined");
- }
- return android::base::StringPrintf("java -jar %s/framework/deploypatchgenerator.jar",
- host_out);
- }
-
- std::string adb_dir = android::base::GetExecutableDirectory();
- if (adb_dir.empty()) {
- error_exit("Could not locate deploypatchgenerator.jar");
- }
- return android::base::StringPrintf(R"(java -jar "%s/deploypatchgenerator.jar")",
- adb_dir.c_str());
-}
-
void create_patch(const char* apkPath, const char* metadataPath, const char* patchPath) {
- std::string generatePatchCommand = android::base::StringPrintf(
- R"(%s "%s" "%s" > "%s")", get_patch_generator_command().c_str(), apkPath, metadataPath,
- patchPath);
- int returnCode = system(generatePatchCommand.c_str());
- if (returnCode != 0) {
- error_exit("Executing %s returned %d", generatePatchCommand.c_str(), returnCode);
+ DeployPatchGenerator generator(false);
+ unique_fd patchFd(adb_open(patchPath, O_WRONLY | O_CREAT | O_CLOEXEC));
+ if (patchFd < 0) {
+ perror_exit("adb: failed to create %s", patchPath);
+ }
+ bool success = generator.CreatePatch(apkPath, metadataPath, patchFd);
+ if (!success) {
+ error_exit("Failed to create patch for %s", apkPath);
}
}
diff --git a/adb/daemon/abb.cpp b/adb/daemon/abb.cpp
index 425438e..17c25e8 100644
--- a/adb/daemon/abb.cpp
+++ b/adb/daemon/abb.cpp
@@ -24,6 +24,7 @@
#include "adb_io.h"
#include "adb_utils.h"
#include "shell_service.h"
+#include "sysdeps.h"
namespace {
@@ -69,6 +70,11 @@
} // namespace
static int execCmd(std::string_view args, borrowed_fd in, borrowed_fd out, borrowed_fd err) {
+ int max_buf = LINUX_MAX_SOCKET_SIZE;
+ adb_setsockopt(in, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
+ adb_setsockopt(out, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
+ adb_setsockopt(err, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
+
AdbFdTextOutput oin(out);
AdbFdTextOutput oerr(err);
return cmdMain(parseCmdArgs(args), oin, oerr, in.get(), out.get(), err.get(),
@@ -98,6 +104,8 @@
}
unique_fd result = StartCommandInProcess(std::string(name), &execCmd, protocol);
+ int max_buf = LINUX_MAX_SOCKET_SIZE;
+ adb_setsockopt(result, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(max_buf));
if (android::base::SendFileDescriptors(fd, "", 1, result.get()) != 1) {
PLOG(ERROR) << "Failed to send an inprocess fd for command: " << data;
break;
diff --git a/adb/daemon/abb_service.cpp b/adb/daemon/abb_service.cpp
index a435279..e1df4a5 100644
--- a/adb/daemon/abb_service.cpp
+++ b/adb/daemon/abb_service.cpp
@@ -53,14 +53,13 @@
return error_fd;
}
- if (!SendProtocolString(socket_fd_, std::string(command))) {
+ if (!SendProtocolString(socket_fd_, command)) {
PLOG(ERROR) << "failed to send command to abb";
socket_fd_.reset();
continue;
}
unique_fd fd;
- std::string error;
char buf;
if (android::base::ReceiveFileDescriptors(socket_fd_, &buf, 1, &fd) != 1) {
PLOG(ERROR) << "failed to receive FD from abb";
diff --git a/adb/daemon/shell_service.cpp b/adb/daemon/shell_service.cpp
index de97068..0fb14c4 100644
--- a/adb/daemon/shell_service.cpp
+++ b/adb/daemon/shell_service.cpp
@@ -222,7 +222,7 @@
bool Subprocess::ForkAndExec(std::string* error) {
unique_fd child_stdinout_sfd, child_stderr_sfd;
unique_fd parent_error_sfd, child_error_sfd;
- char pts_name[PATH_MAX];
+ const char* pts_name = nullptr;
if (command_.empty()) {
__android_log_security_bswrite(SEC_TAG_ADB_SHELL_INTERACTIVE, "");
@@ -283,10 +283,22 @@
cenv.push_back(nullptr);
if (type_ == SubprocessType::kPty) {
- int fd;
- pid_ = forkpty(&fd, pts_name, nullptr, nullptr);
+ unique_fd pty_master(posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC));
+ if (pty_master == -1) {
+ *error =
+ android::base::StringPrintf("failed to create pty master: %s", strerror(errno));
+ return false;
+ }
+ if (unlockpt(pty_master.get()) != 0) {
+ *error = android::base::StringPrintf("failed to unlockpt pty master: %s",
+ strerror(errno));
+ return false;
+ }
+
+ pid_ = fork();
+ pts_name = ptsname(pty_master.get());
if (pid_ > 0) {
- stdinout_sfd_.reset(fd);
+ stdinout_sfd_ = std::move(pty_master);
}
} else {
if (!CreateSocketpair(&stdinout_sfd_, &child_stdinout_sfd)) {
diff --git a/adb/fastdeploy/Android.bp b/adb/fastdeploy/Android.bp
index 1ba0de0..95e1a28 100644
--- a/adb/fastdeploy/Android.bp
+++ b/adb/fastdeploy/Android.bp
@@ -13,27 +13,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
-
java_binary {
name: "deployagent",
sdk_version: "24",
srcs: ["deployagent/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
static_libs: ["apkzlib_zip"],
- wrapper: "deployagent/deployagent.sh",
proto: {
type: "lite",
},
dex_preopt: {
enabled: false,
}
-}
-
-java_binary_host {
- name: "deploypatchgenerator",
- srcs: ["deploypatchgenerator/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
- static_libs: ["apkzlib"],
- manifest: "deploypatchgenerator/manifest.txt",
- proto: {
- type: "full",
- }
-}
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
index 2d3b135..a8103c4 100644
--- a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
+++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
@@ -181,7 +181,7 @@
private static void extractMetaData(String packageName) throws IOException {
File apkFile = getFileFromPackageName(packageName);
APKMetaData apkMetaData = PatchUtils.getAPKMetaData(apkFile);
- apkMetaData.writeDelimitedTo(System.out);
+ apkMetaData.writeTo(System.out);
}
private static int createInstallSession(String[] args) throws IOException {
@@ -223,7 +223,6 @@
}
int writeExitCode = writePatchedDataToSession(new RandomAccessFile(deviceFile, "r"), deltaStream, sessionId);
-
if (writeExitCode == 0) {
return commitInstallSession(sessionId);
} else {
@@ -285,7 +284,6 @@
if (oldDataLen > 0) {
PatchUtils.pipe(oldData, outputStream, buffer, (int) oldDataLen);
}
-
newDataBytesWritten += copyLen + oldDataLen;
}
diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
index f0f00e1..c60f9a6 100644
--- a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
+++ b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
@@ -39,7 +39,7 @@
class PatchUtils {
private static final long NEGATIVE_MASK = 1L << 63;
private static final long NEGATIVE_LONG_SIGN_MASK = 1L << 63;
- public static final String SIGNATURE = "HAMADI/IHD";
+ public static final String SIGNATURE = "FASTDEPLOY";
private static long getOffsetFromEntry(StoredEntry entry) {
return entry.getCentralDirectoryHeader().getOffset() + entry.getLocalHeaderSize();
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
new file mode 100644
index 0000000..22c9243
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
@@ -0,0 +1,167 @@
+/*
+ * 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 "deploy_patch_generator.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "adb_unique_fd.h"
+#include "android-base/file.h"
+#include "patch_utils.h"
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+void DeployPatchGenerator::Log(const char* fmt, ...) {
+ if (!is_verbose_) {
+ return;
+ }
+ va_list ap;
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ printf("\n");
+ va_end(ap);
+}
+
+void DeployPatchGenerator::APKEntryToLog(const APKEntry& entry) {
+ Log("Filename: %s", entry.filename().c_str());
+ Log("CRC32: 0x%08llX", entry.crc32());
+ Log("Data Offset: %lld", entry.dataoffset());
+ Log("Compressed Size: %lld", entry.compressedsize());
+ Log("Uncompressed Size: %lld", entry.uncompressedsize());
+}
+
+void DeployPatchGenerator::APKMetaDataToLog(const char* file, const APKMetaData& metadata) {
+ if (!is_verbose_) {
+ return;
+ }
+ Log("APK Metadata: %s", file);
+ for (int i = 0; i < metadata.entries_size(); i++) {
+ const APKEntry& entry = metadata.entries(i);
+ APKEntryToLog(entry);
+ }
+}
+
+void DeployPatchGenerator::ReportSavings(const std::vector<SimpleEntry>& identicalEntries,
+ uint64_t totalSize) {
+ long totalEqualBytes = 0;
+ int totalEqualFiles = 0;
+ for (size_t i = 0; i < identicalEntries.size(); i++) {
+ if (identicalEntries[i].deviceEntry != nullptr) {
+ totalEqualBytes += identicalEntries[i].localEntry->compressedsize();
+ totalEqualFiles++;
+ }
+ }
+ float savingPercent = (totalEqualBytes * 100.0f) / totalSize;
+ fprintf(stderr, "Detected %d equal APK entries\n", totalEqualFiles);
+ fprintf(stderr, "%ld bytes are equal out of %" PRIu64 " (%.2f%%)\n", totalEqualBytes, totalSize,
+ savingPercent);
+}
+
+void DeployPatchGenerator::GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
+ const char* localApkPath, borrowed_fd output) {
+ unique_fd input(adb_open(localApkPath, O_RDONLY | O_CLOEXEC));
+ size_t newApkSize = adb_lseek(input, 0L, SEEK_END);
+ adb_lseek(input, 0L, SEEK_SET);
+
+ PatchUtils::WriteSignature(output);
+ PatchUtils::WriteLong(newApkSize, output);
+ size_t currentSizeOut = 0;
+ // Write data from the host upto the first entry we have that matches a device entry. Then write
+ // the metadata about the device entry and repeat for all entries that match on device. Finally
+ // write out any data left. If the device and host APKs are exactly the same this ends up
+ // writing out zip metadata from the local APK followed by offsets to the data to use from the
+ // device APK.
+ for (auto&& entry : entriesToUseOnDevice) {
+ int64_t deviceDataOffset = entry.deviceEntry->dataoffset();
+ int64_t hostDataOffset = entry.localEntry->dataoffset();
+ int64_t deviceDataLength = entry.deviceEntry->compressedsize();
+ int64_t deltaFromDeviceDataStart = hostDataOffset - currentSizeOut;
+ PatchUtils::WriteLong(deltaFromDeviceDataStart, output);
+ if (deltaFromDeviceDataStart > 0) {
+ PatchUtils::Pipe(input, output, deltaFromDeviceDataStart);
+ }
+ PatchUtils::WriteLong(deviceDataOffset, output);
+ PatchUtils::WriteLong(deviceDataLength, output);
+ adb_lseek(input, deviceDataLength, SEEK_CUR);
+ currentSizeOut += deltaFromDeviceDataStart + deviceDataLength;
+ }
+ if (currentSizeOut != newApkSize) {
+ PatchUtils::WriteLong(newApkSize - currentSizeOut, output);
+ PatchUtils::Pipe(input, output, newApkSize - currentSizeOut);
+ PatchUtils::WriteLong(0, output);
+ PatchUtils::WriteLong(0, output);
+ }
+}
+
+bool DeployPatchGenerator::CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
+ borrowed_fd output) {
+ std::string content;
+ APKMetaData deviceApkMetadata;
+ if (android::base::ReadFileToString(deviceApkMetadataPath, &content)) {
+ deviceApkMetadata.ParsePartialFromString(content);
+ } else {
+ // TODO: What do we want to do if we don't find any metadata.
+ // The current fallback behavior is to build a patch with the contents of |localApkPath|.
+ }
+
+ APKMetaData localApkMetadata = PatchUtils::GetAPKMetaData(localApkPath);
+ // Log gathered metadata info.
+ APKMetaDataToLog(deviceApkMetadataPath, deviceApkMetadata);
+ APKMetaDataToLog(localApkPath, localApkMetadata);
+
+ std::vector<SimpleEntry> identicalEntries;
+ uint64_t totalSize =
+ BuildIdenticalEntries(identicalEntries, localApkMetadata, deviceApkMetadata);
+ ReportSavings(identicalEntries, totalSize);
+ GeneratePatch(identicalEntries, localApkPath, output);
+ return true;
+}
+
+uint64_t DeployPatchGenerator::BuildIdenticalEntries(std::vector<SimpleEntry>& outIdenticalEntries,
+ const APKMetaData& localApkMetadata,
+ const APKMetaData& deviceApkMetadata) {
+ uint64_t totalSize = 0;
+ for (int i = 0; i < localApkMetadata.entries_size(); i++) {
+ const APKEntry& localEntry = localApkMetadata.entries(i);
+ totalSize += localEntry.compressedsize();
+ for (int j = 0; j < deviceApkMetadata.entries_size(); j++) {
+ const APKEntry& deviceEntry = deviceApkMetadata.entries(j);
+ if (deviceEntry.crc32() == localEntry.crc32() &&
+ deviceEntry.filename().compare(localEntry.filename()) == 0) {
+ SimpleEntry simpleEntry;
+ simpleEntry.localEntry = const_cast<APKEntry*>(&localEntry);
+ simpleEntry.deviceEntry = const_cast<APKEntry*>(&deviceEntry);
+ APKEntryToLog(localEntry);
+ outIdenticalEntries.push_back(simpleEntry);
+ break;
+ }
+ }
+ }
+ std::sort(outIdenticalEntries.begin(), outIdenticalEntries.end(),
+ [](const SimpleEntry& lhs, const SimpleEntry& rhs) {
+ return lhs.localEntry->dataoffset() < rhs.localEntry->dataoffset();
+ });
+ return totalSize;
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
new file mode 100644
index 0000000..30e41a5
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
@@ -0,0 +1,102 @@
+/*
+ * 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 <vector>
+
+#include "adb_unique_fd.h"
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+/**
+ * This class is responsible for creating a patch that can be accepted by the deployagent. The
+ * patch format is documented in GeneratePatch.
+ */
+class DeployPatchGenerator {
+ public:
+ /**
+ * Simple struct to hold mapping between local metadata and device metadata.
+ */
+ struct SimpleEntry {
+ com::android::fastdeploy::APKEntry* localEntry;
+ com::android::fastdeploy::APKEntry* deviceEntry;
+ };
+
+ /**
+ * If |is_verbose| is true ApkEntries that are similar between device and host are written to
+ * the console.
+ */
+ explicit DeployPatchGenerator(bool is_verbose) : is_verbose_(is_verbose) {}
+ /**
+ * Given a |localApkPath|, and the |deviceApkMetadataPath| from an installed APK this function
+ * writes a patch to the given |output|.
+ */
+ bool CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
+ android::base::borrowed_fd output);
+
+ private:
+ bool is_verbose_;
+
+ /**
+ * Log function only logs data to stdout when |is_verbose_| is true.
+ */
+ void Log(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3)));
+
+ /**
+ * Helper function to log the APKMetaData structure. If |is_verbose_| is false this function
+ * early outs. |file| is the path to the file represented by |metadata|. This function is used
+ * for debugging / information.
+ */
+ void APKMetaDataToLog(const char* file, const com::android::fastdeploy::APKMetaData& metadata);
+ /**
+ * Helper function to log APKEntry.
+ */
+ void APKEntryToLog(const com::android::fastdeploy::APKEntry& entry);
+
+ /**
+ * Helper function to report savings by fastdeploy. This function prints out savings even with
+ * |is_verbose_| set to false. |totalSize| is used to show a percentage of savings. Note:
+ * |totalSize| is the size of the ZipEntries. Not the size of the entire file. The metadata of
+ * the zip data needs to be sent across with every iteration.
+ * [Patch format]
+ * |Fixed String| Signature
+ * |long| New Size of Apk
+ * |Packets[]| Array of Packets
+ *
+ * [Packet Format]
+ * |long| Size of data to use from patch
+ * |byte[]| Patch data
+ * |long| Offset of data to use already on device
+ * |long| Length of data to read from device APK
+ * TODO(b/138306784): Move the patch format to a proto.
+ */
+ void ReportSavings(const std::vector<SimpleEntry>& identicalEntries, uint64_t totalSize);
+
+ /**
+ * This enumerates each entry in |entriesToUseOnDevice| and builds a patch file copying data
+ * from |localApkPath| where we are unable to use entries already on the device. The new patch
+ * is written to |output|. The entries are expected to be sorted by data offset from lowest to
+ * highest.
+ */
+ void GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
+ const char* localApkPath, android::base::borrowed_fd output);
+
+ protected:
+ uint64_t BuildIdenticalEntries(
+ std::vector<SimpleEntry>& outIdenticalEntries,
+ const com::android::fastdeploy::APKMetaData& localApkMetadata,
+ const com::android::fastdeploy::APKMetaData& deviceApkMetadataPath);
+};
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
new file mode 100644
index 0000000..9cdc44e
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "deploy_patch_generator.h"
+#include "patch_utils.h"
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+static std::string GetTestFile(const std::string& name) {
+ return "fastdeploy/testdata/" + name;
+}
+
+class TestPatchGenerator : DeployPatchGenerator {
+ public:
+ TestPatchGenerator() : DeployPatchGenerator(false) {}
+ void GatherIdenticalEntries(std::vector<DeployPatchGenerator::SimpleEntry>& outIdenticalEntries,
+ const APKMetaData& metadataA, const APKMetaData& metadataB) {
+ BuildIdenticalEntries(outIdenticalEntries, metadataA, metadataB);
+ }
+};
+
+TEST(DeployPatchGeneratorTest, IdenticalFileEntries) {
+ std::string apkPath = GetTestFile("rotating_cube-release.apk");
+ APKMetaData metadataA = PatchUtils::GetAPKMetaData(apkPath.c_str());
+ TestPatchGenerator generator;
+ std::vector<DeployPatchGenerator::SimpleEntry> entries;
+ generator.GatherIdenticalEntries(entries, metadataA, metadataA);
+ // Expect the entry count to match the number of entries in the metadata.
+ const uint32_t identicalCount = entries.size();
+ const uint32_t entriesCount = metadataA.entries_size();
+ EXPECT_EQ(identicalCount, entriesCount);
+}
+
+TEST(DeployPatchGeneratorTest, NoDeviceMetadata) {
+ std::string apkPath = GetTestFile("rotating_cube-release.apk");
+ // Get size of our test apk.
+ long apkSize = 0;
+ {
+ unique_fd apkFile(adb_open(apkPath.c_str(), O_RDWR));
+ apkSize = adb_lseek(apkFile, 0L, SEEK_END);
+ }
+
+ // Create a patch that is 100% different.
+ TemporaryFile output;
+ DeployPatchGenerator generator(true);
+ generator.CreatePatch(apkPath.c_str(), "", output.fd);
+
+ // Expect a patch file that has a size at least the size of our initial APK.
+ long patchSize = adb_lseek(output.fd, 0L, SEEK_END);
+ EXPECT_GT(patchSize, apkSize);
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/manifest.txt b/adb/fastdeploy/deploypatchgenerator/manifest.txt
deleted file mode 100644
index 5c00505..0000000
--- a/adb/fastdeploy/deploypatchgenerator/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.fastdeploy.DeployPatchGenerator
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
new file mode 100644
index 0000000..f11ddd1
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 "patch_utils.h"
+
+#include <androidfw/ZipFileRO.h>
+#include <stdio.h>
+
+#include "adb_io.h"
+#include "android-base/endian.h"
+#include "sysdeps.h"
+
+using namespace com::android;
+using namespace com::android::fastdeploy;
+using namespace android::base;
+
+static constexpr char kSignature[] = "FASTDEPLOY";
+
+APKMetaData PatchUtils::GetAPKMetaData(const char* apkPath) {
+ APKMetaData apkMetaData;
+#undef open
+ std::unique_ptr<android::ZipFileRO> zipFile(android::ZipFileRO::open(apkPath));
+#define open ___xxx_unix_open
+ if (zipFile == nullptr) {
+ printf("Could not open %s", apkPath);
+ exit(1);
+ }
+ void* cookie;
+ if (zipFile->startIteration(&cookie)) {
+ android::ZipEntryRO entry;
+ while ((entry = zipFile->nextEntry(cookie)) != NULL) {
+ char fileName[256];
+ // Make sure we have a file name.
+ // TODO: Handle filenames longer than 256.
+ if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) {
+ continue;
+ }
+
+ uint32_t uncompressedSize, compressedSize, crc32;
+ int64_t dataOffset;
+ zipFile->getEntryInfo(entry, nullptr, &uncompressedSize, &compressedSize, &dataOffset,
+ nullptr, &crc32);
+ APKEntry* apkEntry = apkMetaData.add_entries();
+ apkEntry->set_crc32(crc32);
+ apkEntry->set_filename(fileName);
+ apkEntry->set_compressedsize(compressedSize);
+ apkEntry->set_uncompressedsize(uncompressedSize);
+ apkEntry->set_dataoffset(dataOffset);
+ }
+ }
+ return apkMetaData;
+}
+
+void PatchUtils::WriteSignature(borrowed_fd output) {
+ WriteFdExactly(output, kSignature, sizeof(kSignature) - 1);
+}
+
+void PatchUtils::WriteLong(int64_t value, borrowed_fd output) {
+ int64_t toLittleEndian = htole64(value);
+ WriteFdExactly(output, &toLittleEndian, sizeof(int64_t));
+}
+
+void PatchUtils::Pipe(borrowed_fd input, borrowed_fd output, size_t amount) {
+ constexpr static int BUFFER_SIZE = 128 * 1024;
+ char buffer[BUFFER_SIZE];
+ size_t transferAmount = 0;
+ while (transferAmount != amount) {
+ long chunkAmount =
+ amount - transferAmount > BUFFER_SIZE ? BUFFER_SIZE : amount - transferAmount;
+ long readAmount = adb_read(input, buffer, chunkAmount);
+ WriteFdExactly(output, buffer, readAmount);
+ transferAmount += readAmount;
+ }
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.h b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
new file mode 100644
index 0000000..0ebfe8f
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
@@ -0,0 +1,46 @@
+/*
+ * 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 "adb_unique_fd.h"
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+/**
+ * Helper class that mirrors the PatchUtils from deploy agent.
+ */
+class PatchUtils {
+ public:
+ /**
+ * This function takes a local APK file and builds the APKMetaData required by the patching
+ * algorithm. The if this function has an error a string is printed to the terminal and exit(1)
+ * is called.
+ */
+ static com::android::fastdeploy::APKMetaData GetAPKMetaData(const char* file);
+ /**
+ * Writes a fixed signature string to the header of the patch.
+ */
+ static void WriteSignature(android::base::borrowed_fd output);
+ /**
+ * Writes an int64 to the |output| reversing the bytes.
+ */
+ static void WriteLong(int64_t value, android::base::borrowed_fd output);
+ /**
+ * Copy |amount| of data from |input| to |output|.
+ */
+ static void Pipe(android::base::borrowed_fd input, android::base::borrowed_fd output,
+ size_t amount);
+};
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
new file mode 100644
index 0000000..a7eeebf
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
@@ -0,0 +1,96 @@
+/*
+ * 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 "patch_utils.h"
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sstream>
+#include <string>
+
+#include "adb_io.h"
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+static std::string GetTestFile(const std::string& name) {
+ return "fastdeploy/testdata/" + name;
+}
+
+bool FileMatchesContent(android::base::borrowed_fd input, const char* contents,
+ ssize_t contentsSize) {
+ adb_lseek(input, 0, SEEK_SET);
+ // Use a temp buffer larger than any test contents.
+ constexpr int BUFFER_SIZE = 2048;
+ char buffer[BUFFER_SIZE];
+ bool result = true;
+ // Validate size of files is equal.
+ ssize_t readAmount = adb_read(input, buffer, BUFFER_SIZE);
+ EXPECT_EQ(readAmount, contentsSize);
+ result = memcmp(buffer, contents, readAmount) == 0;
+ for (int i = 0; i < readAmount; i++) {
+ printf("%x", buffer[i]);
+ }
+ printf(" == ");
+ for (int i = 0; i < contentsSize; i++) {
+ printf("%x", contents[i]);
+ }
+ printf("\n");
+
+ return result;
+}
+
+TEST(PatchUtilsTest, SwapLongWrites) {
+ TemporaryFile output;
+ PatchUtils::WriteLong(0x0011223344556677, output.fd);
+ adb_lseek(output.fd, 0, SEEK_SET);
+ const char expected[] = {0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
+ EXPECT_TRUE(FileMatchesContent(output.fd, expected, 8));
+}
+
+TEST(PatchUtilsTest, PipeWritesAmountToOutput) {
+ std::string expected("Some Data");
+ TemporaryFile input;
+ TemporaryFile output;
+ // Populate input file.
+ WriteFdExactly(input.fd, expected);
+ adb_lseek(input.fd, 0, SEEK_SET);
+ // Open input file for read, and output file for write.
+ PatchUtils::Pipe(input.fd, output.fd, expected.size());
+ // Validate pipe worked
+ EXPECT_TRUE(FileMatchesContent(output.fd, expected.c_str(), expected.size()));
+}
+
+TEST(PatchUtilsTest, SignatureConstMatches) {
+ std::string apkFile = GetTestFile("rotating_cube-release.apk");
+ TemporaryFile output;
+ PatchUtils::WriteSignature(output.fd);
+ std::string contents("FASTDEPLOY");
+ EXPECT_TRUE(FileMatchesContent(output.fd, contents.c_str(), contents.size()));
+}
+
+TEST(PatchUtilsTest, GatherMetadata) {
+ std::string apkFile = GetTestFile("rotating_cube-release.apk");
+ APKMetaData metadata = PatchUtils::GetAPKMetaData(apkFile.c_str());
+ std::string expectedMetadata;
+ android::base::ReadFileToString(GetTestFile("rotating_cube-metadata-release.data"),
+ &expectedMetadata);
+ std::string actualMetadata;
+ metadata.SerializeToString(&actualMetadata);
+ EXPECT_EQ(expectedMetadata, actualMetadata);
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java b/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
deleted file mode 100644
index 24b2eab..0000000
--- a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.fastdeploy;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.StringBuilder;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.ArrayList;
-
-import java.nio.charset.StandardCharsets;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.AbstractMap.SimpleEntry;
-
-import com.android.fastdeploy.APKMetaData;
-import com.android.fastdeploy.APKEntry;
-
-public final class DeployPatchGenerator {
- private static final int BUFFER_SIZE = 128 * 1024;
-
- public static void main(String[] args) {
- try {
- if (args.length < 2) {
- showUsage(0);
- }
-
- boolean verbose = false;
- if (args.length > 2) {
- String verboseFlag = args[2];
- if (verboseFlag.compareTo("--verbose") == 0) {
- verbose = true;
- }
- }
-
- StringBuilder sb = null;
- String apkPath = args[0];
- String deviceMetadataPath = args[1];
- File hostFile = new File(apkPath);
-
- List<APKEntry> deviceZipEntries = getMetadataFromFile(deviceMetadataPath);
- System.err.println("Device Entries (" + deviceZipEntries.size() + ")");
- if (verbose) {
- sb = new StringBuilder();
- for (APKEntry entry : deviceZipEntries) {
- APKEntryToString(entry, sb);
- }
- System.err.println(sb.toString());
- }
-
- List<APKEntry> hostFileEntries = PatchUtils.getAPKMetaData(hostFile).getEntriesList();
- System.err.println("Host Entries (" + hostFileEntries.size() + ")");
- if (verbose) {
- sb = new StringBuilder();
- for (APKEntry entry : hostFileEntries) {
- APKEntryToString(entry, sb);
- }
- System.err.println(sb.toString());
- }
-
- List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet =
- getIdenticalContents(deviceZipEntries, hostFileEntries);
- reportIdenticalContents(identicalContentsEntrySet, hostFile);
-
- if (verbose) {
- sb = new StringBuilder();
- for (SimpleEntry<APKEntry, APKEntry> identicalEntry : identicalContentsEntrySet) {
- APKEntry entry = identicalEntry.getValue();
- APKEntryToString(entry, sb);
- }
- System.err.println("Identical Entries (" + identicalContentsEntrySet.size() + ")");
- System.err.println(sb.toString());
- }
-
- createPatch(identicalContentsEntrySet, hostFile, System.out);
- } catch (Exception e) {
- System.err.println("Error: " + e);
- e.printStackTrace();
- System.exit(2);
- }
- System.exit(0);
- }
-
- private static void showUsage(int exitCode) {
- System.err.println("usage: deploypatchgenerator <apkpath> <deviceapkmetadata> [--verbose]");
- System.err.println("");
- System.exit(exitCode);
- }
-
- private static void APKEntryToString(APKEntry entry, StringBuilder outputString) {
- outputString.append(String.format("Filename: %s\n", entry.getFileName()));
- outputString.append(String.format("CRC32: 0x%08X\n", entry.getCrc32()));
- outputString.append(String.format("Data Offset: %d\n", entry.getDataOffset()));
- outputString.append(String.format("Compressed Size: %d\n", entry.getCompressedSize()));
- outputString.append(String.format("Uncompressed Size: %d\n", entry.getUncompressedSize()));
- }
-
- private static List<APKEntry> getMetadataFromFile(String deviceMetadataPath) throws IOException {
- InputStream is = new FileInputStream(new File(deviceMetadataPath));
- APKMetaData apkMetaData = APKMetaData.parseDelimitedFrom(is);
- return apkMetaData.getEntriesList();
- }
-
- private static List<SimpleEntry<APKEntry, APKEntry>> getIdenticalContents(
- List<APKEntry> deviceZipEntries, List<APKEntry> hostZipEntries) throws IOException {
- List<SimpleEntry<APKEntry, APKEntry>> identicalContents =
- new ArrayList<SimpleEntry<APKEntry, APKEntry>>();
-
- for (APKEntry deviceZipEntry : deviceZipEntries) {
- for (APKEntry hostZipEntry : hostZipEntries) {
- if (deviceZipEntry.getCrc32() == hostZipEntry.getCrc32() &&
- deviceZipEntry.getFileName().equals(hostZipEntry.getFileName())) {
- identicalContents.add(new SimpleEntry(deviceZipEntry, hostZipEntry));
- }
- }
- }
-
- Collections.sort(identicalContents, new Comparator<SimpleEntry<APKEntry, APKEntry>>() {
- @Override
- public int compare(
- SimpleEntry<APKEntry, APKEntry> p1, SimpleEntry<APKEntry, APKEntry> p2) {
- return Long.compare(p1.getValue().getDataOffset(), p2.getValue().getDataOffset());
- }
- });
-
- return identicalContents;
- }
-
- private static void reportIdenticalContents(
- List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet, File hostFile)
- throws IOException {
- long totalEqualBytes = 0;
- int totalEqualFiles = 0;
- for (SimpleEntry<APKEntry, APKEntry> entries : identicalContentsEntrySet) {
- APKEntry hostAPKEntry = entries.getValue();
- totalEqualBytes += hostAPKEntry.getCompressedSize();
- totalEqualFiles++;
- }
-
- float savingPercent = (float) (totalEqualBytes * 100) / hostFile.length();
-
- System.err.println("Detected " + totalEqualFiles + " equal APK entries");
- System.err.println(totalEqualBytes + " bytes are equal out of " + hostFile.length() + " ("
- + savingPercent + "%)");
- }
-
- static void createPatch(List<SimpleEntry<APKEntry, APKEntry>> zipEntrySimpleEntrys,
- File hostFile, OutputStream patchStream) throws IOException, PatchFormatException {
- FileInputStream hostFileInputStream = new FileInputStream(hostFile);
-
- patchStream.write(PatchUtils.SIGNATURE.getBytes(StandardCharsets.US_ASCII));
- PatchUtils.writeFormattedLong(hostFile.length(), patchStream);
-
- byte[] buffer = new byte[BUFFER_SIZE];
- long totalBytesWritten = 0;
- Iterator<SimpleEntry<APKEntry, APKEntry>> entrySimpleEntryIterator =
- zipEntrySimpleEntrys.iterator();
- while (entrySimpleEntryIterator.hasNext()) {
- SimpleEntry<APKEntry, APKEntry> entrySimpleEntry = entrySimpleEntryIterator.next();
- APKEntry deviceAPKEntry = entrySimpleEntry.getKey();
- APKEntry hostAPKEntry = entrySimpleEntry.getValue();
-
- long newDataLen = hostAPKEntry.getDataOffset() - totalBytesWritten;
- long oldDataOffset = deviceAPKEntry.getDataOffset();
- long oldDataLen = deviceAPKEntry.getCompressedSize();
-
- PatchUtils.writeFormattedLong(newDataLen, patchStream);
- PatchUtils.pipe(hostFileInputStream, patchStream, buffer, newDataLen);
- PatchUtils.writeFormattedLong(oldDataOffset, patchStream);
- PatchUtils.writeFormattedLong(oldDataLen, patchStream);
-
- long skip = hostFileInputStream.skip(oldDataLen);
- if (skip != oldDataLen) {
- throw new PatchFormatException("skip error: attempted to skip " + oldDataLen
- + " bytes but return code was " + skip);
- }
- totalBytesWritten += oldDataLen + newDataLen;
- }
- long remainderLen = hostFile.length() - totalBytesWritten;
- PatchUtils.writeFormattedLong(remainderLen, patchStream);
- PatchUtils.pipe(hostFileInputStream, patchStream, buffer, remainderLen);
- PatchUtils.writeFormattedLong(0, patchStream);
- PatchUtils.writeFormattedLong(0, patchStream);
- patchStream.flush();
- }
-}
diff --git a/adb/fastdeploy/testdata/rotating_cube-metadata-release.data b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
new file mode 100644
index 0000000..0671bf3
--- /dev/null
+++ b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
@@ -0,0 +1,6 @@
+
+#ÇÏ«META-INF/MANIFEST.MFÇ Q(W
+#AndroidManifest.xml1 ä(è
+6¦µ>#lib/armeabi-v7a/libvulkan_sample.so ÀÒQ(²ì
+ ãresources.arscôàQ ´(´
+ÂÉclasses.dexÁ ÿ(ô
diff --git a/adb/fastdeploy/testdata/rotating_cube-release.apk b/adb/fastdeploy/testdata/rotating_cube-release.apk
new file mode 100644
index 0000000..d47e0ea
--- /dev/null
+++ b/adb/fastdeploy/testdata/rotating_cube-release.apk
Binary files differ
diff --git a/adb/fdevent/fdevent.h b/adb/fdevent/fdevent.h
index ccb0c92..2424252 100644
--- a/adb/fdevent/fdevent.h
+++ b/adb/fdevent/fdevent.h
@@ -20,6 +20,7 @@
#include <stddef.h>
#include <stdint.h>
+#include <atomic>
#include <chrono>
#include <deque>
#include <functional>
diff --git a/adb/socket_spec.cpp b/adb/socket_spec.cpp
index 1333724..98468b5 100644
--- a/adb/socket_spec.cpp
+++ b/adb/socket_spec.cpp
@@ -93,7 +93,7 @@
}
} else {
std::string addr(spec.substr(4));
- port_value = -1;
+ port_value = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
// FIXME: ParseNetAddress rejects port 0. This currently doesn't hurt, because listening
// on an address that isn't 'localhost' is unsupported.
diff --git a/adb/socket_spec_test.cpp b/adb/socket_spec_test.cpp
index f5ec0f1..3a2f60c 100644
--- a/adb/socket_spec_test.cpp
+++ b/adb/socket_spec_test.cpp
@@ -20,38 +20,71 @@
#include <gtest/gtest.h>
-TEST(socket_spec, parse_tcp_socket_spec) {
+TEST(socket_spec, parse_tcp_socket_spec_just_port) {
std::string hostname, error, serial;
int port;
EXPECT_TRUE(parse_tcp_socket_spec("tcp:5037", &hostname, &port, &serial, &error));
EXPECT_EQ("", hostname);
EXPECT_EQ(5037, port);
EXPECT_EQ("", serial);
+}
- // Bad ports:
+TEST(socket_spec, parse_tcp_socket_spec_bad_ports) {
+ std::string hostname, error, serial;
+ int port;
EXPECT_FALSE(parse_tcp_socket_spec("tcp:", &hostname, &port, &serial, &error));
EXPECT_FALSE(parse_tcp_socket_spec("tcp:-1", &hostname, &port, &serial, &error));
EXPECT_FALSE(parse_tcp_socket_spec("tcp:65536", &hostname, &port, &serial, &error));
+}
+TEST(socket_spec, parse_tcp_socket_spec_host_and_port) {
+ std::string hostname, error, serial;
+ int port;
EXPECT_TRUE(parse_tcp_socket_spec("tcp:localhost:1234", &hostname, &port, &serial, &error));
EXPECT_EQ("localhost", hostname);
EXPECT_EQ(1234, port);
EXPECT_EQ("localhost:1234", serial);
+}
- EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost", &hostname, &port, &serial, &error));
+TEST(socket_spec, parse_tcp_socket_spec_host_no_port) {
+ std::string hostname, error, serial;
+ int port;
+ EXPECT_TRUE(parse_tcp_socket_spec("tcp:localhost", &hostname, &port, &serial, &error));
+ EXPECT_EQ("localhost", hostname);
+ EXPECT_EQ(5555, port);
+ EXPECT_EQ("localhost:5555", serial);
+}
+
+TEST(socket_spec, parse_tcp_socket_spec_host_bad_ports) {
+ std::string hostname, error, serial;
+ int port;
EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost:", &hostname, &port, &serial, &error));
EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost:-1", &hostname, &port, &serial, &error));
EXPECT_FALSE(parse_tcp_socket_spec("tcp:localhost:65536", &hostname, &port, &serial, &error));
+}
- // IPv6:
+TEST(socket_spec, parse_tcp_socket_spec_ipv6_and_port) {
+ std::string hostname, error, serial;
+ int port;
EXPECT_TRUE(parse_tcp_socket_spec("tcp:[::1]:1234", &hostname, &port, &serial, &error));
EXPECT_EQ("::1", hostname);
EXPECT_EQ(1234, port);
EXPECT_EQ("[::1]:1234", serial);
+}
+TEST(socket_spec, parse_tcp_socket_spec_ipv6_no_port) {
+ std::string hostname, error, serial;
+ int port;
+ EXPECT_TRUE(parse_tcp_socket_spec("tcp:::1", &hostname, &port, &serial, &error));
+ EXPECT_EQ("::1", hostname);
+ EXPECT_EQ(5555, port);
+ EXPECT_EQ("[::1]:5555", serial);
+}
+
+TEST(socket_spec, parse_tcp_socket_spec_ipv6_bad_ports) {
+ std::string hostname, error, serial;
+ int port;
EXPECT_FALSE(parse_tcp_socket_spec("tcp:[::1]", &hostname, &port, &serial, &error));
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));
- EXPECT_FALSE(parse_tcp_socket_spec("tcp:::1", &hostname, &port, &serial, &error));
- EXPECT_FALSE(parse_tcp_socket_spec("tcp:::1:1234", &hostname, &port, &serial, &error));
}
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index 6372b3d..dc2525c 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -688,7 +688,7 @@
android::base::SystemErrorCodeToString(err).c_str());
}
_socket_set_errno(err);
- result = -1;
+ return -1;
}
CHECK_GE(static_cast<DWORD>(std::numeric_limits<int>::max()), bytes_written);
return static_cast<int>(bytes_written);
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 8bc925f..3d1d620 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -542,9 +542,7 @@
// for the first time, even if no update occurred.
if (tracker->update_needed) {
tracker->update_needed = false;
-
- std::string transports = list_transports(tracker->long_output);
- device_tracker_send(tracker, transports);
+ device_tracker_send(tracker, list_transports(tracker->long_output));
}
}
@@ -587,13 +585,11 @@
update_transport_status();
// Notify `adb track-devices` clients.
- std::string transports = list_transports(false);
-
device_tracker* tracker = device_tracker_list;
while (tracker != nullptr) {
device_tracker* next = tracker->next;
// This may destroy the tracker if the connection is closed.
- device_tracker_send(tracker, transports);
+ device_tracker_send(tracker, list_transports(tracker->long_output));
tracker = next;
}
}
diff --git a/adb/types.h b/adb/types.h
index cd1366d..8bd66be 100644
--- a/adb/types.h
+++ b/adb/types.h
@@ -16,6 +16,8 @@
#pragma once
+#include <string.h>
+
#include <algorithm>
#include <deque>
#include <memory>
diff --git a/base/errors_unix.cpp b/base/errors_unix.cpp
index 296995e..48269b6 100644
--- a/base/errors_unix.cpp
+++ b/base/errors_unix.cpp
@@ -17,6 +17,7 @@
#include "android-base/errors.h"
#include <errno.h>
+#include <string.h>
namespace android {
namespace base {
diff --git a/base/include/android-base/endian.h b/base/include/android-base/endian.h
index cbbd8c9..10efaa3 100644
--- a/base/include/android-base/endian.h
+++ b/base/include/android-base/endian.h
@@ -41,23 +41,28 @@
#else
-/* Mac OS and Windows have nothing. */
-
-#define __LITTLE_ENDIAN 1234
+#if defined(__APPLE__)
+/* macOS has some of the basics. */
+#include <sys/_endian.h>
+#else
+/* Windows really has nothing. */
#define LITTLE_ENDIAN __LITTLE_ENDIAN
-
-#define __BIG_ENDIAN 4321
#define BIG_ENDIAN __BIG_ENDIAN
-
-#define __BYTE_ORDER __LITTLE_ENDIAN
#define BYTE_ORDER __BYTE_ORDER
-
#define htons(x) __builtin_bswap16(x)
#define htonl(x) __builtin_bswap32(x)
-#define htonq(x) __builtin_bswap64(x)
-
#define ntohs(x) __builtin_bswap16(x)
#define ntohl(x) __builtin_bswap32(x)
+#endif
+
+/* Neither macOS nor Windows have the rest. */
+
+#define __LITTLE_ENDIAN 1234
+#define __BIG_ENDIAN 4321
+#define __BYTE_ORDER __LITTLE_ENDIAN
+
+#define htonq(x) __builtin_bswap64(x)
+
#define ntohq(x) __builtin_bswap64(x)
#define htobe16(x) __builtin_bswap16(x)
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 9a0f4fe..bc197cd 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -46,7 +46,7 @@
namespace fs_mgr {
namespace {
-const std::string kDefaultAndroidDtDir("/proc/device-tree/firmware/android");
+constexpr char kDefaultAndroidDtDir[] = "/proc/device-tree/firmware/android";
struct FlagList {
const char *name;
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index ac15ce4..4ee7db9 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -150,6 +150,31 @@
return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100));
}
+const auto kPhysicalDevice = "/dev/block/by-name/"s;
+
+bool fs_mgr_update_blk_device(FstabEntry* entry) {
+ if (entry->fs_mgr_flags.logical) {
+ fs_mgr_update_logical_partition(entry);
+ }
+ if (fs_mgr_access(entry->blk_device)) {
+ return true;
+ }
+ if (entry->blk_device != "/dev/root") {
+ return false;
+ }
+
+ // special case for system-as-root (taimen and others)
+ auto blk_device = kPhysicalDevice + "system";
+ if (!fs_mgr_access(blk_device)) {
+ blk_device += fs_mgr_get_slot_suffix();
+ if (!fs_mgr_access(blk_device)) {
+ return false;
+ }
+ }
+ entry->blk_device = blk_device;
+ return true;
+}
+
bool fs_mgr_overlayfs_enabled(FstabEntry* entry) {
// readonly filesystem, can not be mount -o remount,rw
// for squashfs, erofs or if free space is (near) zero making such a remount
@@ -157,19 +182,19 @@
if (!fs_mgr_filesystem_has_space(entry->mount_point)) {
return true;
}
- if (entry->fs_mgr_flags.logical) {
- fs_mgr_update_logical_partition(entry);
+
+ // blk_device needs to be setup so we can check superblock.
+ // If we fail here, because during init first stage and have doubts.
+ if (!fs_mgr_update_blk_device(entry)) {
+ return true;
}
+
+ // check if ext4 de-dupe
auto save_errno = errno;
- errno = 0;
auto has_shared_blocks = fs_mgr_has_shared_blocks(entry->mount_point, entry->blk_device);
if (!has_shared_blocks && (entry->mount_point == "/system")) {
has_shared_blocks = fs_mgr_has_shared_blocks("/", entry->blk_device);
}
- // special case for first stage init for system as root (taimen)
- if (!has_shared_blocks && (errno == ENOENT) && (entry->blk_device == "/dev/root")) {
- has_shared_blocks = true;
- }
errno = save_errno;
return has_shared_blocks;
}
@@ -388,8 +413,6 @@
return SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
}
-const auto kPhysicalDevice = "/dev/block/by-name/"s;
-
std::string fs_mgr_overlayfs_super_device(uint32_t slot_number) {
return kPhysicalDevice + fs_mgr_get_super_partition_name(slot_number);
}
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 0ad8d9d..a4e0d76 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -150,6 +150,15 @@
return true;
}
+std::optional<DeviceMapper::Info> DeviceMapper::GetDetailedInfo(const std::string& name) const {
+ struct dm_ioctl io;
+ InitIo(&io, name);
+ if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) {
+ return std::nullopt;
+ }
+ return Info(io.flags);
+}
+
DmDeviceState DeviceMapper::GetState(const std::string& name) const {
struct dm_ioctl io;
InitIo(&io, name);
@@ -162,6 +171,24 @@
return DmDeviceState::SUSPENDED;
}
+bool DeviceMapper::ChangeState(const std::string& name, DmDeviceState state) {
+ if (state != DmDeviceState::SUSPENDED && state != DmDeviceState::ACTIVE) {
+ return false;
+ }
+
+ struct dm_ioctl io;
+ InitIo(&io, name);
+
+ if (state == DmDeviceState::SUSPENDED) io.flags = DM_SUSPEND_FLAG;
+
+ if (ioctl(fd_, DM_DEV_SUSPEND, &io) < 0) {
+ PLOG(ERROR) << "DM_DEV_SUSPEND "
+ << (state == DmDeviceState::SUSPENDED ? "suspend" : "resume") << " failed";
+ return false;
+ }
+ return true;
+}
+
bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table) {
std::string ignore_path;
if (!CreateDevice(name, table, &ignore_path, 0ms)) {
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index da1013e..7c9804c 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -16,6 +16,7 @@
#include "libdm/dm_target.h"
+#include <inttypes.h>
#include <stdio.h>
#include <sys/types.h>
@@ -149,6 +150,25 @@
return base_device_ + " " + cow_device_ + " " + mode + " " + std::to_string(chunk_size_);
}
+// Computes the percentage of complition for snapshot status.
+// @sectors_initial is the number of sectors_allocated stored right before
+// starting the merge.
+double DmTargetSnapshot::MergePercent(const DmTargetSnapshot::Status& status,
+ uint64_t sectors_initial) {
+ uint64_t s = status.sectors_allocated;
+ uint64_t t = status.total_sectors;
+ uint64_t m = status.metadata_sectors;
+ uint64_t i = sectors_initial == 0 ? t : sectors_initial;
+
+ if (t <= s || i <= s) {
+ return 0.0;
+ }
+ if (s == 0 || t == 0 || s <= m) {
+ return 100.0;
+ }
+ return 100.0 / (i - m) * (i - s);
+}
+
bool DmTargetSnapshot::ReportsOverflow(const std::string& target_type) {
DeviceMapper& dm = DeviceMapper::Instance();
DmTargetTypeInfo info;
@@ -165,34 +185,40 @@
}
bool DmTargetSnapshot::ParseStatusText(const std::string& text, Status* status) {
+ // Try to parse the line as it should be
+ int args = sscanf(text.c_str(), "%" PRIu64 "/%" PRIu64 " %" PRIu64, &status->sectors_allocated,
+ &status->total_sectors, &status->metadata_sectors);
+ if (args == 3) {
+ return true;
+ }
auto sections = android::base::Split(text, " ");
+ if (sections.size() == 0) {
+ LOG(ERROR) << "could not parse empty status";
+ return false;
+ }
+ // Error codes are: "Invalid", "Overflow" and "Merge failed"
if (sections.size() == 1) {
- // This is probably an error code, "Invalid" is possible as is "Overflow"
- // on 4.4+.
+ if (text == "Invalid" || text == "Overflow") {
+ status->error = text;
+ return true;
+ }
+ } else if (sections.size() == 2 && text == "Merge failed") {
status->error = text;
return true;
}
- if (sections.size() != 2) {
- LOG(ERROR) << "snapshot status should have two components";
+ LOG(ERROR) << "could not parse snapshot status: wrong format";
+ return false;
+}
+
+bool DmTargetSnapshot::GetDevicesFromParams(const std::string& params, std::string* base_device,
+ std::string* cow_device) {
+ auto pieces = android::base::Split(params, " ");
+ if (pieces.size() < 2) {
+ LOG(ERROR) << "Parameter string is invalid: " << params;
return false;
}
- auto sector_info = android::base::Split(sections[0], "/");
- if (sector_info.size() != 2) {
- LOG(ERROR) << "snapshot sector info should have two components";
- return false;
- }
- if (!android::base::ParseUint(sections[1], &status->metadata_sectors)) {
- LOG(ERROR) << "could not parse metadata sectors";
- return false;
- }
- if (!android::base::ParseUint(sector_info[0], &status->sectors_allocated)) {
- LOG(ERROR) << "could not parse sectors allocated";
- return false;
- }
- if (!android::base::ParseUint(sector_info[1], &status->total_sectors)) {
- LOG(ERROR) << "could not parse total sectors";
- return false;
- }
+ *base_device = pieces[0];
+ *cow_device = pieces[1];
return true;
}
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index b28a8f2..eed21dc 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -166,6 +166,34 @@
ASSERT_TRUE(dev.Destroy());
}
+TEST(libdm, DmSuspendResume) {
+ unique_fd tmp1(CreateTempFile("file_suspend_resume", 512));
+ ASSERT_GE(tmp1, 0);
+
+ LoopDevice loop_a(tmp1, 10s);
+ ASSERT_TRUE(loop_a.valid());
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop_a.device(), 0));
+ ASSERT_TRUE(table.valid());
+
+ TempDevice dev("libdm-test-dm-suspend-resume", table);
+ ASSERT_TRUE(dev.valid());
+ ASSERT_FALSE(dev.path().empty());
+
+ auto& dm = DeviceMapper::Instance();
+
+ // Test Set and Get status of device.
+ vector<DeviceMapper::TargetInfo> targets;
+ ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
+
+ ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::SUSPENDED));
+ ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::SUSPENDED);
+
+ ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::ACTIVE));
+ ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
+}
+
TEST(libdm, DmVerityArgsAvb2) {
std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a";
std::string algorithm = "sha1";
@@ -428,6 +456,87 @@
}
}
+TEST(libdm, ParseStatusText) {
+ DmTargetSnapshot::Status status;
+
+ // Bad inputs
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("X", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123/456", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456 789", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456/789", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123/456/789", &status));
+ EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 / 456 789", &status));
+
+ // Good input
+ EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("123/456 789", &status));
+ EXPECT_EQ(status.sectors_allocated, 123);
+ EXPECT_EQ(status.total_sectors, 456);
+ EXPECT_EQ(status.metadata_sectors, 789);
+
+ // Known error codes
+ EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Invalid", &status));
+ EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Merge failed", &status));
+ EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Overflow", &status));
+}
+
+TEST(libdm, DmSnapshotMergePercent) {
+ DmTargetSnapshot::Status status;
+
+ // Correct input
+ status.sectors_allocated = 1000;
+ status.total_sectors = 1000;
+ status.metadata_sectors = 0;
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status), 1.0);
+
+ status.sectors_allocated = 500;
+ status.total_sectors = 1000;
+ status.metadata_sectors = 0;
+ EXPECT_GE(DmTargetSnapshot::MergePercent(status), 49.0);
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status), 51.0);
+
+ status.sectors_allocated = 0;
+ status.total_sectors = 1000;
+ status.metadata_sectors = 0;
+ EXPECT_GE(DmTargetSnapshot::MergePercent(status), 99.0);
+
+ status.sectors_allocated = 500;
+ status.total_sectors = 1000;
+ status.metadata_sectors = 500;
+ EXPECT_GE(DmTargetSnapshot::MergePercent(status), 99.0);
+
+ status.sectors_allocated = 500;
+ status.total_sectors = 1000;
+ status.metadata_sectors = 0;
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status, 500), 1.0);
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status, 1000), 51.0);
+ EXPECT_GE(DmTargetSnapshot::MergePercent(status, 1000), 49.0);
+
+ // Robustness
+ status.sectors_allocated = 2000;
+ status.total_sectors = 1000;
+ status.metadata_sectors = 0;
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0);
+
+ status.sectors_allocated = 2000;
+ status.total_sectors = 1000;
+ status.metadata_sectors = 2000;
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0);
+
+ status.sectors_allocated = 2000;
+ status.total_sectors = 0;
+ status.metadata_sectors = 2000;
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0);
+
+ status.sectors_allocated = 1000;
+ status.total_sectors = 0;
+ status.metadata_sectors = 1000;
+ EXPECT_LE(DmTargetSnapshot::MergePercent(status, 0), 0.0);
+}
+
TEST(libdm, CryptArgs) {
DmTargetCrypt target1(0, 512, "sha1", "abcdefgh", 50, "/dev/loop0", 100);
ASSERT_EQ(target1.name(), "crypt");
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 9c0c2f3..cf306f3 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -27,6 +27,7 @@
#include <chrono>
#include <memory>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
@@ -46,6 +47,8 @@
enum class DmDeviceState { INVALID, SUSPENDED, ACTIVE };
+static constexpr uint64_t kSectorSize = 512;
+
class DeviceMapper final {
public:
class DmBlockDevice final {
@@ -70,15 +73,37 @@
uint64_t dev_;
};
+ class Info {
+ uint32_t flags_;
+
+ public:
+ explicit Info(uint32_t flags) : flags_(flags) {}
+
+ bool IsActiveTablePresent() const { return flags_ & DM_ACTIVE_PRESENT_FLAG; }
+ bool IsBufferFull() const { return flags_ & DM_BUFFER_FULL_FLAG; }
+ bool IsInactiveTablePresent() const { return flags_ & DM_INACTIVE_PRESENT_FLAG; }
+ bool IsReadOnly() const { return flags_ & DM_READONLY_FLAG; }
+ bool IsSuspended() const { return flags_ & DM_SUSPEND_FLAG; }
+ };
+
// Removes a device mapper device with the given name.
// Returns 'true' on success, false otherwise.
bool DeleteDevice(const std::string& name);
+ // Fetches and returns the complete state of the underlying device mapper
+ // device with given name.
+ std::optional<Info> GetDetailedInfo(const std::string& name) const;
+
// Returns the current state of the underlying device mapper device
// with given name.
// One of INVALID, SUSPENDED or ACTIVE.
DmDeviceState GetState(const std::string& name) const;
+ // Puts the given device to the specified status, which must be either:
+ // - SUSPENDED: suspend the device, or
+ // - ACTIVE: resumes the device.
+ bool ChangeState(const std::string& name, DmDeviceState state);
+
// Creates a device, loads the given table, and activates it. If the device
// is not able to be activated, it is destroyed, and false is returned.
// After creation, |path| contains the result of calling
@@ -172,6 +197,7 @@
struct TargetInfo {
struct dm_target_spec spec;
std::string data;
+ TargetInfo() {}
TargetInfo(const struct dm_target_spec& spec, const std::string& data)
: spec(spec), data(data) {}
};
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index 722922d..a66ab7a 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -216,8 +216,11 @@
std::string error;
};
+ static double MergePercent(const Status& status, uint64_t sectors_initial = 0);
static bool ParseStatusText(const std::string& text, Status* status);
static bool ReportsOverflow(const std::string& target_type);
+ static bool GetDevicesFromParams(const std::string& params, std::string* base_device,
+ std::string* cow_device);
private:
std::string base_device_;
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index b504161..5b377ae 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -36,6 +36,7 @@
"builder.cpp",
"images.cpp",
"partition_opener.cpp",
+ "property_fetcher.cpp",
"reader.cpp",
"utility.cpp",
"writer.cpp",
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index c12e3b2..777743c 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -20,21 +20,16 @@
#include <algorithm>
-#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include "liblp/liblp.h"
+#include "liblp/property_fetcher.h"
#include "reader.h"
#include "utility.h"
namespace android {
namespace fs_mgr {
-std::optional<bool> MetadataBuilder::sABOverride;
-std::optional<bool> MetadataBuilder::sRetrofitDap;
-
-static const std::string kDefaultGroup = "default";
-
bool LinearExtent::AddTo(LpMetadata* out) const {
if (device_index_ >= out->block_devices.size()) {
LERROR << "Extent references unknown block device.";
@@ -164,16 +159,27 @@
return nullptr;
}
- // On non-retrofit devices there is only one location for metadata: the
- // super partition. update_engine will remove and resize partitions as
- // needed. On the other hand, for retrofit devices, we'll need to
- // translate block device and group names to update their slot suffixes.
+ // On retrofit DAP devices, modify the metadata so that it is suitable for being written
+ // to the target slot later. We detect retrofit DAP devices by checking the super partition
+ // name and system properties.
+ // See comments for UpdateMetadataForOtherSuper.
auto super_device = GetMetadataSuperBlockDevice(*metadata.get());
- if (GetBlockDevicePartitionName(*super_device) == "super" ||
- !IsRetrofitDynamicPartitionsDevice()) {
- return New(*metadata.get(), &opener);
+ if (GetBlockDevicePartitionName(*super_device) != "super" &&
+ IsRetrofitDynamicPartitionsDevice()) {
+ if (!UpdateMetadataForOtherSuper(metadata.get(), source_slot_number, target_slot_number)) {
+ return nullptr;
+ }
}
+ return New(*metadata.get(), &opener);
+}
+
+// For retrofit DAP devices, there are (conceptually) two super partitions. We'll need to translate
+// block device and group names to update their slot suffixes.
+// (On the other hand, On non-retrofit DAP devices there is only one location for metadata: the
+// super partition. update_engine will remove and resize partitions as needed.)
+bool MetadataBuilder::UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number,
+ uint32_t target_slot_number) {
// Clear partitions and extents, since they have no meaning on the target
// slot. We also clear groups since they are re-added during OTA.
metadata->partitions.clear();
@@ -193,7 +199,7 @@
// refers to a target or unknown block device.
LERROR << "Invalid block device for slot " << source_slot_suffix << ": "
<< partition_name;
- return nullptr;
+ return false;
}
std::string new_name =
partition_name.substr(0, partition_name.size() - slot_suffix.size()) +
@@ -202,20 +208,12 @@
auto new_device = source_block_device;
if (!UpdateBlockDevicePartitionName(&new_device, new_name)) {
LERROR << "Partition name too long: " << new_name;
- return nullptr;
+ return false;
}
metadata->block_devices.emplace_back(new_device);
}
- return New(*metadata.get(), &opener);
-}
-
-void MetadataBuilder::OverrideABForTesting(bool ab_device) {
- sABOverride = ab_device;
-}
-
-void MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(bool retrofit) {
- sRetrofitDap = retrofit;
+ return true;
}
MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false) {
@@ -414,7 +412,7 @@
geometry_.metadata_slot_count = metadata_slot_count;
geometry_.logical_block_size = logical_block_size;
- if (!AddGroup(kDefaultGroup, 0)) {
+ if (!AddGroup(std::string(kDefaultGroup), 0)) {
return false;
}
return true;
@@ -430,7 +428,7 @@
}
Partition* MetadataBuilder::AddPartition(const std::string& name, uint32_t attributes) {
- return AddPartition(name, kDefaultGroup, attributes);
+ return AddPartition(name, std::string(kDefaultGroup), attributes);
}
Partition* MetadataBuilder::AddPartition(const std::string& name, const std::string& group_name,
@@ -1050,17 +1048,12 @@
}
bool MetadataBuilder::IsABDevice() {
- if (sABOverride.has_value()) {
- return *sABOverride;
- }
- return !android::base::GetProperty("ro.boot.slot_suffix", "").empty();
+ return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty();
}
bool MetadataBuilder::IsRetrofitDynamicPartitionsDevice() {
- if (sRetrofitDap.has_value()) {
- return *sRetrofitDap;
- }
- return !android::base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false);
+ return IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit",
+ false);
}
bool MetadataBuilder::IsRetrofitMetadata() const {
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 377ec68..6d27873 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -19,36 +19,40 @@
#include <gtest/gtest.h>
#include <liblp/builder.h>
+#include "mock_property_fetcher.h"
#include "utility.h"
using namespace std;
using namespace android::fs_mgr;
+using ::android::fs_mgr::MockPropertyFetcher;
+using ::testing::_;
+using ::testing::AnyNumber;
using ::testing::ElementsAre;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+static void ResetPropertyFetcher() {
+ IPropertyFetcher::OverrideForTesting(std::make_unique<NiceMock<MockPropertyFetcher>>());
+}
+
+MockPropertyFetcher* GetMockedInstance() {
+ return static_cast<MockPropertyFetcher*>(IPropertyFetcher::GetInstance());
+}
class Environment : public ::testing::Environment {
public:
- void SetUp() override {
- MetadataBuilder::OverrideABForTesting(false);
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
- }
+ void SetUp() override { ResetPropertyFetcher(); }
};
int main(int argc, char** argv) {
- ::testing::AddGlobalTestEnvironment(new Environment);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
class BuilderTest : public ::testing::Test {
public:
- void SetUp() override {
- MetadataBuilder::OverrideABForTesting(false);
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
- }
- void TearDown() override {
- MetadataBuilder::OverrideABForTesting(false);
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
- }
+ void SetUp() override { ResetPropertyFetcher(); }
+ void TearDown() override { ResetPropertyFetcher(); }
};
TEST_F(BuilderTest, BuildBasic) {
@@ -785,7 +789,9 @@
// A and B slots should be allocated from separate halves of the partition,
// to mitigate allocating too many extents. (b/120433288)
- MetadataBuilder::OverrideABForTesting(true);
+ ON_CALL(*GetMockedInstance(), GetProperty("ro.boot.slot_suffix", _))
+ .WillByDefault(Return("_a"));
+
auto builder = MetadataBuilder::New(device_info, 65536, 2);
ASSERT_NE(builder, nullptr);
Partition* system_a = builder->AddPartition("system_a", 0);
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index a2221ef..3b229bd 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -24,6 +24,7 @@
#include <memory>
#include <optional>
#include <set>
+#include <string_view>
#include "liblp.h"
#include "partition_opener.h"
@@ -37,6 +38,9 @@
static const uint32_t kDefaultPartitionAlignment = 1024 * 1024;
static const uint32_t kDefaultBlockSize = 4096;
+// Name of the default group in a metadata.
+static constexpr std::string_view kDefaultGroup = "default";
+
// Abstraction around dm-targets that can be encoded into logical partition tables.
class Extent {
public:
@@ -196,12 +200,6 @@
return New(device_info, metadata_max_size, metadata_slot_count);
}
- // Used by the test harness to override whether the device is "A/B".
- static void OverrideABForTesting(bool ab_device);
-
- // Used by the test harness to override whether the device is "retrofitting dynamic partitions".
- static void OverrideRetrofitDynamicParititonsForTesting(bool retrofit);
-
// Define a new partition group. By default there is one group called
// "default", with an unrestricted size. A non-zero size will restrict the
// total space used by all partitions in the group.
@@ -347,8 +345,8 @@
const std::vector<Interval>& free_list,
uint64_t sectors_needed) const;
- static std::optional<bool> sABOverride;
- static std::optional<bool> sRetrofitDap;
+ static bool UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number,
+ uint32_t target_slot_number);
LpMetadataGeometry geometry_;
LpMetadataHeader header_;
diff --git a/fs_mgr/liblp/include/liblp/property_fetcher.h b/fs_mgr/liblp/include/liblp/property_fetcher.h
new file mode 100644
index 0000000..e73a1f5
--- /dev/null
+++ b/fs_mgr/liblp/include/liblp/property_fetcher.h
@@ -0,0 +1,42 @@
+//
+// 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 <memory>
+
+namespace android {
+namespace fs_mgr {
+
+class IPropertyFetcher {
+ public:
+ virtual ~IPropertyFetcher() = default;
+ virtual std::string GetProperty(const std::string& key, const std::string& defaultValue) = 0;
+ virtual bool GetBoolProperty(const std::string& key, bool defaultValue) = 0;
+
+ static IPropertyFetcher* GetInstance();
+ static void OverrideForTesting(std::unique_ptr<IPropertyFetcher>&&);
+};
+
+class PropertyFetcher : public IPropertyFetcher {
+ public:
+ ~PropertyFetcher() = default;
+ std::string GetProperty(const std::string& key, const std::string& defaultValue) override;
+ bool GetBoolProperty(const std::string& key, bool defaultValue) override;
+};
+
+} // namespace fs_mgr
+} // namespace android
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 70dd85f..2990863 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -23,10 +23,12 @@
#include <android-base/unique_fd.h>
#include <fs_mgr.h>
#include <fstab/fstab.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <liblp/builder.h>
#include "images.h"
+#include "mock_property_fetcher.h"
#include "reader.h"
#include "test_partition_opener.h"
#include "utility.h"
@@ -34,6 +36,8 @@
using namespace std;
using namespace android::fs_mgr;
+using ::testing::_;
+using ::testing::Return;
using unique_fd = android::base::unique_fd;
// Our tests assume a 128KiB disk with two 512 byte metadata slots.
@@ -664,7 +668,8 @@
}
TEST(liblp, UpdateRetrofit) {
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(true);
+ ON_CALL(*GetMockedInstance(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+ .WillByDefault(Return(true));
unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
ASSERT_NE(builder, nullptr);
@@ -695,7 +700,8 @@
}
TEST(liblp, UpdateNonRetrofit) {
- MetadataBuilder::OverrideRetrofitDynamicParititonsForTesting(false);
+ ON_CALL(*GetMockedInstance(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+ .WillByDefault(Return(false));
unique_fd fd = CreateFlashedDisk();
ASSERT_GE(fd, 0);
diff --git a/fs_mgr/liblp/mock_property_fetcher.h b/fs_mgr/liblp/mock_property_fetcher.h
new file mode 100644
index 0000000..eb91de2
--- /dev/null
+++ b/fs_mgr/liblp/mock_property_fetcher.h
@@ -0,0 +1,47 @@
+/*
+ * 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 <gmock/gmock.h>
+
+#include <liblp/property_fetcher.h>
+
+namespace android {
+namespace fs_mgr {
+
+class MockPropertyFetcher : public IPropertyFetcher {
+ public:
+ MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&));
+ MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool));
+
+ // By default, return default_value for all functions.
+ MockPropertyFetcher() {
+ using ::testing::_;
+ using ::testing::Invoke;
+ ON_CALL(*this, GetProperty(_, _)).WillByDefault(Invoke([](const auto&, const auto& def) {
+ return def;
+ }));
+ ON_CALL(*this, GetBoolProperty(_, _)).WillByDefault(Invoke([](const auto&, auto def) {
+ return def;
+ }));
+ }
+};
+
+} // namespace fs_mgr
+} // namespace android
+
+android::fs_mgr::MockPropertyFetcher* GetMockedInstance();
diff --git a/fs_mgr/liblp/property_fetcher.cpp b/fs_mgr/liblp/property_fetcher.cpp
new file mode 100644
index 0000000..038ef4d
--- /dev/null
+++ b/fs_mgr/liblp/property_fetcher.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 "liblp/property_fetcher.h"
+
+#include <memory>
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace fs_mgr {
+
+std::string PropertyFetcher::GetProperty(const std::string& key, const std::string& default_value) {
+ return android::base::GetProperty(key, default_value);
+}
+
+bool PropertyFetcher::GetBoolProperty(const std::string& key, bool default_value) {
+ return android::base::GetBoolProperty(key, default_value);
+}
+
+static std::unique_ptr<IPropertyFetcher>* GetInstanceAllocation() {
+ static std::unique_ptr<IPropertyFetcher> instance = std::make_unique<PropertyFetcher>();
+ return &instance;
+}
+
+IPropertyFetcher* IPropertyFetcher::GetInstance() {
+ return GetInstanceAllocation()->get();
+}
+
+void IPropertyFetcher::OverrideForTesting(std::unique_ptr<IPropertyFetcher>&& fetcher) {
+ GetInstanceAllocation()->swap(fetcher);
+ fetcher.reset();
+}
+
+} // namespace fs_mgr
+} // namespace android
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index dcee6d2..8dbe955 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -18,6 +18,7 @@
#include <stddef.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include <functional>
diff --git a/fs_mgr/liblp/writer.cpp b/fs_mgr/liblp/writer.cpp
index bffcb7e..8a983ad 100644
--- a/fs_mgr/liblp/writer.cpp
+++ b/fs_mgr/liblp/writer.cpp
@@ -17,6 +17,7 @@
#include "writer.h"
#include <inttypes.h>
+#include <string.h>
#include <unistd.h>
#include <string>
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 3a08049..52aad12 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -14,15 +14,13 @@
// limitations under the License.
//
-cc_library {
- name: "libsnapshot",
- recovery_available: true,
+cc_defaults {
+ name: "libsnapshot_defaults",
defaults: ["fs_mgr_defaults"],
- cppflags: [
+ cflags: [
"-D_FILE_OFFSET_BITS=64",
- ],
- srcs: [
- "snapshot.cpp",
+ "-Wall",
+ "-Werror",
],
shared_libs: [
"libbase",
@@ -30,7 +28,53 @@
],
static_libs: [
"libdm",
+ ],
+ whole_static_libs: [
"libext2_uuid",
+ "libext4_utils",
+ "libfiemap",
],
export_include_dirs: ["include"],
}
+
+filegroup {
+ name: "libsnapshot_sources",
+ srcs: [
+ "snapshot.cpp",
+ ],
+}
+
+cc_library_static {
+ name: "libsnapshot",
+ defaults: ["libsnapshot_defaults"],
+ srcs: [":libsnapshot_sources"],
+ static_libs: [
+ "libfiemap_binder",
+ ],
+}
+
+cc_library_static {
+ name: "libsnapshot_nobinder",
+ defaults: ["libsnapshot_defaults"],
+ srcs: [":libsnapshot_sources"],
+ recovery_available: true,
+}
+
+cc_test {
+ name: "libsnapshot_test",
+ defaults: ["libsnapshot_defaults"],
+ srcs: [
+ "snapshot_test.cpp",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
+ static_libs: [
+ "libcutils",
+ "libcrypto",
+ "libfs_mgr",
+ "liblp",
+ "libsnapshot",
+ ],
+}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 5cfd7fa..f7608dc 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -19,51 +19,79 @@
#include <chrono>
#include <memory>
#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
+#ifndef FRIEND_TEST
+#define FRIEND_TEST(test_set_name, individual_test) \
+ friend class test_set_name##_##individual_test##_Test
+#define DEFINED_FRIEND_TEST
+#endif
namespace android {
+
+namespace fiemap {
+class IImageManager;
+} // namespace fiemap
+
namespace snapshot {
-enum class UpdateStatus {
+enum class UpdateState : unsigned int {
// No update or merge is in progress.
None,
+ // An update is applying; snapshots may already exist.
+ Initiated,
+
// An update is pending, but has not been successfully booted yet.
Unverified,
// The kernel is merging in the background.
Merging,
+ // Post-merge cleanup steps could not be completed due to a transient
+ // error, but the next reboot will finish any pending operations.
+ MergeNeedsReboot,
+
// Merging is complete, and needs to be acknowledged.
- MergeCompleted
+ MergeCompleted,
+
+ // Merging failed due to an unrecoverable error.
+ MergeFailed
};
class SnapshotManager final {
public:
- // Return a new SnapshotManager instance, or null on error.
- static std::unique_ptr<SnapshotManager> New();
+ // Dependency injection for testing.
+ class IDeviceInfo {
+ public:
+ virtual ~IDeviceInfo() {}
+ virtual std::string GetGsidDir() const = 0;
+ virtual std::string GetMetadataDir() const = 0;
- // Create a new snapshot device with the given name, base device, and COW device
- // size. The new device path will be returned in |dev_path|. If timeout_ms is
- // greater than zero, this function will wait the given amount of time for
- // |dev_path| to become available, and fail otherwise. If timeout_ms is 0, then
- // no wait will occur and |dev_path| may not yet exist on return.
- bool CreateSnapshot(const std::string& name, const std::string& base_device, uint64_t cow_size,
- std::string* dev_path, const std::chrono::milliseconds& timeout_ms);
+ // Return true if the device is currently running off snapshot devices,
+ // indicating that we have booted after applying (but not merging) an
+ // OTA.
+ virtual bool IsRunningSnapshot() const = 0;
+ };
- // Map a snapshot device that was previously created with CreateSnapshot.
- // If a merge was previously initiated, the device-mapper table will have a
- // snapshot-merge target instead of a snapshot target. The timeout parameter
- // is the same as in CreateSnapshotDevice.
- bool MapSnapshotDevice(const std::string& name, const std::string& base_device,
- const std::chrono::milliseconds& timeout_ms, std::string* dev_path);
+ ~SnapshotManager();
- // Unmap a snapshot device previously mapped with MapSnapshotDevice().
- bool UnmapSnapshotDevice(const std::string& name);
+ // Return a new SnapshotManager instance, or null on error. The device
+ // pointer is owned for the lifetime of SnapshotManager. If null, a default
+ // instance will be created.
+ static std::unique_ptr<SnapshotManager> New(IDeviceInfo* device = nullptr);
- // Remove the backing copy-on-write image for the named snapshot. If the
- // device is still mapped, this will attempt an Unmap, and fail if the
- // unmap fails.
- bool DeleteSnapshot(const std::string& name);
+ // Begin an update. This must be called before creating any snapshots. It
+ // will fail if GetUpdateState() != None.
+ bool BeginUpdate();
+
+ // Cancel an update; any snapshots will be deleted. This will fail if the
+ // state != Initiated or None.
+ bool CancelUpdate();
// Initiate a merge on all snapshot devices. This should only be used after an
// update has been marked successful after booting.
@@ -71,18 +99,184 @@
// Wait for the current merge to finish, then perform cleanup when it
// completes. It is necessary to call this after InitiateMerge(), or when
- // a merge is detected for the first time after boot.
- bool WaitForMerge();
+ // a merge state is detected during boot.
+ //
+ // Note that after calling WaitForMerge(), GetUpdateState() may still return
+ // that a merge is in progress:
+ // MergeFailed indicates that a fatal error occurred. WaitForMerge() may
+ // called any number of times again to attempt to make more progress, but
+ // we do not expect it to succeed if a catastrophic error occurred.
+ //
+ // MergeNeedsReboot indicates that the merge has completed, but cleanup
+ // failed. This can happen if for some reason resources were not closed
+ // properly. In this case another reboot is needed before we can take
+ // another OTA. However, WaitForMerge() can be called again without
+ // rebooting, to attempt to finish cleanup anyway.
+ //
+ // MergeCompleted indicates that the update has fully completed.
+ // GetUpdateState will return None, and a new update can begin.
+ UpdateState WaitForMerge();
// Find the status of the current update, if any.
//
// |progress| depends on the returned status:
- // None: 0
- // Unverified: 0
- // Merging: Value in the range [0, 100)
+ // Merging: Value in the range [0, 100]
// MergeCompleted: 100
- UpdateStatus GetUpdateStatus(double* progress);
+ // Other: 0
+ UpdateState GetUpdateState(double* progress = nullptr);
+
+ private:
+ FRIEND_TEST(SnapshotTest, CreateSnapshot);
+ FRIEND_TEST(SnapshotTest, MapSnapshot);
+ FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
+ FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
+ FRIEND_TEST(SnapshotTest, Merge);
+ FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
+ friend class SnapshotTest;
+
+ using DmTargetSnapshot = android::dm::DmTargetSnapshot;
+ using IImageManager = android::fiemap::IImageManager;
+ using TargetInfo = android::dm::DeviceMapper::TargetInfo;
+
+ explicit SnapshotManager(IDeviceInfo* info);
+
+ // This is created lazily since it connects via binder.
+ bool EnsureImageManager();
+
+ // Helper function for tests.
+ IImageManager* image_manager() const { return images_.get(); }
+
+ // Since libsnapshot is included into multiple processes, we flock() our
+ // files for simple synchronization. LockedFile is a helper to assist with
+ // this. It also serves as a proof-of-lock for some functions.
+ class LockedFile final {
+ public:
+ LockedFile(const std::string& path, android::base::unique_fd&& fd, int lock_mode)
+ : path_(path), fd_(std::move(fd)), lock_mode_(lock_mode) {}
+ ~LockedFile();
+
+ const std::string& path() const { return path_; }
+ int fd() const { return fd_; }
+ int lock_mode() const { return lock_mode_; }
+
+ private:
+ std::string path_;
+ android::base::unique_fd fd_;
+ int lock_mode_;
+ };
+ std::unique_ptr<LockedFile> OpenFile(const std::string& file, int open_flags, int lock_flags);
+ bool Truncate(LockedFile* file);
+
+ // Create a new snapshot record. This creates the backing COW store and
+ // persists information needed to map the device. The device can be mapped
+ // with MapSnapshot().
+ //
+ // |device_size| should be the size of the base_device that will be passed
+ // via MapDevice(). |snapshot_size| should be the number of bytes in the
+ // base device, starting from 0, that will be snapshotted. The cow_size
+ // should be the amount of space that will be allocated to store snapshot
+ // deltas.
+ //
+ // If |snapshot_size| < device_size, then the device will always
+ // be mapped with two table entries: a dm-snapshot range covering
+ // snapshot_size, and a dm-linear range covering the remainder.
+ //
+ // All sizes are specified in bytes, and the device and snapshot sizes
+ // must be a multiple of the sector size (512 bytes). |cow_size| will
+ // be rounded up to the nearest sector.
+ bool CreateSnapshot(LockedFile* lock, const std::string& name, uint64_t device_size,
+ uint64_t snapshot_size, uint64_t cow_size);
+
+ // Map a snapshot device that was previously created with CreateSnapshot.
+ // If a merge was previously initiated, the device-mapper table will have a
+ // snapshot-merge target instead of a snapshot target. If the timeout
+ // parameter greater than zero, this function will wait the given amount
+ // of time for |dev_path| to become available, and fail otherwise. If
+ // timeout_ms is 0, then no wait will occur and |dev_path| may not yet
+ // exist on return.
+ bool MapSnapshot(LockedFile* lock, const std::string& name, const std::string& base_device,
+ const std::chrono::milliseconds& timeout_ms, std::string* dev_path);
+
+ // Remove the backing copy-on-write image for the named snapshot. The
+ // caller is responsible for ensuring that the snapshot is unmapped.
+ bool DeleteSnapshot(LockedFile* lock, const std::string& name);
+
+ // Unmap a snapshot device previously mapped with MapSnapshotDevice().
+ bool UnmapSnapshot(LockedFile* lock, const std::string& name);
+
+ // Unmap and remove all known snapshots.
+ bool RemoveAllSnapshots(LockedFile* lock);
+
+ // List the known snapshot names.
+ bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots);
+
+ // Interact with /metadata/ota/state.
+ std::unique_ptr<LockedFile> OpenStateFile(int open_flags, int lock_flags);
+ std::unique_ptr<LockedFile> LockShared();
+ std::unique_ptr<LockedFile> LockExclusive();
+ UpdateState ReadUpdateState(LockedFile* file);
+ bool WriteUpdateState(LockedFile* file, UpdateState state);
+ std::string GetStateFilePath() const;
+
+ // This state is persisted per-snapshot in /metadata/ota/snapshots/.
+ struct SnapshotStatus {
+ std::string state;
+ uint64_t device_size;
+ uint64_t snapshot_size;
+ // These are non-zero when merging.
+ uint64_t sectors_allocated = 0;
+ uint64_t metadata_sectors = 0;
+ };
+
+ // Helpers for merging.
+ bool SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
+ bool RewriteSnapshotDeviceTable(const std::string& dm_name);
+ bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
+ void AcknowledgeMergeSuccess(LockedFile* lock);
+ void AcknowledgeMergeFailure();
+
+ // Note that these require the name of the device containing the snapshot,
+ // which may be the "inner" device. Use GetsnapshotDeviecName().
+ bool QuerySnapshotStatus(const std::string& dm_name, std::string* target_type,
+ DmTargetSnapshot::Status* status);
+ bool IsSnapshotDevice(const std::string& dm_name, TargetInfo* target = nullptr);
+
+ // Internal callback for when merging is complete.
+ bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
+ const SnapshotStatus& status);
+ bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
+
+ // Only the following UpdateStates are used here:
+ // UpdateState::Merging
+ // UpdateState::MergeCompleted
+ // UpdateState::MergeFailed
+ // UpdateState::MergeNeedsReboot
+ UpdateState CheckMergeState();
+ UpdateState CheckMergeState(LockedFile* lock);
+ UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name);
+
+ // Interact with status files under /metadata/ota/snapshots.
+ bool WriteSnapshotStatus(LockedFile* lock, const std::string& name,
+ const SnapshotStatus& status);
+ bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status);
+ std::string GetSnapshotStatusFilePath(const std::string& name);
+
+ // Return the name of the device holding the "snapshot" or "snapshot-merge"
+ // target. This may not be the final device presented via MapSnapshot(), if
+ // for example there is a linear segment.
+ std::string GetSnapshotDeviceName(const std::string& snapshot_name,
+ const SnapshotStatus& status);
+
+ std::string gsid_dir_;
+ std::string metadata_dir_;
+ std::unique_ptr<IDeviceInfo> device_;
+ std::unique_ptr<IImageManager> images_;
};
} // namespace snapshot
} // namespace android
+
+#ifdef DEFINED_FRIEND_TEST
+#undef DEFINED_FRIEND_TEST
+#undef FRIEND_TEST
+#endif
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 3e80239..63a01f3 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -14,58 +14,1096 @@
#include <libsnapshot/snapshot.h>
+#include <dirent.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/unistd.h>
+
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
namespace android {
namespace snapshot {
-std::unique_ptr<SnapshotManager> SnapshotManager::New() {
- return std::make_unique<SnapshotManager>();
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::dm::DmTable;
+using android::dm::DmTargetLinear;
+using android::dm::DmTargetSnapshot;
+using android::dm::kSectorSize;
+using android::dm::SnapshotStorageMode;
+using android::fiemap::IImageManager;
+using namespace std::chrono_literals;
+using namespace std::string_literals;
+
+// Unit is sectors, this is a 4K chunk.
+static constexpr uint32_t kSnapshotChunkSize = 8;
+
+class DeviceInfo final : public SnapshotManager::IDeviceInfo {
+ public:
+ std::string GetGsidDir() const override { return "ota"s; }
+ std::string GetMetadataDir() const override { return "/metadata/ota"s; }
+ bool IsRunningSnapshot() const override;
+};
+
+bool DeviceInfo::IsRunningSnapshot() const {
+ // :TODO: implement this check.
+ return true;
}
-bool SnapshotManager::CreateSnapshot(const std::string& name, const std::string& base_device,
- uint64_t cow_size, std::string* dev_path,
- const std::chrono::milliseconds& timeout_ms) {
- // (1) Create COW device using libgsi_image.
- // (2) Create snapshot device using libdm + DmTargetSnapshot.
- // (3) Record partition in /metadata/ota.
- (void)name;
- (void)base_device;
- (void)cow_size;
- (void)dev_path;
- (void)timeout_ms;
- return false;
+// Note: IIMageManager is an incomplete type in the header, so the default
+// destructor doesn't work.
+SnapshotManager::~SnapshotManager() {}
+
+std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
+ if (!info) {
+ info = new DeviceInfo();
+ }
+ return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
}
-bool SnapshotManager::MapSnapshotDevice(const std::string& name, const std::string& base_device,
- const std::chrono::milliseconds& timeout_ms,
- std::string* dev_path) {
- (void)name;
- (void)base_device;
- (void)dev_path;
- (void)timeout_ms;
- return false;
+SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
+ gsid_dir_ = device_->GetGsidDir();
+ metadata_dir_ = device_->GetMetadataDir();
}
-bool SnapshotManager::UnmapSnapshotDevice(const std::string& name) {
- (void)name;
- return false;
+static std::string GetCowName(const std::string& snapshot_name) {
+ return snapshot_name + "-cow";
}
-bool SnapshotManager::DeleteSnapshot(const std::string& name) {
- (void)name;
- return false;
+bool SnapshotManager::BeginUpdate() {
+ auto file = LockExclusive();
+ if (!file) return false;
+
+ auto state = ReadUpdateState(file.get());
+ if (state != UpdateState::None) {
+ LOG(ERROR) << "An update is already in progress, cannot begin a new update";
+ return false;
+ }
+ return WriteUpdateState(file.get(), UpdateState::Initiated);
+}
+
+bool SnapshotManager::CancelUpdate() {
+ auto file = LockExclusive();
+ if (!file) return false;
+
+ UpdateState state = ReadUpdateState(file.get());
+ if (state == UpdateState::None) return true;
+ if (state != UpdateState::Initiated) {
+ LOG(ERROR) << "Cannot cancel update after it has completed or started merging";
+ return false;
+ }
+
+ if (!RemoveAllSnapshots(file.get())) {
+ LOG(ERROR) << "Could not remove all snapshots";
+ return false;
+ }
+
+ if (!WriteUpdateState(file.get(), UpdateState::None)) {
+ LOG(ERROR) << "Could not write new update state";
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
+ uint64_t device_size, uint64_t snapshot_size,
+ uint64_t cow_size) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ // Sanity check these sizes. Like liblp, we guarantee the partition size
+ // is respected, which means it has to be sector-aligned. (This guarantee
+ // is useful for locating avb footers correctly). The COW size, however,
+ // can be arbitrarily larger than specified, so we can safely round it up.
+ if (device_size % kSectorSize != 0) {
+ LOG(ERROR) << "Snapshot " << name
+ << " device size is not a multiple of the sector size: " << device_size;
+ return false;
+ }
+ if (snapshot_size % kSectorSize != 0) {
+ LOG(ERROR) << "Snapshot " << name
+ << " snapshot size is not a multiple of the sector size: " << snapshot_size;
+ return false;
+ }
+
+ // Round the COW size up to the nearest sector.
+ cow_size += kSectorSize - 1;
+ cow_size &= ~(kSectorSize - 1);
+
+ LOG(INFO) << "Snapshot " << name << " will have COW size " << cow_size;
+
+ // Note, we leave the status file hanging around if we fail to create the
+ // actual backing image. This is harmless, since it'll get removed when
+ // CancelUpdate is called.
+ SnapshotStatus status = {
+ .state = "created",
+ .device_size = device_size,
+ .snapshot_size = snapshot_size,
+ };
+ if (!WriteSnapshotStatus(lock, name, status)) {
+ PLOG(ERROR) << "Could not write snapshot status: " << name;
+ return false;
+ }
+
+ auto cow_name = GetCowName(name);
+ int cow_flags = IImageManager::CREATE_IMAGE_ZERO_FILL;
+ return images_->CreateBackingImage(cow_name, cow_size, cow_flags);
+}
+
+bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
+ const std::string& base_device,
+ const std::chrono::milliseconds& timeout_ms,
+ std::string* dev_path) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ SnapshotStatus status;
+ if (!ReadSnapshotStatus(lock, name, &status)) {
+ return false;
+ }
+ if (status.state == "merge-completed") {
+ LOG(ERROR) << "Should not create a snapshot device for " << name
+ << " after merging has completed.";
+ return false;
+ }
+
+ // Validate the block device size, as well as the requested snapshot size.
+ // During this we also compute the linear sector region if any.
+ {
+ unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
+ if (fd < 0) {
+ PLOG(ERROR) << "open failed: " << base_device;
+ return false;
+ }
+ auto dev_size = get_block_device_size(fd);
+ if (!dev_size) {
+ PLOG(ERROR) << "Could not determine block device size: " << base_device;
+ return false;
+ }
+ if (status.device_size != dev_size) {
+ LOG(ERROR) << "Block device size for " << base_device << " does not match"
+ << "(expected " << status.device_size << ", got " << dev_size << ")";
+ return false;
+ }
+ }
+ if (status.device_size % kSectorSize != 0) {
+ LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size;
+ return false;
+ }
+ if (status.snapshot_size % kSectorSize != 0 || status.snapshot_size > status.device_size) {
+ LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size;
+ return false;
+ }
+ uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
+ uint64_t linear_sectors = (status.device_size - status.snapshot_size) / kSectorSize;
+
+ auto cow_name = GetCowName(name);
+
+ std::string cow_dev;
+ if (!images_->MapImageDevice(cow_name, timeout_ms, &cow_dev)) {
+ LOG(ERROR) << "Could not map image device: " << cow_name;
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+
+ // Note that merging is a global state. We do track whether individual devices
+ // have completed merging, but the start of the merge process is considered
+ // atomic.
+ SnapshotStorageMode mode;
+ switch (ReadUpdateState(lock)) {
+ case UpdateState::MergeCompleted:
+ case UpdateState::MergeNeedsReboot:
+ LOG(ERROR) << "Should not create a snapshot device for " << name
+ << " after global merging has completed.";
+ return false;
+ case UpdateState::Merging:
+ case UpdateState::MergeFailed:
+ // Note: MergeFailed indicates that a merge is in progress, but
+ // is possibly stalled. We still have to honor the merge.
+ mode = SnapshotStorageMode::Merge;
+ break;
+ default:
+ mode = SnapshotStorageMode::Persistent;
+ break;
+ }
+
+ // The kernel (tested on 4.19) crashes horribly if a device has both a snapshot
+ // and a linear target in the same table. Instead, we stack them, and give the
+ // snapshot device a different name. It is not exposed to the caller in this
+ // case.
+ auto snap_name = (linear_sectors > 0) ? name + "-inner" : name;
+
+ DmTable table;
+ table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_dev, mode,
+ kSnapshotChunkSize);
+ if (!dm.CreateDevice(snap_name, table, dev_path, timeout_ms)) {
+ LOG(ERROR) << "Could not create snapshot device: " << snap_name;
+ images_->UnmapImageDevice(cow_name);
+ return false;
+ }
+
+ if (linear_sectors) {
+ // Our stacking will looks like this:
+ // [linear, linear] ; to snapshot, and non-snapshot region of base device
+ // [snapshot-inner]
+ // [base device] [cow]
+ DmTable table;
+ table.Emplace<DmTargetLinear>(0, snapshot_sectors, *dev_path, 0);
+ table.Emplace<DmTargetLinear>(snapshot_sectors, linear_sectors, base_device,
+ snapshot_sectors);
+ if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
+ LOG(ERROR) << "Could not create outer snapshot device: " << name;
+ dm.DeleteDevice(snap_name);
+ images_->UnmapImageDevice(cow_name);
+ return false;
+ }
+ }
+
+ // :TODO: when merging is implemented, we need to add an argument to the
+ // status indicating how much progress is left to merge. (device-mapper
+ // does not retain the initial values, so we can't derive them.)
+ return true;
+}
+
+bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ SnapshotStatus status;
+ if (!ReadSnapshotStatus(lock, name, &status)) {
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ if (dm.GetState(name) != DmDeviceState::INVALID && !dm.DeleteDevice(name)) {
+ LOG(ERROR) << "Could not delete snapshot device: " << name;
+ return false;
+ }
+
+ // There may be an extra device, since the kernel doesn't let us have a
+ // snapshot and linear target in the same table.
+ auto dm_name = GetSnapshotDeviceName(name, status);
+ if (name != dm_name && !dm.DeleteDevice(dm_name)) {
+ LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name;
+ return false;
+ }
+
+ auto cow_name = GetCowName(name);
+ if (images_->IsImageMapped(cow_name) && !images_->UnmapImageDevice(cow_name)) {
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name) {
+ CHECK(lock);
+ if (!EnsureImageManager()) return false;
+
+ auto cow_name = GetCowName(name);
+ if (!images_->BackingImageExists(cow_name)) {
+ return true;
+ }
+ if (images_->IsImageMapped(cow_name) && !images_->UnmapImageDevice(cow_name)) {
+ return false;
+ }
+ if (!images_->DeleteBackingImage(cow_name)) {
+ return false;
+ }
+
+ std::string error;
+ auto file_path = GetSnapshotStatusFilePath(name);
+ if (!android::base::RemoveFileIfExists(file_path, &error)) {
+ LOG(ERROR) << "Failed to remove status file " << file_path << ": " << error;
+ return false;
+ }
+ return true;
}
bool SnapshotManager::InitiateMerge() {
- return false;
+ auto lock = LockExclusive();
+ if (!lock) return false;
+
+ UpdateState state = ReadUpdateState(lock.get());
+ if (state != UpdateState::Unverified) {
+ LOG(ERROR) << "Cannot begin a merge if an update has not been verified";
+ return false;
+ }
+ if (!device_->IsRunningSnapshot()) {
+ LOG(ERROR) << "Cannot begin a merge if the device is not booted off a snapshot";
+ return false;
+ }
+
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock.get(), &snapshots)) {
+ LOG(ERROR) << "Could not list snapshots";
+ return false;
+ }
+
+ auto& dm = DeviceMapper::Instance();
+ for (const auto& snapshot : snapshots) {
+ // The device has to be mapped, since everything should be merged at
+ // the same time. This is a fairly serious error. We could forcefully
+ // map everything here, but it should have been mapped during first-
+ // stage init.
+ if (dm.GetState(snapshot) == DmDeviceState::INVALID) {
+ LOG(ERROR) << "Cannot begin merge; device " << snapshot << " is not mapped.";
+ return false;
+ }
+ }
+
+ // Point of no return - mark that we're starting a merge. From now on every
+ // snapshot must be a merge target.
+ if (!WriteUpdateState(lock.get(), UpdateState::Merging)) {
+ return false;
+ }
+
+ bool rewrote_all = true;
+ for (const auto& snapshot : snapshots) {
+ // If this fails, we have no choice but to continue. Everything must
+ // be merged. This is not an ideal state to be in, but it is safe,
+ // because we the next boot will try again.
+ if (!SwitchSnapshotToMerge(lock.get(), snapshot)) {
+ LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot;
+ rewrote_all = false;
+ }
+ }
+
+ // If we couldn't switch everything to a merge target, pre-emptively mark
+ // this merge as failed. It will get acknowledged when WaitForMerge() is
+ // called.
+ if (!rewrote_all) {
+ WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+ }
+
+ // Return true no matter what, because a merge was initiated.
+ return true;
}
-bool SnapshotManager::WaitForMerge() {
- return false;
+bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
+ SnapshotStatus status;
+ if (!ReadSnapshotStatus(lock, name, &status)) {
+ return false;
+ }
+ if (status.state != "created") {
+ LOG(WARNING) << "Snapshot " << name << " has unexpected state: " << status.state;
+ }
+
+ // After this, we return true because we technically did switch to a merge
+ // target. Everything else we do here is just informational.
+ auto dm_name = GetSnapshotDeviceName(name, status);
+ if (!RewriteSnapshotDeviceTable(dm_name)) {
+ return false;
+ }
+
+ status.state = "merging";
+
+ DmTargetSnapshot::Status dm_status;
+ if (!QuerySnapshotStatus(dm_name, nullptr, &dm_status)) {
+ LOG(ERROR) << "Could not query merge status for snapshot: " << dm_name;
+ }
+ status.sectors_allocated = dm_status.sectors_allocated;
+ status.metadata_sectors = dm_status.metadata_sectors;
+ if (!WriteSnapshotStatus(lock, name, status)) {
+ LOG(ERROR) << "Could not update status file for snapshot: " << name;
+ }
+ return true;
}
-UpdateStatus SnapshotManager::GetUpdateStatus(double* progress) {
- *progress = 0.0f;
- return UpdateStatus::None;
+bool SnapshotManager::RewriteSnapshotDeviceTable(const std::string& dm_name) {
+ auto& dm = DeviceMapper::Instance();
+
+ std::vector<DeviceMapper::TargetInfo> old_targets;
+ if (!dm.GetTableInfo(dm_name, &old_targets)) {
+ LOG(ERROR) << "Could not read snapshot device table: " << dm_name;
+ return false;
+ }
+ if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") {
+ LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << dm_name;
+ return false;
+ }
+
+ std::string base_device, cow_device;
+ if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) {
+ LOG(ERROR) << "Could not derive underlying devices for snapshot: " << dm_name;
+ return false;
+ }
+
+ DmTable table;
+ table.Emplace<DmTargetSnapshot>(0, old_targets[0].spec.length, base_device, cow_device,
+ SnapshotStorageMode::Merge, kSnapshotChunkSize);
+ if (!dm.LoadTableAndActivate(dm_name, table)) {
+ LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << dm_name;
+ return false;
+ }
+ LOG(INFO) << "Successfully switched snapshot device to a merge target: " << dm_name;
+ return true;
+}
+
+enum class TableQuery {
+ Table,
+ Status,
+};
+
+static bool GetSingleTarget(const std::string& dm_name, TableQuery query,
+ DeviceMapper::TargetInfo* target) {
+ auto& dm = DeviceMapper::Instance();
+ if (dm.GetState(dm_name) == DmDeviceState::INVALID) {
+ return false;
+ }
+
+ std::vector<DeviceMapper::TargetInfo> targets;
+ bool result;
+ if (query == TableQuery::Status) {
+ result = dm.GetTableStatus(dm_name, &targets);
+ } else {
+ result = dm.GetTableInfo(dm_name, &targets);
+ }
+ if (!result) {
+ LOG(ERROR) << "Could not query device: " << dm_name;
+ return false;
+ }
+ if (targets.size() != 1) {
+ return false;
+ }
+
+ *target = std::move(targets[0]);
+ return true;
+}
+
+bool SnapshotManager::IsSnapshotDevice(const std::string& dm_name, TargetInfo* target) {
+ DeviceMapper::TargetInfo snap_target;
+ if (!GetSingleTarget(dm_name, TableQuery::Status, &snap_target)) {
+ return false;
+ }
+ auto type = DeviceMapper::GetTargetType(snap_target.spec);
+ if (type != "snapshot" && type != "snapshot-merge") {
+ return false;
+ }
+ if (target) {
+ *target = std::move(snap_target);
+ }
+ return true;
+}
+
+bool SnapshotManager::QuerySnapshotStatus(const std::string& dm_name, std::string* target_type,
+ DmTargetSnapshot::Status* status) {
+ DeviceMapper::TargetInfo target;
+ if (!IsSnapshotDevice(dm_name, &target)) {
+ LOG(ERROR) << "Device " << dm_name << " is not a snapshot or snapshot-merge device";
+ return false;
+ }
+ if (!DmTargetSnapshot::ParseStatusText(target.data, status)) {
+ LOG(ERROR) << "Could not parse snapshot status text: " << dm_name;
+ return false;
+ }
+ if (target_type) {
+ *target_type = DeviceMapper::GetTargetType(target.spec);
+ }
+ return true;
+}
+
+// Note that when a merge fails, we will *always* try again to complete the
+// merge each time the device boots. There is no harm in doing so, and if
+// the problem was transient, we might manage to get a new outcome.
+UpdateState SnapshotManager::WaitForMerge() {
+ while (true) {
+ UpdateState state = CheckMergeState();
+ if (state != UpdateState::Merging) {
+ // Either there is no merge, or the merge was finished, so no need
+ // to keep waiting.
+ return state;
+ }
+
+ // This wait is not super time sensitive, so we have a relatively
+ // low polling frequency.
+ std::this_thread::sleep_for(2s);
+ }
+}
+
+UpdateState SnapshotManager::CheckMergeState() {
+ auto lock = LockExclusive();
+ if (!lock) {
+ AcknowledgeMergeFailure();
+ return UpdateState::MergeFailed;
+ }
+
+ auto state = CheckMergeState(lock.get());
+ if (state == UpdateState::MergeCompleted) {
+ AcknowledgeMergeSuccess(lock.get());
+ } else if (state == UpdateState::MergeFailed) {
+ AcknowledgeMergeFailure();
+ }
+ return state;
+}
+
+UpdateState SnapshotManager::CheckMergeState(LockedFile* lock) {
+ UpdateState state = ReadUpdateState(lock);
+ switch (state) {
+ case UpdateState::None:
+ case UpdateState::MergeCompleted:
+ // Harmless races are allowed between two callers of WaitForMerge,
+ // so in both of these cases we just propagate the state.
+ return state;
+
+ case UpdateState::Merging:
+ case UpdateState::MergeNeedsReboot:
+ case UpdateState::MergeFailed:
+ // We'll poll each snapshot below. Note that for the NeedsReboot
+ // case, we always poll once to give cleanup another opportunity to
+ // run.
+ break;
+
+ default:
+ LOG(ERROR) << "No merge exists, cannot wait. Update state: "
+ << static_cast<uint32_t>(state);
+ return UpdateState::None;
+ }
+
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock, &snapshots)) {
+ return UpdateState::MergeFailed;
+ }
+
+ bool failed = false;
+ bool merging = false;
+ bool needs_reboot = false;
+ for (const auto& snapshot : snapshots) {
+ UpdateState snapshot_state = CheckTargetMergeState(lock, snapshot);
+ switch (snapshot_state) {
+ case UpdateState::MergeFailed:
+ failed = true;
+ break;
+ case UpdateState::Merging:
+ merging = true;
+ break;
+ case UpdateState::MergeNeedsReboot:
+ needs_reboot = true;
+ break;
+ case UpdateState::MergeCompleted:
+ break;
+ default:
+ LOG(ERROR) << "Unknown merge status: " << static_cast<uint32_t>(snapshot_state);
+ failed = true;
+ break;
+ }
+ }
+
+ if (merging) {
+ // Note that we handle "Merging" before we handle anything else. We
+ // want to poll until *nothing* is merging if we can, so everything has
+ // a chance to get marked as completed or failed.
+ return UpdateState::Merging;
+ }
+ if (failed) {
+ // Note: since there are many drop-out cases for failure, we acknowledge
+ // it in WaitForMerge rather than here and elsewhere.
+ return UpdateState::MergeFailed;
+ }
+ if (needs_reboot) {
+ WriteUpdateState(lock, UpdateState::MergeNeedsReboot);
+ return UpdateState::MergeNeedsReboot;
+ }
+ return UpdateState::MergeCompleted;
+}
+
+UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name) {
+ SnapshotStatus snapshot_status;
+ if (!ReadSnapshotStatus(lock, name, &snapshot_status)) {
+ return UpdateState::MergeFailed;
+ }
+
+ std::string dm_name = GetSnapshotDeviceName(name, snapshot_status);
+
+ // During a check, we decided the merge was complete, but we were unable to
+ // collapse the device-mapper stack and perform COW cleanup. If we haven't
+ // rebooted after this check, the device will still be a snapshot-merge
+ // target. If the have rebooted, the device will now be a linear target,
+ // and we can try cleanup again.
+ if (snapshot_status.state == "merge-complete" && !IsSnapshotDevice(dm_name)) {
+ // NB: It's okay if this fails now, we gave cleanup our best effort.
+ OnSnapshotMergeComplete(lock, name, snapshot_status);
+ return UpdateState::MergeCompleted;
+ }
+
+ std::string target_type;
+ DmTargetSnapshot::Status status;
+ if (!QuerySnapshotStatus(dm_name, &target_type, &status)) {
+ return UpdateState::MergeFailed;
+ }
+ if (target_type != "snapshot-merge") {
+ // We can get here if we failed to rewrite the target type in
+ // InitiateMerge(). If we failed to create the target in first-stage
+ // init, boot would not succeed.
+ LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
+ return UpdateState::MergeFailed;
+ }
+
+ // These two values are equal when merging is complete.
+ if (status.sectors_allocated != status.metadata_sectors) {
+ if (snapshot_status.state == "merge-complete") {
+ LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
+ return UpdateState::MergeFailed;
+ }
+ return UpdateState::Merging;
+ }
+
+ // Merging is done. First, update the status file to indicate the merge
+ // is complete. We do this before calling OnSnapshotMergeComplete, even
+ // though this means the write is potentially wasted work (since in the
+ // ideal case we'll immediately delete the file).
+ //
+ // This makes it simpler to reason about the next reboot: no matter what
+ // part of cleanup failed, first-stage init won't try to create another
+ // snapshot device for this partition.
+ snapshot_status.state = "merge-complete";
+ if (!WriteSnapshotStatus(lock, name, snapshot_status)) {
+ return UpdateState::MergeFailed;
+ }
+ if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
+ return UpdateState::MergeNeedsReboot;
+ }
+ return UpdateState::MergeCompleted;
+}
+
+void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
+ if (!WriteUpdateState(lock, UpdateState::None)) {
+ // We'll try again next reboot, ad infinitum.
+ return;
+ }
+}
+
+void SnapshotManager::AcknowledgeMergeFailure() {
+ // Log first, so worst case, we always have a record of why the calls below
+ // were being made.
+ LOG(ERROR) << "Merge could not be completed and will be marked as failed.";
+
+ auto lock = LockExclusive();
+ if (!lock) return;
+
+ // Since we released the lock in between WaitForMerge and here, it's
+ // possible (1) the merge successfully completed or (2) was already
+ // marked as a failure. So make sure to check the state again, and
+ // only mark as a failure if appropriate.
+ UpdateState state = ReadUpdateState(lock.get());
+ if (state != UpdateState::Merging && state != UpdateState::MergeNeedsReboot) {
+ return;
+ }
+
+ WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+}
+
+bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
+ const SnapshotStatus& status) {
+ auto dm_name = GetSnapshotDeviceName(name, status);
+ if (IsSnapshotDevice(dm_name)) {
+ // We are extra-cautious here, to avoid deleting the wrong table.
+ std::string target_type;
+ DmTargetSnapshot::Status dm_status;
+ if (!QuerySnapshotStatus(dm_name, &target_type, &dm_status)) {
+ return false;
+ }
+ if (target_type != "snapshot-merge") {
+ LOG(ERROR) << "Unexpected target type " << target_type
+ << " for snapshot device: " << dm_name;
+ return false;
+ }
+ if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
+ LOG(ERROR) << "Merge is unexpectedly incomplete for device " << dm_name;
+ return false;
+ }
+ if (!CollapseSnapshotDevice(name, status)) {
+ LOG(ERROR) << "Unable to collapse snapshot: " << name;
+ return false;
+ }
+ // Note that collapsing is implicitly an Unmap, so we don't need to
+ // unmap the snapshot.
+ }
+
+ if (!DeleteSnapshot(lock, name)) {
+ LOG(ERROR) << "Could not delete snapshot: " << name;
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
+ const SnapshotStatus& status) {
+ // Ideally, we would complete the following steps to collapse the device:
+ // (1) Rewrite the snapshot table to be identical to the base device table.
+ // (2) Rewrite the verity table to use the "snapshot" (now linear) device.
+ // (3) Delete the base device.
+ //
+ // This should be possible once libsnapshot understands LpMetadata. In the
+ // meantime, we implement a simpler solution: rewriting the snapshot table
+ // to be a single dm-linear segment against the base device. While not as
+ // ideal, it still lets us remove the COW device. We can remove this
+ // implementation once the new method has been tested.
+ auto& dm = DeviceMapper::Instance();
+ auto dm_name = GetSnapshotDeviceName(name, status);
+
+ DeviceMapper::TargetInfo target;
+ if (!GetSingleTarget(dm_name, TableQuery::Table, &target)) {
+ return false;
+ }
+ if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
+ // This should be impossible, it was checked above.
+ LOG(ERROR) << "Snapshot device has invalid target type: " << dm_name;
+ return false;
+ }
+
+ std::string base_device, cow_device;
+ if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
+ LOG(ERROR) << "Could not parse snapshot device " << dm_name
+ << " parameters: " << target.data;
+ return false;
+ }
+
+ uint64_t num_sectors = status.snapshot_size / kSectorSize;
+ if (num_sectors * kSectorSize != status.snapshot_size) {
+ LOG(ERROR) << "Snapshot " << name
+ << " size is not sector aligned: " << status.snapshot_size;
+ return false;
+ }
+
+ if (dm_name != name) {
+ // We've derived the base device, but we actually need to replace the
+ // table of the outermost device. Do a quick verification that this
+ // device looks like we expect it to.
+ std::vector<DeviceMapper::TargetInfo> outer_table;
+ if (!dm.GetTableInfo(name, &outer_table)) {
+ LOG(ERROR) << "Could not validate outer snapshot table: " << name;
+ return false;
+ }
+ if (outer_table.size() != 2) {
+ LOG(ERROR) << "Expected 2 dm-linear targets for tabble " << name
+ << ", got: " << outer_table.size();
+ return false;
+ }
+ for (const auto& target : outer_table) {
+ auto target_type = DeviceMapper::GetTargetType(target.spec);
+ if (target_type != "linear") {
+ LOG(ERROR) << "Outer snapshot table may only contain linear targets, but " << name
+ << " has target: " << target_type;
+ return false;
+ }
+ }
+ uint64_t sectors = outer_table[0].spec.length + outer_table[1].spec.length;
+ if (sectors != num_sectors) {
+ LOG(ERROR) << "Outer snapshot " << name << " should have " << num_sectors
+ << ", got: " << sectors;
+ return false;
+ }
+ }
+
+ // Note: we are replacing the OUTER table here, so we do not use dm_name.
+ DmTargetLinear new_target(0, num_sectors, base_device, 0);
+ LOG(INFO) << "Replacing snapshot device " << name
+ << " table with: " << new_target.GetParameterString();
+
+ DmTable table;
+ table.Emplace<DmTargetLinear>(new_target);
+ if (!dm.LoadTableAndActivate(name, table)) {
+ return false;
+ }
+
+ if (dm_name != name) {
+ // Attempt to delete the snapshot device. Nothing should be depending on
+ // the device, and device-mapper should have flushed remaining I/O. We
+ // could in theory replace with dm-zero (or re-use the table above), but
+ // for now it's better to know why this would fail.
+ if (!dm.DeleteDevice(dm_name)) {
+ LOG(ERROR) << "Unable to delete snapshot device " << dm_name << ", COW cannot be "
+ << "reclaimed until after reboot.";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock, &snapshots)) {
+ LOG(ERROR) << "Could not list snapshots";
+ return false;
+ }
+
+ bool ok = true;
+ for (const auto& name : snapshots) {
+ ok &= DeleteSnapshot(lock, name);
+ }
+ return ok;
+}
+
+UpdateState SnapshotManager::GetUpdateState(double* progress) {
+ // If we've never started an update, the state file won't exist.
+ auto state_file = GetStateFilePath();
+ if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) {
+ return UpdateState::None;
+ }
+
+ auto file = LockShared();
+ if (!file) {
+ return UpdateState::None;
+ }
+
+ auto state = ReadUpdateState(file.get());
+ if (progress) {
+ *progress = 0.0;
+ if (state == UpdateState::Merging) {
+ // :TODO: When merging is implemented, set progress_val.
+ } else if (state == UpdateState::MergeCompleted) {
+ *progress = 100.0;
+ }
+ }
+ return state;
+}
+
+bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots) {
+ CHECK(lock);
+
+ auto dir_path = metadata_dir_ + "/snapshots"s;
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(dir_path.c_str()), closedir);
+ if (!dir) {
+ PLOG(ERROR) << "opendir failed: " << dir_path;
+ return false;
+ }
+
+ struct dirent* dp;
+ while ((dp = readdir(dir.get())) != nullptr) {
+ if (dp->d_type != DT_REG) continue;
+ snapshots->emplace_back(dp->d_name);
+ }
+ return true;
+}
+
+auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags)
+ -> std::unique_ptr<LockedFile> {
+ unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660));
+ if (fd < 0) {
+ PLOG(ERROR) << "Open failed: " << file;
+ return nullptr;
+ }
+ if (flock(fd, lock_flags) < 0) {
+ PLOG(ERROR) << "Acquire flock failed: " << file;
+ return nullptr;
+ }
+ // For simplicity, we want to CHECK that lock_mode == LOCK_EX, in some
+ // calls, so strip extra flags.
+ int lock_mode = lock_flags & (LOCK_EX | LOCK_SH);
+ return std::make_unique<LockedFile>(file, std::move(fd), lock_mode);
+}
+
+SnapshotManager::LockedFile::~LockedFile() {
+ if (flock(fd_, LOCK_UN) < 0) {
+ PLOG(ERROR) << "Failed to unlock file: " << path_;
+ }
+}
+
+std::string SnapshotManager::GetStateFilePath() const {
+ return metadata_dir_ + "/state"s;
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::OpenStateFile(int open_flags,
+ int lock_flags) {
+ auto state_file = GetStateFilePath();
+ return OpenFile(state_file, open_flags, lock_flags);
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::LockShared() {
+ return OpenStateFile(O_RDONLY, LOCK_SH);
+}
+
+std::unique_ptr<SnapshotManager::LockedFile> SnapshotManager::LockExclusive() {
+ return OpenStateFile(O_RDWR | O_CREAT, LOCK_EX);
+}
+
+UpdateState SnapshotManager::ReadUpdateState(LockedFile* file) {
+ // Reset position since some calls read+write.
+ if (lseek(file->fd(), 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek state file failed";
+ return UpdateState::None;
+ }
+
+ std::string contents;
+ if (!android::base::ReadFdToString(file->fd(), &contents)) {
+ PLOG(ERROR) << "Read state file failed";
+ return UpdateState::None;
+ }
+
+ if (contents.empty() || contents == "none") {
+ return UpdateState::None;
+ } else if (contents == "initiated") {
+ return UpdateState::Initiated;
+ } else if (contents == "unverified") {
+ return UpdateState::Unverified;
+ } else if (contents == "merging") {
+ return UpdateState::Merging;
+ } else if (contents == "merge-completed") {
+ return UpdateState::MergeCompleted;
+ } else if (contents == "merge-needs-reboot") {
+ return UpdateState::MergeNeedsReboot;
+ } else if (contents == "merge-failed") {
+ return UpdateState::MergeFailed;
+ } else {
+ LOG(ERROR) << "Unknown merge state in update state file";
+ return UpdateState::None;
+ }
+}
+
+bool SnapshotManager::WriteUpdateState(LockedFile* file, UpdateState state) {
+ std::string contents;
+ switch (state) {
+ case UpdateState::None:
+ contents = "none";
+ break;
+ case UpdateState::Initiated:
+ contents = "initiated";
+ break;
+ case UpdateState::Unverified:
+ contents = "unverified";
+ break;
+ case UpdateState::Merging:
+ contents = "merging";
+ break;
+ case UpdateState::MergeCompleted:
+ contents = "merge-completed";
+ break;
+ case UpdateState::MergeNeedsReboot:
+ contents = "merge-needs-reboot";
+ break;
+ case UpdateState::MergeFailed:
+ contents = "merge-failed";
+ break;
+ default:
+ LOG(ERROR) << "Unknown update state";
+ return false;
+ }
+
+ if (!Truncate(file)) return false;
+ if (!android::base::WriteStringToFd(contents, file->fd())) {
+ PLOG(ERROR) << "Could not write to state file";
+ return false;
+ }
+ return true;
+}
+
+std::string SnapshotManager::GetSnapshotStatusFilePath(const std::string& name) {
+ auto file = metadata_dir_ + "/snapshots/"s + name;
+ return file;
+}
+
+bool SnapshotManager::ReadSnapshotStatus(LockedFile* lock, const std::string& name,
+ SnapshotStatus* status) {
+ CHECK(lock);
+ auto path = GetSnapshotStatusFilePath(name);
+
+ unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+ if (fd < 0) {
+ PLOG(ERROR) << "Open failed: " << path;
+ return false;
+ }
+
+ std::string contents;
+ if (!android::base::ReadFdToString(fd, &contents)) {
+ PLOG(ERROR) << "read failed: " << path;
+ return false;
+ }
+ auto pieces = android::base::Split(contents, " ");
+ if (pieces.size() != 5) {
+ LOG(ERROR) << "Invalid status line for snapshot: " << path;
+ return false;
+ }
+
+ status->state = pieces[0];
+ if (!android::base::ParseUint(pieces[1], &status->device_size)) {
+ LOG(ERROR) << "Invalid device size in status line for: " << path;
+ return false;
+ }
+ if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) {
+ LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
+ return false;
+ }
+ if (!android::base::ParseUint(pieces[3], &status->sectors_allocated)) {
+ LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
+ return false;
+ }
+ if (!android::base::ParseUint(pieces[4], &status->metadata_sectors)) {
+ LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const std::string& name,
+ const SnapshotStatus& status) {
+ // The caller must take an exclusive lock to modify snapshots.
+ CHECK(lock);
+ CHECK(lock->lock_mode() == LOCK_EX);
+
+ auto path = GetSnapshotStatusFilePath(name);
+ unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC, 0660));
+ if (fd < 0) {
+ PLOG(ERROR) << "Open failed: " << path;
+ return false;
+ }
+
+ std::vector<std::string> pieces = {
+ status.state,
+ std::to_string(status.device_size),
+ std::to_string(status.snapshot_size),
+ std::to_string(status.sectors_allocated),
+ std::to_string(status.metadata_sectors),
+ };
+ auto contents = android::base::Join(pieces, " ");
+
+ if (!android::base::WriteStringToFd(contents, fd)) {
+ PLOG(ERROR) << "write failed: " << path;
+ return false;
+ }
+ return true;
+}
+
+bool SnapshotManager::Truncate(LockedFile* file) {
+ if (lseek(file->fd(), 0, SEEK_SET) < 0) {
+ PLOG(ERROR) << "lseek file failed: " << file->path();
+ return false;
+ }
+ if (ftruncate(file->fd(), 0) < 0) {
+ PLOG(ERROR) << "truncate failed: " << file->path();
+ return false;
+ }
+ return true;
+}
+
+std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name,
+ const SnapshotStatus& status) {
+ if (status.device_size != status.snapshot_size) {
+ return snapshot_name + "-inner";
+ }
+ return snapshot_name;
+}
+
+bool SnapshotManager::EnsureImageManager() {
+ if (images_) return true;
+
+ // For now, use a preset timeout.
+ images_ = android::fiemap::IImageManager::Open(gsid_dir_, 15000ms);
+ if (!images_) {
+ LOG(ERROR) << "Could not open ImageManager";
+ return false;
+ }
+ return true;
}
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
new file mode 100644
index 0000000..4903224
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -0,0 +1,339 @@
+// 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 <libsnapshot/snapshot.h>
+
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <iostream>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using namespace std::chrono_literals;
+using namespace std::string_literals;
+
+class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
+ public:
+ std::string GetGsidDir() const override { return "ota/test"s; }
+ std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
+ bool IsRunningSnapshot() const override { return is_running_snapshot_; }
+
+ void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; }
+
+ private:
+ bool is_running_snapshot_;
+};
+
+std::unique_ptr<SnapshotManager> sm;
+TestDeviceInfo* test_device = nullptr;
+
+class SnapshotTest : public ::testing::Test {
+ public:
+ SnapshotTest() : dm_(DeviceMapper::Instance()) {}
+
+ protected:
+ void SetUp() override {
+ test_device->set_is_running_snapshot(false);
+
+ if (sm->GetUpdateState() != UpdateState::None) {
+ CleanupTestArtifacts();
+ }
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->EnsureImageManager());
+
+ image_manager_ = sm->image_manager();
+ ASSERT_NE(image_manager_, nullptr);
+ }
+
+ void TearDown() override {
+ lock_ = nullptr;
+
+ CleanupTestArtifacts();
+ }
+
+ void CleanupTestArtifacts() {
+ // Normally cancelling inside a merge is not allowed. Since these
+ // are tests, we don't care, destroy everything that might exist.
+ std::vector<std::string> snapshots = {"test-snapshot"};
+ for (const auto& snapshot : snapshots) {
+ DeleteSnapshotDevice(snapshot);
+ temp_images_.emplace_back(snapshot + "-cow");
+
+ auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
+ android::base::RemoveFileIfExists(status_file);
+ }
+
+ // Remove all images.
+ temp_images_.emplace_back("test-snapshot-cow");
+ for (const auto& temp_image : temp_images_) {
+ image_manager_->UnmapImageDevice(temp_image);
+ image_manager_->DeleteBackingImage(temp_image);
+ }
+
+ if (sm->GetUpdateState() != UpdateState::None) {
+ auto state_file = sm->GetStateFilePath();
+ unlink(state_file.c_str());
+ }
+ }
+
+ bool AcquireLock() {
+ lock_ = sm->OpenStateFile(O_RDWR, LOCK_EX);
+ return !!lock_;
+ }
+
+ bool CreateTempDevice(const std::string& name, uint64_t size, std::string* path) {
+ if (!image_manager_->CreateBackingImage(name, size, false)) {
+ return false;
+ }
+ temp_images_.emplace_back(name);
+ return image_manager_->MapImageDevice(name, 10s, path);
+ }
+
+ bool DeleteSnapshotDevice(const std::string& snapshot) {
+ if (dm_.GetState(snapshot) != DmDeviceState::INVALID) {
+ if (!dm_.DeleteDevice(snapshot)) return false;
+ }
+ if (dm_.GetState(snapshot + "-inner") != DmDeviceState::INVALID) {
+ if (!dm_.DeleteDevice(snapshot + "-inner")) return false;
+ }
+ return true;
+ }
+
+ DeviceMapper& dm_;
+ std::unique_ptr<SnapshotManager::LockedFile> lock_;
+ std::vector<std::string> temp_images_;
+ android::fiemap::IImageManager* image_manager_ = nullptr;
+};
+
+TEST_F(SnapshotTest, CreateSnapshot) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kDeviceSize = 1024 * 1024;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+ kDeviceSize));
+
+ std::vector<std::string> snapshots;
+ ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
+ ASSERT_EQ(snapshots.size(), 1);
+ ASSERT_EQ(snapshots[0], "test-snapshot");
+
+ // Scope so delete can re-acquire the snapshot file lock.
+ {
+ SnapshotManager::SnapshotStatus status;
+ ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status));
+ ASSERT_EQ(status.state, "created");
+ ASSERT_EQ(status.device_size, kDeviceSize);
+ ASSERT_EQ(status.snapshot_size, kDeviceSize);
+ }
+
+ ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
+ ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot"));
+}
+
+TEST_F(SnapshotTest, MapSnapshot) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kDeviceSize = 1024 * 1024;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+ kDeviceSize));
+
+ std::string base_device;
+ ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+
+ std::string snap_device;
+ ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+ ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
+}
+
+TEST_F(SnapshotTest, MapPartialSnapshot) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kSnapshotSize = 1024 * 1024;
+ static const uint64_t kDeviceSize = 1024 * 1024 * 2;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kSnapshotSize,
+ kSnapshotSize));
+
+ std::string base_device;
+ ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+
+ std::string snap_device;
+ ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+ ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
+}
+
+TEST_F(SnapshotTest, NoMergeBeforeReboot) {
+ ASSERT_TRUE(AcquireLock());
+
+ // Set the state to Unverified, as if we finished an update.
+ ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
+
+ // Release the lock.
+ lock_ = nullptr;
+
+ // Merge should fail, since we didn't mark the device as rebooted.
+ ASSERT_FALSE(sm->InitiateMerge());
+}
+
+TEST_F(SnapshotTest, Merge) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kDeviceSize = 1024 * 1024;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+ kDeviceSize));
+
+ std::string base_device, snap_device;
+ ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+ ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+
+ std::string test_string = "This is a test string.";
+ {
+ unique_fd fd(open(snap_device.c_str(), O_RDWR | O_CLOEXEC | O_SYNC));
+ ASSERT_GE(fd, 0);
+ ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size()));
+ }
+
+ // Note: we know the name of the device is test-snapshot because we didn't
+ // request a linear segment.
+ DeviceMapper::TargetInfo target;
+ ASSERT_TRUE(sm->IsSnapshotDevice("test-snapshot", &target));
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+ // Set the state to Unverified, as if we finished an update.
+ ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
+
+ // Release the lock.
+ lock_ = nullptr;
+
+ test_device->set_is_running_snapshot(true);
+ ASSERT_TRUE(sm->InitiateMerge());
+
+ // The device should have been switched to a snapshot-merge target.
+ ASSERT_TRUE(sm->IsSnapshotDevice("test-snapshot", &target));
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+
+ // We should not be able to cancel an update now.
+ ASSERT_FALSE(sm->CancelUpdate());
+
+ ASSERT_EQ(sm->WaitForMerge(), UpdateState::MergeCompleted);
+ ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
+
+ // The device should no longer be a snapshot or snapshot-merge.
+ ASSERT_FALSE(sm->IsSnapshotDevice("test-snapshot"));
+
+ // Test that we can read back the string we wrote to the snapshot.
+ unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_GE(fd, 0);
+
+ std::string buffer(test_string.size(), '\0');
+ ASSERT_TRUE(android::base::ReadFully(fd, buffer.data(), buffer.size()));
+ ASSERT_EQ(test_string, buffer);
+}
+
+TEST_F(SnapshotTest, MergeCannotRemoveCow) {
+ ASSERT_TRUE(AcquireLock());
+
+ static const uint64_t kDeviceSize = 1024 * 1024;
+ ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot", kDeviceSize, kDeviceSize,
+ kDeviceSize));
+
+ std::string base_device, snap_device;
+ ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+ ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+
+ // Keep an open handle to the cow device. This should cause the merge to
+ // be incomplete.
+ auto cow_path = android::base::GetProperty("gsid.mapped_image.test-snapshot-cow", "");
+ unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC));
+ ASSERT_GE(fd, 0);
+
+ // Set the state to Unverified, as if we finished an update.
+ ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
+
+ // Release the lock.
+ lock_ = nullptr;
+
+ test_device->set_is_running_snapshot(true);
+ ASSERT_TRUE(sm->InitiateMerge());
+
+ // COW cannot be removed due to open fd, so expect a soft failure.
+ ASSERT_EQ(sm->WaitForMerge(), UpdateState::MergeNeedsReboot);
+
+ // Forcefully delete the snapshot device, so it looks like we just rebooted.
+ ASSERT_TRUE(DeleteSnapshotDevice("test-snapshot"));
+
+ // Map snapshot should fail now, because we're in a merge-complete state.
+ ASSERT_TRUE(AcquireLock());
+ ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
+
+ // Release everything and now the merge should complete.
+ fd = {};
+ lock_ = nullptr;
+
+ ASSERT_EQ(sm->WaitForMerge(), UpdateState::MergeCompleted);
+}
+
+} // namespace snapshot
+} // namespace android
+
+using namespace android::snapshot;
+
+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);
+
+ std::vector<std::string> paths = {
+ "/data/gsi/ota/test",
+ "/metadata/gsi/ota/test",
+ "/metadata/ota/test",
+ "/metadata/ota/test/snapshots",
+ };
+ for (const auto& path : paths) {
+ if (!Mkdir(path)) {
+ return 1;
+ }
+ }
+
+ // Create this once, otherwise, gsid will start/stop between each test.
+ test_device = new TestDeviceInfo();
+ sm = SnapshotManager::New(test_device);
+ if (!sm) {
+ std::cerr << "Could not create snapshot manager";
+ return 1;
+ }
+
+ return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index c2a0f33..397d8e5 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -15,10 +15,13 @@
adb remount tests
---help This help
---serial Specify device (must if multiple are present)
---color Dress output with highlighting colors
---print-time Report the test duration
+--color Dress output with highlighting colors
+--help This help
+--no-wait-screen Do not wait for display screen to settle
+--print-time Report the test duration
+--serial Specify device (must if multiple are present)
+--wait-adb <duration> adb wait timeout
+--wait-fastboot <duration> fastboot wait timeout
Conditions:
- Must be a userdebug build.
@@ -53,6 +56,7 @@
ADB_WAIT=4m
FASTBOOT_WAIT=2m
+screen_wait=true
##
## Helper Functions
@@ -185,7 +189,7 @@
[ "USAGE: adb_cat <file> >stdout
Returns: content of file to stdout with carriage returns skipped,
- true of the file exists" ]
+ true if the file exists" ]
adb_cat() {
local OUTPUT="`adb_sh cat ${1} </dev/null 2>&1`"
local ret=${?}
@@ -193,6 +197,17 @@
return ${ret}
}
+[ "USAGE: adb_ls <dirfile> >stdout
+
+Returns: filename or directoru content to stdout with carriage returns skipped,
+ true if the ls had no errors" ]
+adb_ls() {
+ local OUTPUT="`adb_sh ls ${1} </dev/null 2>/dev/null`"
+ local ret=${?}
+ echo "${OUTPUT}" | tr -d '\r'
+ return ${ret}
+}
+
[ "USAGE: adb_reboot
Returns: true if the reboot command succeeded" ]
@@ -436,6 +451,10 @@
-n - echo newline at exit
TIMEOUT - default `format_duration ${wait_for_screen_timeout}`" ]
wait_for_screen() {
+ if ! ${screen_wait}; then
+ adb_wait
+ return
+ fi
exit_function=true
if [ X"-n" = X"${1}" ]; then
exit_function=echo
@@ -743,6 +762,9 @@
OPTIONS=`getopt --alternative --unquoted \
--longoptions help,serial:,colour,color,no-colour,no-color \
+ --longoptions wait-adb:,wait-fastboot: \
+ --longoptions wait-screen,wait-display \
+ --longoptions no-wait-screen,no-wait-display \
--longoptions gtest_print_time,print-time \
-- "?hs:" ${*}` ||
( echo "${USAGE}" >&2 ; false ) ||
@@ -766,9 +788,23 @@
--no-color | --no-colour)
color=false
;;
+ --no-wait-display | --no-wait-screen)
+ screen_wait=false
+ ;;
+ --wait-display | --wait-screen)
+ screen_wait=true
+ ;;
--print-time | --gtest_print_time)
print_time=true
;;
+ --wait-adb)
+ ADB_WAIT=${2}
+ shift
+ ;;
+ --wait-fastboot)
+ FASTBOOT_WAIT=${2}
+ shift
+ ;;
--)
shift
break
@@ -854,14 +890,37 @@
# If reboot too soon after fresh flash, could trip device update failure logic
wait_for_screen
# Can we test remount -R command?
+OVERLAYFS_BACKING="cache mnt/scratch"
overlayfs_supported=true
-if [ "orange" = "`get_property ro.boot.verifiedbootstate`" -a \
- "2" = "`get_property partition.system.verified`" ]; then
+if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
+ "2" != "`get_property partition.system.verified`" ]; then
restore() {
${overlayfs_supported} || return 0
inFastboot &&
fastboot reboot &&
- adb_wait ${ADB_WAIT}
+ adb_wait ${ADB_WAIT} ||
+ true
+ if inAdb; then
+ reboot=false
+ for d in ${OVERLAYFS_BACKING}; do
+ if adb_su ls -d /${d}/overlay </dev/null >/dev/null 2>/dev/null; then
+ adb_su rm -rf /${d}/overlay </dev/null
+ reboot=true
+ fi
+ done
+ if ${reboot}; then
+ adb_reboot &&
+ adb_wait ${ADB_WAIT}
+ fi
+ fi
+ }
+else
+ restore() {
+ ${overlayfs_supported} || return 0
+ inFastboot &&
+ fastboot reboot &&
+ adb_wait ${ADB_WAIT} ||
+ true
inAdb &&
adb_root &&
adb enable-verity >/dev/null 2>/dev/null &&
@@ -920,7 +979,6 @@
# So lets do our best to surgically wipe the overlayfs state without
# having to go through enable-verity transition.
reboot=false
-OVERLAYFS_BACKING="cache mnt/scratch"
for d in ${OVERLAYFS_BACKING}; do
if adb_sh ls -d /${d}/overlay </dev/null >/dev/null 2>/dev/null; then
echo "${ORANGE}[ WARNING ]${NORMAL} /${d}/overlay is setup, surgically wiping" >&2
@@ -1145,10 +1203,14 @@
A="Hello World! $(date)"
echo "${A}" | adb_sh cat - ">/system/hello"
+echo "${A}" | adb_sh cat - ">/system/priv-app/hello"
echo "${A}" | adb_sh cat - ">/vendor/hello"
B="`adb_cat /system/hello`" ||
- die "sytem hello"
+ die "system hello"
check_eq "${A}" "${B}" /system before reboot
+B="`adb_cat /system/priv-app/hello`" ||
+ die "system priv-app hello"
+check_eq "${A}" "${B}" /system/priv-app before reboot
B="`adb_cat /vendor/hello`" ||
die "vendor hello"
check_eq "${A}" "${B}" /vendor before reboot
@@ -1230,6 +1292,13 @@
fi
B="`adb_cat /system/hello`"
check_eq "${A}" "${B}" /system after reboot
+# If overlayfs has a nested security problem, this will fail.
+B="`adb_ls /system/`" ||
+ dir "adb ls /system"
+[ X"${B}" != X"${B#*priv-app}" ] ||
+ dir "adb ls /system/priv-app"
+B="`adb_cat /system/priv-app/hello`"
+check_eq "${A}" "${B}" /system/priv-app after reboot
echo "${GREEN}[ OK ]${NORMAL} /system content remains after reboot" >&2
# Only root can read vendor if sepolicy permissions are as expected.
adb_root ||
@@ -1351,6 +1420,12 @@
fi
B="`adb_cat /system/hello`"
check_eq "${A}" "${B}" system after flash vendor
+ B="`adb_ls /system/`" ||
+ dir "adb ls /system"
+ [ X"${B}" != X"${B#*priv-app}" ] ||
+ dir "adb ls /system/priv-app"
+ B="`adb_cat /system/priv-app/hello`"
+ check_eq "${A}" "${B}" system/priv-app after flash vendor
adb_root ||
die "adb root"
B="`adb_cat /vendor/hello`"
@@ -1392,15 +1467,17 @@
echo "${H}"
[ ${err} = 0 ] &&
( adb_sh rm /vendor/hello </dev/null 2>/dev/null || true ) &&
- adb_sh rm /system/hello </dev/null ||
+ adb_sh rm /system/hello /system/priv-app/hello </dev/null ||
( [ -n "${L}" ] && echo "${L}" && false ) ||
die -t ${T} "cleanup hello"
B="`adb_cat /system/hello`"
check_eq "cat: /system/hello: No such file or directory" "${B}" after rm
+B="`adb_cat /system/priv-app/hello`"
+check_eq "cat: /system/priv-app/hello: No such file or directory" "${B}" after rm
B="`adb_cat /vendor/hello`"
check_eq "cat: /vendor/hello: No such file or directory" "${B}" after rm
-if [ -n "${scratch_partition}" ]; then
+if ${is_bootloader_fastboot} && [ -n "${scratch_partition}" ]; then
echo "${GREEN}[ RUN ]${NORMAL} test fastboot flash to ${scratch_partition} recovery" >&2
@@ -1413,7 +1490,7 @@
}
dd if=/dev/zero of=${img} bs=4096 count=16 2>/dev/null &&
fastboot_wait ${FASTBOOT_WAIT} ||
- die "reboot into fastboot `usb_status`"
+ die "reboot into fastboot to flash scratch `usb_status`"
fastboot flash --force ${scratch_partition} ${img}
err=${?}
cleanup
@@ -1542,7 +1619,9 @@
adb_wait ${ADB_WAIT} ||
die "adb remount -R"
if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
- "2" = "`get_property partition.system.verified`" ]; then
+ "2" = "`get_property partition.system.verified`" ] &&
+ [ -n "`get_property ro.boot.verifiedbootstate`" -o \
+ -n "`get_property partition.system.verified`" ]; then
die "remount -R command failed to disable verity"
fi
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 7e6ad5b..2738457 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -49,7 +49,10 @@
std::cerr << " delete <dm-name>" << std::endl;
std::cerr << " list <devices | targets> [-v]" << std::endl;
std::cerr << " getpath <dm-name>" << std::endl;
+ std::cerr << " info <dm-name>" << std::endl;
std::cerr << " status <dm-name>" << std::endl;
+ std::cerr << " resume <dm-name>" << std::endl;
+ std::cerr << " suspend <dm-name>" << std::endl;
std::cerr << " table <dm-name>" << std::endl;
std::cerr << " help" << std::endl;
std::cerr << std::endl;
@@ -194,19 +197,12 @@
char** argv_;
};
-static int DmCreateCmdHandler(int argc, char** argv) {
- if (argc < 1) {
- std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
- return -EINVAL;
- }
- std::string name = argv[0];
-
+static bool parse_table_args(DmTable* table, int argc, char** argv) {
// Parse extended options first.
- DmTable table;
int arg_index = 1;
while (arg_index < argc && argv[arg_index][0] == '-') {
if (strcmp(argv[arg_index], "-ro") == 0) {
- table.set_readonly(true);
+ table->set_readonly(true);
arg_index++;
} else {
std::cerr << "Unrecognized option: " << argv[arg_index] << std::endl;
@@ -218,15 +214,30 @@
TargetParser parser(argc - arg_index, argv + arg_index);
while (parser.More()) {
std::unique_ptr<DmTarget> target = parser.Next();
- if (!target || !table.AddTarget(std::move(target))) {
+ if (!target || !table->AddTarget(std::move(target))) {
return -EINVAL;
}
}
- if (table.num_targets() == 0) {
+ if (table->num_targets() == 0) {
std::cerr << "Must define at least one target." << std::endl;
return -EINVAL;
}
+ return 0;
+}
+
+static int DmCreateCmdHandler(int argc, char** argv) {
+ if (argc < 1) {
+ std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
+ return -EINVAL;
+ }
+ std::string name = argv[0];
+
+ DmTable table;
+ int ret = parse_table_args(&table, argc, argv);
+ if (ret) {
+ return ret;
+ }
DeviceMapper& dm = DeviceMapper::Instance();
if (!dm.CreateDevice(name, table)) {
@@ -252,6 +263,27 @@
return 0;
}
+static int DmReplaceCmdHandler(int argc, char** argv) {
+ if (argc < 1) {
+ std::cerr << "Usage: dmctl replace <dm-name> <targets...>" << std::endl;
+ return -EINVAL;
+ }
+ std::string name = argv[0];
+
+ DmTable table;
+ int ret = parse_table_args(&table, argc, argv);
+ if (ret) {
+ return ret;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ if (!dm.LoadTableAndActivate(name, table)) {
+ std::cerr << "Failed to replace device-mapper table to: " << name << std::endl;
+ return -EIO;
+ }
+ return 0;
+}
+
static int DmListTargets(DeviceMapper& dm, [[maybe_unused]] int argc,
[[maybe_unused]] char** argv) {
std::vector<DmTargetTypeInfo> targets;
@@ -357,6 +389,41 @@
return 0;
}
+static int InfoCmdHandler(int argc, char** argv) {
+ if (argc != 1) {
+ std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+ return -EINVAL;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ auto info = dm.GetDetailedInfo(argv[0]);
+ if (!info) {
+ std::cerr << "Invalid device \"" << argv[0] << "\"." << std::endl;
+ return -EINVAL;
+ }
+
+ constexpr int spacing = 14;
+ std::cout << std::left << std::setw(spacing) << "device"
+ << ": " << argv[0] << std::endl;
+ std::cout << std::left << std::setw(spacing) << "active"
+ << ": " << std::boolalpha << !info->IsSuspended() << std::endl;
+ std::cout << std::left << std::setw(spacing) << "access"
+ << ": ";
+ if (info->IsReadOnly()) {
+ std::cout << "ro ";
+ } else {
+ std::cout << "rw ";
+ }
+ std::cout << std::endl;
+ std::cout << std::left << std::setw(spacing) << "activeTable"
+ << ": " << std::boolalpha << info->IsActiveTablePresent() << std::endl;
+ std::cout << std::left << std::setw(spacing) << "inactiveTable"
+ << ": " << std::boolalpha << info->IsInactiveTablePresent() << std::endl;
+ std::cout << std::left << std::setw(spacing) << "bufferFull"
+ << ": " << std::boolalpha << info->IsBufferFull() << std::endl;
+ return 0;
+}
+
static int DumpTable(const std::string& mode, int argc, char** argv) {
if (argc != 1) {
std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
@@ -399,15 +466,47 @@
return DumpTable("status", argc, argv);
}
+static int ResumeCmdHandler(int argc, char** argv) {
+ if (argc != 1) {
+ std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+ return -EINVAL;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ if (!dm.ChangeState(argv[0], DmDeviceState::ACTIVE)) {
+ std::cerr << "Could not resume device \"" << argv[0] << "\"." << std::endl;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int SuspendCmdHandler(int argc, char** argv) {
+ if (argc != 1) {
+ std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+ return -EINVAL;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ if (!dm.ChangeState(argv[0], DmDeviceState::SUSPENDED)) {
+ std::cerr << "Could not suspend device \"" << argv[0] << "\"." << std::endl;
+ return -EINVAL;
+ }
+ return 0;
+}
+
static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
// clang-format off
{"create", DmCreateCmdHandler},
{"delete", DmDeleteCmdHandler},
+ {"replace", DmReplaceCmdHandler},
{"list", DmListCmdHandler},
{"help", HelpCmdHandler},
{"getpath", GetPathCmdHandler},
+ {"info", InfoCmdHandler},
{"table", TableCmdHandler},
{"status", StatusCmdHandler},
+ {"resume", ResumeCmdHandler},
+ {"suspend", SuspendCmdHandler},
// clang-format on
};
diff --git a/init/Android.bp b/init/Android.bp
index ba60085..3233cc3 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -83,6 +83,7 @@
"libfscrypt",
"libgsi",
"libhidl-gen-utils",
+ "libjsoncpp",
"libkeyutils",
"liblog",
"liblogwrap",
@@ -93,12 +94,16 @@
"libutils",
],
bootstrap: true,
+ visibility: [":__subpackages__"],
}
cc_library_static {
name: "libinit",
recovery_available: true,
- defaults: ["init_defaults", "selinux_policy_version"],
+ defaults: [
+ "init_defaults",
+ "selinux_policy_version",
+ ],
srcs: [
"action.cpp",
"action_manager.cpp",
@@ -114,6 +119,7 @@
"first_stage_mount.cpp",
"import_parser.cpp",
"init.cpp",
+ "interface_utils.cpp",
"keychords.cpp",
"modalias_handler.cpp",
"mount_handler.cpp",
@@ -143,7 +149,10 @@
"ueventd_parser.cpp",
"util.cpp",
],
- whole_static_libs: ["libcap", "com.android.sysprop.apex"],
+ whole_static_libs: [
+ "libcap",
+ "com.android.sysprop.apex",
+ ],
header_libs: ["bootimg_headers"],
proto: {
type: "lite",
@@ -153,7 +162,10 @@
target: {
recovery: {
cflags: ["-DRECOVERY"],
- exclude_shared_libs: ["libbinder", "libutils"],
+ exclude_shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
},
},
}
@@ -182,7 +194,10 @@
target: {
recovery: {
cflags: ["-DRECOVERY"],
- exclude_shared_libs: ["libbinder", "libutils"],
+ exclude_shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
},
},
}
@@ -227,9 +242,13 @@
genrule {
name: "generated_stub_builtin_function_map",
+ tool_files: ["host_builtin_map.py"],
out: ["generated_stub_builtin_function_map.h"],
- srcs: ["builtins.cpp"],
- cmd: "sed -n '/Builtin-function-map start/{:a;n;/Builtin-function-map end/q;p;ba}' $(in) | sed -e 's/do_[^}]*/do_stub/g' > $(out)",
+ srcs: [
+ "builtins.cpp",
+ "check_builtins.cpp",
+ ],
+ cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location check_builtins.cpp) > $(out)",
}
cc_binary {
@@ -260,9 +279,11 @@
"action_manager.cpp",
"action_parser.cpp",
"capabilities.cpp",
+ "check_builtins.cpp",
"epoll.cpp",
"keychords.cpp",
"import_parser.cpp",
+ "interface_utils.cpp",
"host_import_parser.cpp",
"host_init_verifier.cpp",
"parser.cpp",
@@ -281,7 +302,7 @@
},
generated_headers: [
"generated_stub_builtin_function_map",
- "generated_android_ids"
+ "generated_android_ids",
],
target: {
android: {
diff --git a/init/action.cpp b/init/action.cpp
index 0c476df..69e40d0 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -35,9 +35,11 @@
builtin_arguments.args.resize(args.size());
builtin_arguments.args[0] = args[0];
for (std::size_t i = 1; i < args.size(); ++i) {
- if (!expand_props(args[i], &builtin_arguments.args[i])) {
- return Error() << "cannot expand '" << args[i] << "'";
+ auto expanded_arg = ExpandProps(args[i]);
+ if (!expanded_arg) {
+ return expanded_arg.error();
}
+ builtin_arguments.args[i] = std::move(*expanded_arg);
}
return function(builtin_arguments);
@@ -66,6 +68,30 @@
return RunBuiltinFunction(func_, args_, kInitContext);
}
+Result<void> Command::CheckCommand() const {
+ auto builtin_arguments = BuiltinArguments("host_init_verifier");
+
+ builtin_arguments.args.resize(args_.size());
+ builtin_arguments.args[0] = args_[0];
+ for (size_t i = 1; i < args_.size(); ++i) {
+ auto expanded_arg = ExpandProps(args_[i]);
+ if (!expanded_arg) {
+ if (expanded_arg.error().message().find("doesn't exist while expanding") !=
+ std::string::npos) {
+ // If we failed because we won't have a property, use an empty string, which is
+ // never returned from the parser, to indicate that this field cannot be checked.
+ builtin_arguments.args[i] = "";
+ } else {
+ return expanded_arg.error();
+ }
+ } else {
+ builtin_arguments.args[i] = std::move(*expanded_arg);
+ }
+ }
+
+ return func_(builtin_arguments);
+}
+
std::string Command::BuildCommandString() const {
return Join(args_, ' ');
}
@@ -105,6 +131,18 @@
return commands_.size();
}
+size_t Action::CheckAllCommands() const {
+ size_t failures = 0;
+ for (const auto& command : commands_) {
+ if (auto result = command.CheckCommand(); !result) {
+ LOG(ERROR) << "Command '" << command.BuildCommandString() << "' (" << filename_ << ":"
+ << command.line() << ") failed: " << result.error();
+ ++failures;
+ }
+ }
+ return failures;
+}
+
void Action::ExecuteOneCommand(std::size_t command) const {
// We need a copy here since some Command execution may result in
// changing commands_ vector by importing .rc files through parser
@@ -123,18 +161,8 @@
auto result = command.InvokeFunc(subcontext_);
auto duration = t.duration();
- // There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
- // device, such as '/sys/class/leds/jogball-backlight/brightness'. As of this writing, there
- // are 198 such failures on bullhead. Instead of spamming the log reporting them, we do not
- // report such failures unless we're running at the DEBUG log level.
- bool report_failure = !result.has_value();
- if (report_failure && android::base::GetMinimumLogSeverity() > android::base::DEBUG &&
- result.error().code() == ENOENT) {
- report_failure = false;
- }
-
// Any action longer than 50ms will be warned to user as slow operation
- if (report_failure || duration > 50ms ||
+ if (!result.has_value() || duration > 50ms ||
android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
std::string trigger_name = BuildTriggersString();
std::string cmd_str = command.BuildCommandString();
@@ -167,10 +195,11 @@
found = true;
}
} else {
- std::string prop_val = android::base::GetProperty(trigger_name, "");
- if (prop_val.empty() || (trigger_value != "*" && trigger_value != prop_val)) {
- return false;
+ std::string prop_value = android::base::GetProperty(trigger_name, "");
+ if (trigger_value == "*" && !prop_value.empty()) {
+ continue;
}
+ if (trigger_value != prop_value) return false;
}
}
return found;
diff --git a/init/action.h b/init/action.h
index 80c1da4..1534bf9 100644
--- a/init/action.h
+++ b/init/action.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _INIT_ACTION_H
-#define _INIT_ACTION_H
+#pragma once
#include <map>
#include <queue>
@@ -41,6 +40,7 @@
Result<void> InvokeFunc(Subcontext* subcontext) const;
std::string BuildCommandString() const;
+ Result<void> CheckCommand() const;
int line() const { return line_; }
@@ -63,7 +63,7 @@
Result<void> AddCommand(std::vector<std::string>&& args, int line);
void AddCommand(BuiltinFunction f, std::vector<std::string>&& args, int line);
- std::size_t NumCommands() const;
+ size_t NumCommands() const;
void ExecuteOneCommand(std::size_t command) const;
void ExecuteAllCommands() const;
bool CheckEvent(const EventTrigger& event_trigger) const;
@@ -71,6 +71,7 @@
bool CheckEvent(const BuiltinAction& builtin_action) const;
std::string BuildTriggersString() const;
void DumpState() const;
+ size_t CheckAllCommands() const;
bool oneshot() const { return oneshot_; }
const std::string& filename() const { return filename_; }
@@ -96,5 +97,3 @@
} // namespace init
} // namespace android
-
-#endif
diff --git a/init/action_manager.cpp b/init/action_manager.cpp
index 541c8f2..ebca762 100644
--- a/init/action_manager.cpp
+++ b/init/action_manager.cpp
@@ -23,6 +23,14 @@
ActionManager::ActionManager() : current_command_(0) {}
+size_t ActionManager::CheckAllCommands() {
+ size_t failures = 0;
+ for (const auto& action : actions_) {
+ failures += action->CheckAllCommands();
+ }
+ return failures;
+}
+
ActionManager& ActionManager::GetInstance() {
static ActionManager instance;
return instance;
diff --git a/init/action_manager.h b/init/action_manager.h
index 5f47a6d..a2b95ac 100644
--- a/init/action_manager.h
+++ b/init/action_manager.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _INIT_ACTION_MANAGER_H
-#define _INIT_ACTION_MANAGER_H
+#pragma once
#include <string>
#include <vector>
@@ -32,6 +31,7 @@
// Exposed for testing
ActionManager();
+ size_t CheckAllCommands();
void AddAction(std::unique_ptr<Action> action);
void QueueEventTrigger(const std::string& trigger);
@@ -55,5 +55,3 @@
} // namespace init
} // namespace android
-
-#endif
diff --git a/init/builtins.cpp b/init/builtins.cpp
index b6e26a1..a2d782b 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -89,6 +89,43 @@
namespace android {
namespace init {
+// There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
+// device, such as '/sys/class/leds/jogball-backlight/brightness'. As of this writing, there
+// are 81 such failures on cuttlefish. Instead of spamming the log reporting them, we do not
+// report such failures unless we're running at the DEBUG log level.
+class ErrorIgnoreEnoent {
+ public:
+ ErrorIgnoreEnoent()
+ : ignore_error_(errno == ENOENT &&
+ android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+ explicit ErrorIgnoreEnoent(int errno_to_append)
+ : error_(errno_to_append),
+ ignore_error_(errno_to_append == ENOENT &&
+ android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
+
+ template <typename T>
+ operator android::base::expected<T, ResultError>() {
+ if (ignore_error_) {
+ return {};
+ }
+ return error_;
+ }
+
+ template <typename T>
+ ErrorIgnoreEnoent& operator<<(T&& t) {
+ error_ << t;
+ return *this;
+ }
+
+ private:
+ Error error_;
+ bool ignore_error_;
+};
+
+inline ErrorIgnoreEnoent ErrnoErrorIgnoreEnoent() {
+ return ErrorIgnoreEnoent(errno);
+}
+
std::vector<std::string> late_import_paths;
static constexpr std::chrono::nanoseconds kCommandRetryTimeout = 5s;
@@ -201,26 +238,26 @@
static Result<void> do_exec(const BuiltinArguments& args) {
auto service = Service::MakeTemporaryOneshotService(args.args);
if (!service) {
- return Error() << "Could not create exec service";
+ return Error() << "Could not create exec service: " << service.error();
}
- if (auto result = service->ExecStart(); !result) {
+ if (auto result = (*service)->ExecStart(); !result) {
return Error() << "Could not start exec service: " << result.error();
}
- ServiceList::GetInstance().AddService(std::move(service));
+ ServiceList::GetInstance().AddService(std::move(*service));
return {};
}
static Result<void> do_exec_background(const BuiltinArguments& args) {
auto service = Service::MakeTemporaryOneshotService(args.args);
if (!service) {
- return Error() << "Could not create exec background service";
+ return Error() << "Could not create exec background service: " << service.error();
}
- if (auto result = service->Start(); !result) {
+ if (auto result = (*service)->Start(); !result) {
return Error() << "Could not start exec background service: " << result.error();
}
- ServiceList::GetInstance().AddService(std::move(service));
+ ServiceList::GetInstance().AddService(std::move(*service));
return {};
}
@@ -330,7 +367,7 @@
return ErrnoError() << "fchmodat() failed";
}
} else {
- return ErrnoError() << "mkdir() failed";
+ return ErrnoErrorIgnoreEnoent() << "mkdir() failed";
}
}
@@ -344,7 +381,7 @@
if (args.size() == 5) {
gid = DecodeUid(args[4]);
if (!gid) {
- return Error() << "Unable to decode GID for '" << args[3] << "': " << gid.error();
+ return Error() << "Unable to decode GID for '" << args[4] << "': " << gid.error();
}
}
@@ -459,7 +496,7 @@
if (wait)
wait_for_file(source, kCommandRetryTimeout);
if (mount(source, target, system, flags, options) < 0) {
- return ErrnoError() << "mount() failed";
+ return ErrnoErrorIgnoreEnoent() << "mount() failed";
}
}
@@ -683,7 +720,7 @@
Service* svc = ServiceList::GetInstance().FindService(args[1]);
if (!svc) return Error() << "service " << args[1] << " not found";
if (auto result = svc->Start(); !result) {
- return Error() << "Could not start service: " << result.error();
+ return ErrorIgnoreEnoent() << "Could not start service: " << result.error();
}
return {};
}
@@ -729,10 +766,7 @@
if (MakeSymlink(args[1], args[2]) < 0) {
// The symlink builtin is often used to create symlinks for older devices to be backwards
// compatible with new paths, therefore we skip reporting this error.
- if (errno == EEXIST && android::base::GetMinimumLogSeverity() > android::base::DEBUG) {
- return {};
- }
- return ErrnoError() << "symlink() failed";
+ return ErrnoErrorIgnoreEnoent() << "symlink() failed";
}
return {};
}
@@ -790,7 +824,8 @@
static Result<void> do_write(const BuiltinArguments& args) {
if (auto result = WriteFile(args[1], args[2]); !result) {
- return Error() << "Unable to write to file '" << args[1] << "': " << result.error();
+ return ErrorIgnoreEnoent()
+ << "Unable to write to file '" << args[1] << "': " << result.error();
}
return {};
@@ -908,7 +943,7 @@
}
if (lchown(path.c_str(), *uid, *gid) == -1) {
- return ErrnoError() << "lchown() failed";
+ return ErrnoErrorIgnoreEnoent() << "lchown() failed";
}
return {};
@@ -930,50 +965,27 @@
static Result<void> do_chmod(const BuiltinArguments& args) {
mode_t mode = get_mode(args[1].c_str());
if (fchmodat(AT_FDCWD, args[2].c_str(), mode, AT_SYMLINK_NOFOLLOW) < 0) {
- return ErrnoError() << "fchmodat() failed";
+ return ErrnoErrorIgnoreEnoent() << "fchmodat() failed";
}
return {};
}
static Result<void> do_restorecon(const BuiltinArguments& args) {
+ auto restorecon_info = ParseRestorecon(args.args);
+ if (!restorecon_info) {
+ return restorecon_info.error();
+ }
+
+ const auto& [flag, paths] = *restorecon_info;
+
int ret = 0;
-
- struct flag_type {const char* name; int value;};
- static const flag_type flags[] = {
- {"--recursive", SELINUX_ANDROID_RESTORECON_RECURSE},
- {"--skip-ce", SELINUX_ANDROID_RESTORECON_SKIPCE},
- {"--cross-filesystems", SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS},
- {0, 0}
- };
-
- int flag = 0;
-
- bool in_flags = true;
- for (size_t i = 1; i < args.size(); ++i) {
- if (android::base::StartsWith(args[i], "--")) {
- if (!in_flags) {
- return Error() << "flags must precede paths";
- }
- bool found = false;
- for (size_t j = 0; flags[j].name; ++j) {
- if (args[i] == flags[j].name) {
- flag |= flags[j].value;
- found = true;
- break;
- }
- }
- if (!found) {
- return Error() << "bad flag " << args[i];
- }
- } else {
- in_flags = false;
- if (selinux_android_restorecon(args[i].c_str(), flag) < 0) {
- ret = errno;
- }
+ for (const auto& path : paths) {
+ if (selinux_android_restorecon(path.c_str(), flag) < 0) {
+ ret = errno;
}
}
- if (ret) return ErrnoError() << "selinux_android_restorecon() failed";
+ if (ret) return ErrnoErrorIgnoreEnoent() << "selinux_android_restorecon() failed";
return {};
}
@@ -1056,9 +1068,9 @@
const BuiltinArguments& args) {
auto service = Service::MakeTemporaryOneshotService(args.args);
if (!service) {
- return Error() << "Could not create exec service";
+ return Error() << "Could not create exec service: " << service.error();
}
- service->AddReapCallback([reboot_reason](const siginfo_t& siginfo) {
+ (*service)->AddReapCallback([reboot_reason](const siginfo_t& siginfo) {
if (siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) {
// TODO (b/122850122): support this in gsi
if (fscrypt_is_native() && !android::gsi::IsGsiRunning()) {
@@ -1073,10 +1085,10 @@
}
}
});
- if (auto result = service->ExecStart(); !result) {
+ if (auto result = (*service)->ExecStart(); !result) {
return Error() << "Could not start exec service: " << result.error();
}
- ServiceList::GetInstance().AddService(std::move(service));
+ ServiceList::GetInstance().AddService(std::move(*service));
return {};
}
diff --git a/init/check_builtins.cpp b/init/check_builtins.cpp
new file mode 100644
index 0000000..771f1d7
--- /dev/null
+++ b/init/check_builtins.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Note that these check functions cannot check expanded arguments from properties, since they will
+// not know what those properties would be at runtime. They will be passed an empty string in the
+// situation that the input line had a property expansion without a default value, since an empty
+// string is otherwise an impossible value. They should therefore disregard checking empty
+// arguments.
+
+#include "check_builtins.h"
+
+#include <sys/time.h>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+#include "builtin_arguments.h"
+#include "interface_utils.h"
+#include "rlimit_parser.h"
+#include "service.h"
+#include "util.h"
+
+using android::base::ParseInt;
+using android::base::StartsWith;
+
+#define ReturnIfAnyArgsEmpty() \
+ for (const auto& arg : args) { \
+ if (arg.empty()) { \
+ return {}; \
+ } \
+ }
+
+namespace android {
+namespace init {
+
+Result<void> check_chown(const BuiltinArguments& args) {
+ if (!args[1].empty()) {
+ auto uid = DecodeUid(args[1]);
+ if (!uid) {
+ return Error() << "Unable to decode UID for '" << args[1] << "': " << uid.error();
+ }
+ }
+
+ // GID is optional and pushes the index of path out by one if specified.
+ if (args.size() == 4 && !args[2].empty()) {
+ auto gid = DecodeUid(args[2]);
+ if (!gid) {
+ return Error() << "Unable to decode GID for '" << args[2] << "': " << gid.error();
+ }
+ }
+
+ return {};
+}
+
+Result<void> check_exec(const BuiltinArguments& args) {
+ ReturnIfAnyArgsEmpty();
+
+ auto result = Service::MakeTemporaryOneshotService(args.args);
+ if (!result) {
+ return result.error();
+ }
+
+ return {};
+}
+
+Result<void> check_exec_background(const BuiltinArguments& args) {
+ return check_exec(std::move(args));
+}
+
+Result<void> check_interface_restart(const BuiltinArguments& args) {
+ if (auto result = IsKnownInterface(args[1]); !result) {
+ return result.error();
+ }
+ return {};
+}
+
+Result<void> check_interface_start(const BuiltinArguments& args) {
+ return check_interface_restart(std::move(args));
+}
+
+Result<void> check_interface_stop(const BuiltinArguments& args) {
+ return check_interface_restart(std::move(args));
+}
+
+Result<void> check_load_system_props(const BuiltinArguments& args) {
+ return Error() << "'load_system_props' is deprecated";
+}
+
+Result<void> check_loglevel(const BuiltinArguments& args) {
+ ReturnIfAnyArgsEmpty();
+
+ int log_level = -1;
+ ParseInt(args[1], &log_level);
+ if (log_level < 0 || log_level > 7) {
+ return Error() << "loglevel must be in the range of 0-7";
+ }
+ return {};
+}
+
+Result<void> check_mkdir(const BuiltinArguments& args) {
+ if (args.size() >= 4) {
+ if (!args[3].empty()) {
+ auto uid = DecodeUid(args[3]);
+ if (!uid) {
+ return Error() << "Unable to decode UID for '" << args[3] << "': " << uid.error();
+ }
+ }
+
+ if (args.size() == 5 && !args[4].empty()) {
+ auto gid = DecodeUid(args[4]);
+ if (!gid) {
+ return Error() << "Unable to decode GID for '" << args[4] << "': " << gid.error();
+ }
+ }
+ }
+
+ return {};
+}
+
+Result<void> check_restorecon(const BuiltinArguments& args) {
+ ReturnIfAnyArgsEmpty();
+
+ auto restorecon_info = ParseRestorecon(args.args);
+ if (!restorecon_info) {
+ return restorecon_info.error();
+ }
+
+ return {};
+}
+
+Result<void> check_restorecon_recursive(const BuiltinArguments& args) {
+ return check_restorecon(std::move(args));
+}
+
+Result<void> check_setprop(const BuiltinArguments& args) {
+ const std::string& name = args[1];
+ if (name.empty()) {
+ return {};
+ }
+ const std::string& value = args[2];
+
+ if (!IsLegalPropertyName(name)) {
+ return Error() << "'" << name << "' is not a legal property name";
+ }
+
+ if (!value.empty()) {
+ if (auto result = IsLegalPropertyValue(name, value); !result) {
+ return result.error();
+ }
+ }
+
+ if (StartsWith(name, "ctl.")) {
+ return Error()
+ << "Do not set ctl. properties from init; call the Service functions directly";
+ }
+
+ static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
+ if (name == kRestoreconProperty) {
+ return Error() << "Do not set '" << kRestoreconProperty
+ << "' from init; use the restorecon builtin directly";
+ }
+
+ return {};
+}
+
+Result<void> check_setrlimit(const BuiltinArguments& args) {
+ ReturnIfAnyArgsEmpty();
+
+ auto rlimit = ParseRlimit(args.args);
+ if (!rlimit) return rlimit.error();
+ return {};
+}
+
+Result<void> check_sysclktz(const BuiltinArguments& args) {
+ ReturnIfAnyArgsEmpty();
+
+ struct timezone tz = {};
+ if (!android::base::ParseInt(args[1], &tz.tz_minuteswest)) {
+ return Error() << "Unable to parse mins_west_of_gmt";
+ }
+ return {};
+}
+
+Result<void> check_wait(const BuiltinArguments& args) {
+ if (args.size() == 3 && !args[2].empty()) {
+ int timeout_int;
+ if (!android::base::ParseInt(args[2], &timeout_int)) {
+ return Error() << "failed to parse timeout";
+ }
+ }
+ return {};
+}
+
+Result<void> check_wait_for_prop(const BuiltinArguments& args) {
+ return check_setprop(std::move(args));
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/check_builtins.h b/init/check_builtins.h
new file mode 100644
index 0000000..4ff0d0c
--- /dev/null
+++ b/init/check_builtins.h
@@ -0,0 +1,43 @@
+/*
+ * 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 "builtin_arguments.h"
+#include "result.h"
+
+namespace android {
+namespace init {
+
+Result<void> check_chown(const BuiltinArguments& args);
+Result<void> check_exec(const BuiltinArguments& args);
+Result<void> check_exec_background(const BuiltinArguments& args);
+Result<void> check_interface_restart(const BuiltinArguments& args);
+Result<void> check_interface_start(const BuiltinArguments& args);
+Result<void> check_interface_stop(const BuiltinArguments& args);
+Result<void> check_load_system_props(const BuiltinArguments& args);
+Result<void> check_loglevel(const BuiltinArguments& args);
+Result<void> check_mkdir(const BuiltinArguments& args);
+Result<void> check_restorecon(const BuiltinArguments& args);
+Result<void> check_restorecon_recursive(const BuiltinArguments& args);
+Result<void> check_setprop(const BuiltinArguments& args);
+Result<void> check_setrlimit(const BuiltinArguments& args);
+Result<void> check_sysclktz(const BuiltinArguments& args);
+Result<void> check_wait(const BuiltinArguments& args);
+Result<void> check_wait_for_prop(const BuiltinArguments& args);
+
+} // namespace init
+} // namespace android
diff --git a/init/host_builtin_map.py b/init/host_builtin_map.py
new file mode 100755
index 0000000..6afcb17
--- /dev/null
+++ b/init/host_builtin_map.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+"""Generates the builtins map to be used by host_init_verifier.
+
+It copies the builtin function map from builtins.cpp, then replaces do_xxx() functions with the
+equivalent check_xxx() if found in check_builtins.cpp.
+
+"""
+
+import re
+import argparse
+
+parser = argparse.ArgumentParser('host_builtin_map.py')
+parser.add_argument('--builtins', required=True, help='Path to builtins.cpp')
+parser.add_argument('--check_builtins', required=True, help='Path to check_builtins.cpp')
+args = parser.parse_args()
+
+CHECK_REGEX = re.compile(r'.+check_(\S+)\(.+')
+check_functions = []
+with open(args.check_builtins) as check_file:
+ for line in check_file:
+ match = CHECK_REGEX.match(line)
+ if match:
+ check_functions.append(match.group(1))
+
+function_map = []
+with open(args.builtins) as builtins_file:
+ in_function_map = False
+ for line in builtins_file:
+ if '// Builtin-function-map start' in line:
+ in_function_map = True
+ elif '// Builtin-function-map end' in line:
+ in_function_map = False
+ elif in_function_map:
+ function_map.append(line)
+
+DO_REGEX = re.compile(r'.+do_([^\}]+).+')
+FUNCTION_REGEX = re.compile(r'(do_[^\}]+)')
+for line in function_map:
+ match = DO_REGEX.match(line)
+ if match:
+ if match.group(1) in check_functions:
+ print line.replace('do_', 'check_'),
+ else:
+ print FUNCTION_REGEX.sub('check_stub', line),
+ else:
+ print line,
diff --git a/init/host_init_stubs.h b/init/host_init_stubs.h
index 7c0544a..f9a08a5 100644
--- a/init/host_init_stubs.h
+++ b/init/host_init_stubs.h
@@ -26,6 +26,7 @@
// android/api-level.h
#define __ANDROID_API_P__ 28
+#define __ANDROID_API_R__ 30
// sys/system_properties.h
#define PROP_VALUE_MAX 92
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index dfde51b..b2402b3 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -30,13 +30,14 @@
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
-#include <json/json.h>
#include "action.h"
#include "action_manager.h"
#include "action_parser.h"
+#include "check_builtins.h"
#include "host_import_parser.h"
#include "host_init_stubs.h"
+#include "interface_utils.h"
#include "parser.h"
#include "result.h"
#include "service.h"
@@ -131,51 +132,22 @@
return nullptr;
}
-static std::optional<android::init::InterfaceInheritanceHierarchyMap>
-ReadInterfaceInheritanceHierarchy(const std::string& interface_inheritance_hierarchy_file) {
- if (interface_inheritance_hierarchy_file.empty()) {
- LOG(WARNING) << "Missing an interface inheritance hierarchy file.";
- return {};
- }
-
- Json::Value root;
- Json::Reader reader;
- std::ifstream stream(interface_inheritance_hierarchy_file);
- if (!reader.parse(stream, root)) {
- LOG(ERROR) << "Failed to read interface inheritance hierarchy file: "
- << interface_inheritance_hierarchy_file << "\n"
- << reader.getFormattedErrorMessages();
- return {};
- }
-
- android::init::InterfaceInheritanceHierarchyMap result;
- for (const Json::Value& entry : root) {
- std::set<std::string> inherited_interfaces;
- for (const Json::Value& intf : entry["inheritedInterfaces"]) {
- inherited_interfaces.insert(intf.asString());
- }
- result[entry["interface"].asString()] = inherited_interfaces;
- }
-
- return result;
-}
-
namespace android {
namespace init {
-static Result<void> do_stub(const BuiltinArguments& args) {
+static Result<void> check_stub(const BuiltinArguments& args) {
return {};
}
#include "generated_stub_builtin_function_map.h"
void PrintUsage() {
- std::cout << "usage: host_init_verifier [-p FILE] -k FILE <init rc file>\n"
+ std::cout << "usage: host_init_verifier [-p FILE] -i FILE <init rc file>\n"
"\n"
"Tests an init script for correctness\n"
"\n"
"-p FILE\tSearch this passwd file for users and groups\n"
- "-k FILE\tUse this file as a space-separated list of known interfaces\n"
+ "-i FILE\tParse this JSON file for the HIDL interface inheritance hierarchy\n"
<< std::endl;
}
@@ -216,21 +188,26 @@
argc -= optind;
argv += optind;
- if (argc != 1) {
+ if (argc != 1 || interface_inheritance_hierarchy_file.empty()) {
PrintUsage();
return EXIT_FAILURE;
}
+ auto interface_inheritance_hierarchy_map =
+ ReadInterfaceInheritanceHierarchy(interface_inheritance_hierarchy_file);
+ if (!interface_inheritance_hierarchy_map) {
+ LOG(ERROR) << interface_inheritance_hierarchy_map.error();
+ return EXIT_FAILURE;
+ }
+ SetKnownInterfaces(*interface_inheritance_hierarchy_map);
+
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
Action::set_function_map(&function_map);
ActionManager& am = ActionManager::GetInstance();
ServiceList& sl = ServiceList::GetInstance();
Parser parser;
- parser.AddSectionParser(
- "service",
- std::make_unique<ServiceParser>(
- &sl, nullptr,
- ReadInterfaceInheritanceHierarchy(interface_inheritance_hierarchy_file)));
+ parser.AddSectionParser("service", std::make_unique<ServiceParser>(
+ &sl, nullptr, *interface_inheritance_hierarchy_map));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
parser.AddSectionParser("import", std::make_unique<HostImportParser>());
@@ -238,9 +215,10 @@
LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
return EXIT_FAILURE;
}
- if (parser.parse_error_count() > 0) {
- LOG(ERROR) << "Failed to parse init script '" << *argv << "' with "
- << parser.parse_error_count() << " errors";
+ size_t failures = parser.parse_error_count() + am.CheckAllCommands() + sl.CheckAllCommands();
+ if (failures > 0) {
+ LOG(ERROR) << "Failed to parse init script '" << *argv << "' with " << failures
+ << " errors";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
diff --git a/init/import_parser.cpp b/init/import_parser.cpp
index c72b7d6..1a43508 100644
--- a/init/import_parser.cpp
+++ b/init/import_parser.cpp
@@ -29,15 +29,14 @@
return Error() << "single argument needed for import\n";
}
- std::string conf_file;
- bool ret = expand_props(args[1], &conf_file);
- if (!ret) {
- return Error() << "error while expanding import";
+ auto conf_file = ExpandProps(args[1]);
+ if (!conf_file) {
+ return Error() << "Could not expand import: " << conf_file.error();
}
- LOG(INFO) << "Added '" << conf_file << "' to import list";
+ LOG(INFO) << "Added '" << *conf_file << "' to import list";
if (filename_.empty()) filename_ = filename;
- imports_.emplace_back(std::move(conf_file), line);
+ imports_.emplace_back(std::move(*conf_file), line);
return {};
}
diff --git a/init/interface_utils.cpp b/init/interface_utils.cpp
new file mode 100644
index 0000000..a54860f
--- /dev/null
+++ b/init/interface_utils.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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 "interface_utils.h"
+
+#include <fstream>
+#include <sstream>
+
+#include <android-base/strings.h>
+#include <hidl-util/FqInstance.h>
+#include <json/json.h>
+
+using android::FqInstance;
+using android::FQName;
+using android::base::Error;
+
+namespace android {
+namespace init {
+
+namespace {
+
+std::string FQNamesToString(const std::set<FQName>& fqnames) {
+ std::set<std::string> fqname_strings;
+ for (const FQName& fqname : fqnames) {
+ fqname_strings.insert(fqname.string());
+ }
+ return android::base::Join(fqname_strings, " ");
+}
+
+} // namespace
+
+Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy(
+ const std::string& path) {
+ Json::Value root;
+ Json::Reader reader;
+ std::ifstream stream(path);
+ if (!reader.parse(stream, root)) {
+ return Error() << "Failed to read interface inheritance hierarchy file: " << path << "\n"
+ << reader.getFormattedErrorMessages();
+ }
+
+ InterfaceInheritanceHierarchyMap result;
+ for (const Json::Value& entry : root) {
+ std::set<FQName> inherited_interfaces;
+ for (const Json::Value& intf : entry["inheritedInterfaces"]) {
+ FQName fqname;
+ if (!fqname.setTo(intf.asString())) {
+ return Error() << "Unable to parse interface '" << intf.asString() << "'";
+ }
+ inherited_interfaces.insert(fqname);
+ }
+ std::string intf_string = entry["interface"].asString();
+ FQName fqname;
+ if (!fqname.setTo(intf_string)) {
+ return Error() << "Unable to parse interface '" << intf_string << "'";
+ }
+ result[fqname] = inherited_interfaces;
+ }
+
+ return result;
+}
+
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
+ const InterfaceInheritanceHierarchyMap& hierarchy) {
+ std::set<FQName> interface_fqnames;
+ for (const std::string& instance : instances) {
+ FqInstance fqinstance;
+ if (!fqinstance.setTo(instance)) {
+ return Error() << "Unable to parse interface instance '" << instance << "'";
+ }
+ interface_fqnames.insert(fqinstance.getFqName());
+ }
+ return CheckInterfaceInheritanceHierarchy(interface_fqnames, hierarchy);
+}
+
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<FQName>& interfaces,
+ const InterfaceInheritanceHierarchyMap& hierarchy) {
+ std::ostringstream error_stream;
+ for (const FQName& intf : interfaces) {
+ if (hierarchy.count(intf) == 0) {
+ error_stream << "\nInterface is not in the known set of hidl_interfaces: '"
+ << intf.string()
+ << "'. Please ensure the interface is spelled correctly and built "
+ << "by a hidl_interface target.";
+ continue;
+ }
+ const std::set<FQName>& required_interfaces = hierarchy.at(intf);
+ std::set<FQName> diff;
+ std::set_difference(required_interfaces.begin(), required_interfaces.end(),
+ interfaces.begin(), interfaces.end(),
+ std::inserter(diff, diff.begin()));
+ if (!diff.empty()) {
+ error_stream << "\nInterface '" << intf.string() << "' requires its full inheritance "
+ << "hierarchy to be listed in this init_rc file. Missing "
+ << "interfaces: [" << FQNamesToString(diff) << "]";
+ }
+ }
+ const std::string& errors = error_stream.str();
+ if (!errors.empty()) {
+ return Error() << errors;
+ }
+
+ return {};
+}
+
+std::optional<std::set<FQName>> known_interfaces;
+
+void SetKnownInterfaces(const InterfaceInheritanceHierarchyMap& hierarchy) {
+ known_interfaces = std::set<FQName>();
+ for (const auto& [intf, inherited_interfaces] : hierarchy) {
+ known_interfaces->insert(intf);
+ }
+}
+
+Result<void> IsKnownInterface(const std::string& instance) {
+ FqInstance fqinstance;
+ if (!fqinstance.setTo(instance)) {
+ return Error() << "Unable to parse interface instance '" << instance << "'";
+ }
+ return IsKnownInterface(fqinstance.getFqName());
+}
+
+Result<void> IsKnownInterface(const FQName& intf) {
+ if (!known_interfaces) {
+ return Error() << "No known interfaces have been loaded.";
+ }
+ if (known_interfaces->count(intf) == 0) {
+ return Error() << "Interface is not in the known set of hidl_interfaces: '" << intf.string()
+ << "'. Please ensure the interface is spelled correctly and built "
+ << "by a hidl_interface target.";
+ }
+ return {};
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/interface_utils.h b/init/interface_utils.h
new file mode 100644
index 0000000..bd0c104
--- /dev/null
+++ b/init/interface_utils.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+
+#include <hidl-util/FQName.h>
+
+#include "result.h"
+
+namespace android {
+namespace init {
+
+using InterfaceInheritanceHierarchyMap = std::map<android::FQName, std::set<android::FQName>>;
+
+// Reads the HIDL interface inheritance hierarchy JSON file at the given path.
+Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy(const std::string& path);
+
+// For the given set of interfaces / interface instances, checks that each
+// interface's hierarchy of inherited interfaces is also included in the given
+// interface set. Uses the provided hierarchy data.
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<std::string>& instances,
+ const InterfaceInheritanceHierarchyMap& hierarchy);
+Result<void> CheckInterfaceInheritanceHierarchy(const std::set<android::FQName>& interfaces,
+ const InterfaceInheritanceHierarchyMap& hierarchy);
+
+// Saves the set of known interfaces using the provided HIDL interface
+// inheritance hierarchy.
+void SetKnownInterfaces(const InterfaceInheritanceHierarchyMap& hierarchy);
+
+// Checks if the provided interface is in the set of known interfaces. Returns
+// an empty Result if present, otherwise an Error.
+Result<void> IsKnownInterface(const std::string& instance);
+Result<void> IsKnownInterface(const FQName& intf);
+
+} // namespace init
+} // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 3761750..17622a3 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -174,13 +174,8 @@
return PROP_ERROR_INVALID_NAME;
}
- if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
- *error = "Property value too long";
- return PROP_ERROR_INVALID_VALUE;
- }
-
- if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
- *error = "Value is not a UTF8 encoded string";
+ if (auto result = IsLegalPropertyValue(name, value); !result) {
+ *error = result.error().message();
return PROP_ERROR_INVALID_VALUE;
}
@@ -648,13 +643,14 @@
}
std::string raw_filename(fn);
- std::string expanded_filename;
- if (!expand_props(raw_filename, &expanded_filename)) {
- LOG(ERROR) << "Could not expand filename '" << raw_filename << "'";
+ auto expanded_filename = ExpandProps(raw_filename);
+
+ if (!expanded_filename) {
+ LOG(ERROR) << "Could not expand filename ': " << expanded_filename.error();
continue;
}
- load_properties_from_file(expanded_filename.c_str(), key, properties);
+ load_properties_from_file(expanded_filename->c_str(), key, properties);
} else {
value = strchr(key, '=');
if (!value) continue;
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 309fcc1..fd42256 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -497,24 +497,28 @@
// This function returns the Android version with which the vendor SEPolicy was compiled.
// It is used for version checks such as whether or not vendor_init should be used
int SelinuxGetVendorAndroidVersion() {
- if (!IsSplitPolicyDevice()) {
- // If this device does not split sepolicy files, it's not a Treble device and therefore,
- // we assume it's always on the latest platform.
- return __ANDROID_API_FUTURE__;
- }
+ static int vendor_android_version = [] {
+ if (!IsSplitPolicyDevice()) {
+ // If this device does not split sepolicy files, it's not a Treble device and therefore,
+ // we assume it's always on the latest platform.
+ return __ANDROID_API_FUTURE__;
+ }
- std::string version;
- if (!GetVendorMappingVersion(&version)) {
- LOG(FATAL) << "Could not read vendor SELinux version";
- }
+ std::string version;
+ if (!GetVendorMappingVersion(&version)) {
+ LOG(FATAL) << "Could not read vendor SELinux version";
+ }
- int major_version;
- std::string major_version_str(version, 0, version.find('.'));
- if (!ParseInt(major_version_str, &major_version)) {
- PLOG(FATAL) << "Failed to parse the vendor sepolicy major version " << major_version_str;
- }
+ int major_version;
+ std::string major_version_str(version, 0, version.find('.'));
+ if (!ParseInt(major_version_str, &major_version)) {
+ PLOG(FATAL) << "Failed to parse the vendor sepolicy major version "
+ << major_version_str;
+ }
- return major_version;
+ return major_version;
+ }();
+ return vendor_android_version;
}
// This function initializes SELinux then execs init to run in the init SELinux context.
diff --git a/init/service.cpp b/init/service.cpp
index 47f4db9..9537843 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -100,9 +100,11 @@
expanded_args.resize(args.size());
c_strings.push_back(const_cast<char*>(args[0].data()));
for (std::size_t i = 1; i < args.size(); ++i) {
- if (!expand_props(args[i], &expanded_args[i])) {
- LOG(FATAL) << args[0] << ": cannot expand '" << args[i] << "'";
+ auto expanded_arg = ExpandProps(args[i]);
+ if (!expanded_arg) {
+ LOG(FATAL) << args[0] << ": cannot expand arguments': " << expanded_arg.error();
}
+ expanded_args[i] = *expanded_arg;
c_strings.push_back(expanded_args[i].data());
}
c_strings.push_back(nullptr);
@@ -165,6 +167,15 @@
property_set(boottime_property, std::to_string(start_ns));
}
}
+
+ // init.svc_debug_pid.* properties are only for tests, and should not be used
+ // on device for security checks.
+ std::string pid_property = "init.svc_debug_pid." + name_;
+ if (new_state == "running") {
+ property_set(pid_property, std::to_string(pid_));
+ } else if (new_state == "stopped") {
+ property_set(pid_property, "");
+ }
}
void Service::KillProcessGroup(int signal) {
@@ -633,7 +644,8 @@
}
}
-std::unique_ptr<Service> Service::MakeTemporaryOneshotService(const std::vector<std::string>& args) {
+Result<std::unique_ptr<Service>> Service::MakeTemporaryOneshotService(
+ const std::vector<std::string>& args) {
// Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
// SECLABEL can be a - to denote default
std::size_t command_arg = 1;
@@ -644,13 +656,11 @@
}
}
if (command_arg > 4 + NR_SVC_SUPP_GIDS) {
- LOG(ERROR) << "exec called with too many supplementary group ids";
- return nullptr;
+ return Error() << "exec called with too many supplementary group ids";
}
if (command_arg >= args.size()) {
- LOG(ERROR) << "exec called without command";
- return nullptr;
+ return Error() << "exec called without command";
}
std::vector<std::string> str_args(args.begin() + command_arg, args.end());
@@ -669,8 +679,7 @@
if (command_arg > 3) {
uid = DecodeUid(args[2]);
if (!uid) {
- LOG(ERROR) << "Unable to decode UID for '" << args[2] << "': " << uid.error();
- return nullptr;
+ return Error() << "Unable to decode UID for '" << args[2] << "': " << uid.error();
}
}
Result<gid_t> gid = 0;
@@ -678,16 +687,14 @@
if (command_arg > 4) {
gid = DecodeUid(args[3]);
if (!gid) {
- LOG(ERROR) << "Unable to decode GID for '" << args[3] << "': " << gid.error();
- return nullptr;
+ return Error() << "Unable to decode GID for '" << args[3] << "': " << gid.error();
}
std::size_t nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */;
for (size_t i = 0; i < nr_supp_gids; ++i) {
auto supp_gid = DecodeUid(args[4 + i]);
if (!supp_gid) {
- LOG(ERROR) << "Unable to decode GID for '" << args[4 + i]
- << "': " << supp_gid.error();
- return nullptr;
+ return Error() << "Unable to decode GID for '" << args[4 + i]
+ << "': " << supp_gid.error();
}
supp_gids.push_back(*supp_gid);
}
diff --git a/init/service.h b/init/service.h
index cdf31bb..ccefc8e 100644
--- a/init/service.h
+++ b/init/service.h
@@ -71,7 +71,8 @@
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);
- static std::unique_ptr<Service> MakeTemporaryOneshotService(const std::vector<std::string>& args);
+ static Result<std::unique_ptr<Service>> MakeTemporaryOneshotService(
+ const std::vector<std::string>& args);
bool IsRunning() { return (flags_ & SVC_RUNNING) != 0; }
Result<void> ExecStart();
@@ -96,6 +97,7 @@
void AddReapCallback(std::function<void(const siginfo_t& siginfo)> callback) {
reap_callbacks_.emplace_back(std::move(callback));
}
+ size_t CheckAllCommands() const { return onrestart_.CheckAllCommands(); }
static bool is_exec_service_running() { return is_exec_service_running_; }
diff --git a/init/service_list.cpp b/init/service_list.cpp
index 3a48183..c51a9cf 100644
--- a/init/service_list.cpp
+++ b/init/service_list.cpp
@@ -28,6 +28,14 @@
return instance;
}
+size_t ServiceList::CheckAllCommands() {
+ size_t failures = 0;
+ for (const auto& service : services_) {
+ failures += service->CheckAllCommands();
+ }
+ return failures;
+}
+
void ServiceList::AddService(std::unique_ptr<Service> service) {
services_.emplace_back(std::move(service));
}
diff --git a/init/service_list.h b/init/service_list.h
index 2136a21..ee2c702 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -30,6 +30,7 @@
// Exposed for testing
ServiceList();
+ size_t CheckAllCommands();
void AddService(std::unique_ptr<Service> service);
void RemoveService(const Service& svc);
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 65d96c6..dd552fb 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -193,9 +193,9 @@
Result<void> ServiceParser::ParseKeycodes(std::vector<std::string>&& args) {
auto it = args.begin() + 1;
if (args.size() == 2 && StartsWith(args[1], "$")) {
- std::string expanded;
- if (!expand_props(args[1], &expanded)) {
- return Error() << "Could not expand property '" << args[1] << "'";
+ auto expanded = ExpandProps(args[1]);
+ if (!expanded) {
+ return expanded.error();
}
// If the property is not set, it defaults to none, in which case there are no keycodes
@@ -204,7 +204,7 @@
return {};
}
- args = Split(expanded, ",");
+ args = Split(*expanded, ",");
it = args.begin();
}
@@ -422,9 +422,11 @@
FileDescriptor file;
file.type = args[2];
- if (!expand_props(args[1], &file.name)) {
- return Error() << "Could not expand property in file path '" << args[1] << "'";
+ auto file_name = ExpandProps(args[1]);
+ if (!file_name) {
+ return Error() << "Could not expand file path ': " << file_name.error();
}
+ file.name = *file_name;
if (file.name[0] != '/' || file.name.find("../") != std::string::npos) {
return Error() << "file name must not be relative";
}
@@ -566,33 +568,10 @@
}
if (interface_inheritance_hierarchy_) {
- std::set<std::string> interface_names;
- for (const std::string& intf : service_->interfaces()) {
- interface_names.insert(Split(intf, "/")[0]);
- }
- std::ostringstream error_stream;
- for (const std::string& intf : interface_names) {
- if (interface_inheritance_hierarchy_->count(intf) == 0) {
- error_stream << "\nInterface is not in the known set of hidl_interfaces: '" << intf
- << "'. Please ensure the interface is spelled correctly and built "
- << "by a hidl_interface target.";
- continue;
- }
- const std::set<std::string>& required_interfaces =
- (*interface_inheritance_hierarchy_)[intf];
- std::set<std::string> diff;
- std::set_difference(required_interfaces.begin(), required_interfaces.end(),
- interface_names.begin(), interface_names.end(),
- std::inserter(diff, diff.begin()));
- if (!diff.empty()) {
- error_stream << "\nInterface '" << intf << "' requires its full inheritance "
- << "hierarchy to be listed in this init_rc file. Missing "
- << "interfaces: [" << base::Join(diff, " ") << "]";
- }
- }
- const std::string& errors = error_stream.str();
- if (!errors.empty()) {
- return Error() << errors;
+ if (const auto& check_hierarchy_result = CheckInterfaceInheritanceHierarchy(
+ service_->interfaces(), *interface_inheritance_hierarchy_);
+ !check_hierarchy_result) {
+ return Error() << check_hierarchy_result.error();
}
}
diff --git a/init/service_parser.h b/init/service_parser.h
index 98ab15a..4729874 100644
--- a/init/service_parser.h
+++ b/init/service_parser.h
@@ -18,6 +18,7 @@
#include <vector>
+#include "interface_utils.h"
#include "parser.h"
#include "service.h"
#include "service_list.h"
@@ -26,8 +27,6 @@
namespace android {
namespace init {
-using InterfaceInheritanceHierarchyMap = std::map<std::string, std::set<std::string>>;
-
class ServiceParser : public SectionParser {
public:
ServiceParser(
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 6a34acc..c9cc7bd 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -75,15 +75,15 @@
TEST(service, make_temporary_oneshot_service_invalid_syntax) {
std::vector<std::string> args;
// Nothing.
- ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+ ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
// No arguments to 'exec'.
args.push_back("exec");
- ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+ ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
// No command in "exec --".
args.push_back("--");
- ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+ ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
}
TEST(service, make_temporary_oneshot_service_too_many_supplementary_gids) {
@@ -97,7 +97,7 @@
}
args.push_back("--");
args.push_back("/system/bin/id");
- ASSERT_EQ(nullptr, Service::MakeTemporaryOneshotService(args));
+ ASSERT_FALSE(Service::MakeTemporaryOneshotService(args));
}
static void Test_make_temporary_oneshot_service(bool dash_dash, bool seclabel, bool uid, bool gid,
@@ -122,8 +122,9 @@
}
args.push_back("/system/bin/toybox");
args.push_back("id");
- auto svc = Service::MakeTemporaryOneshotService(args);
- ASSERT_NE(nullptr, svc);
+ auto service_ret = Service::MakeTemporaryOneshotService(args);
+ ASSERT_TRUE(service_ret);
+ auto svc = std::move(*service_ret);
if (seclabel) {
ASSERT_EQ("u:r:su:s0", svc->seclabel());
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index c9a09cd..984235d 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -73,6 +73,13 @@
auto exec_duration_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
+ } else if (service->flags() & SVC_ONESHOT) {
+ auto exec_duration = boot_clock::now() - service->time_started();
+ auto exec_duration_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration)
+ .count();
+ wait_string = StringPrintf(" oneshot service took %f seconds in background",
+ exec_duration_ms / 1000.0f);
}
} else {
name = StringPrintf("Untracked pid %d", pid);
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index a13f0c7..00f91d8 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -151,15 +151,15 @@
void SubcontextProcess::ExpandArgs(const SubcontextCommand::ExpandArgsCommand& expand_args_command,
SubcontextReply* reply) const {
for (const auto& arg : expand_args_command.args()) {
- auto expanded_prop = std::string{};
- if (!expand_props(arg, &expanded_prop)) {
+ auto expanded_arg = ExpandProps(arg);
+ if (!expanded_arg) {
auto* failure = reply->mutable_failure();
- failure->set_error_string("Failed to expand '" + arg + "'");
+ failure->set_error_string(expanded_arg.error().message());
failure->set_error_errno(0);
return;
} else {
auto* expand_args_reply = reply->mutable_expand_args_reply();
- expand_args_reply->add_expanded_args(expanded_prop);
+ expand_args_reply->add_expanded_args(*expanded_arg);
}
}
}
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index e120a62..ae89c38 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -166,7 +166,8 @@
};
auto result = subcontext.ExpandArgs(args);
ASSERT_FALSE(result);
- EXPECT_EQ("Failed to expand '" + args[1] + "'", result.error().message());
+ EXPECT_EQ("unexpected end of string in '" + args[1] + "', looking for }",
+ result.error().message());
});
}
diff --git a/init/test_utils/Android.bp b/init/test_utils/Android.bp
new file mode 100644
index 0000000..1cb05b6
--- /dev/null
+++ b/init/test_utils/Android.bp
@@ -0,0 +1,27 @@
+cc_library_static {
+ name: "libinit_test_utils",
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Wno-unused-parameter",
+ "-Werror",
+ ],
+ srcs: [
+ "service_utils.cpp",
+ ],
+ shared_libs: [
+ "libcutils",
+ "liblog",
+ "libjsoncpp",
+ "libprotobuf-cpp-lite",
+ "libhidl-gen-utils",
+ ],
+ whole_static_libs: [
+ "libinit",
+ "libpropertyinfoparser",
+ ],
+ static_libs: [
+ "libbase",
+ ],
+ export_include_dirs: ["include"], // for tests
+}
diff --git a/init/test_utils/include/init-test-utils/service_utils.h b/init/test_utils/include/init-test-utils/service_utils.h
new file mode 100644
index 0000000..3ec61d4
--- /dev/null
+++ b/init/test_utils/include/init-test-utils/service_utils.h
@@ -0,0 +1,32 @@
+//
+// 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 <map>
+#include <set>
+
+#include <android-base/result.h>
+#include <hidl-util/FqInstance.h>
+
+namespace android {
+namespace init {
+
+using ServiceInterfacesMap = std::map<std::string, std::set<android::FqInstance>>;
+android::base::Result<ServiceInterfacesMap> GetOnDeviceServiceInterfacesMap();
+
+} // namespace init
+} // namespace android
diff --git a/init/test_utils/service_utils.cpp b/init/test_utils/service_utils.cpp
new file mode 100644
index 0000000..bc00702
--- /dev/null
+++ b/init/test_utils/service_utils.cpp
@@ -0,0 +1,63 @@
+//
+// 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 <string>
+
+#include <android-base/logging.h>
+
+#include "../parser.h"
+#include "../service.h"
+#include "../service_list.h"
+#include "../service_parser.h"
+#include "include/init-test-utils/service_utils.h"
+
+namespace android {
+namespace init {
+
+android::base::Result<ServiceInterfacesMap> GetOnDeviceServiceInterfacesMap() {
+ ServiceList& service_list = ServiceList::GetInstance();
+ Parser parser;
+ parser.AddSectionParser("service",
+ std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+ for (const auto& location : {
+ "/init.rc",
+ "/system/etc/init",
+ "/system_ext/etc/init",
+ "/product/etc/init",
+ "/odm/etc/init",
+ "/vendor/etc/init",
+ }) {
+ parser.ParseConfig(location);
+ }
+
+ ServiceInterfacesMap result;
+ for (const auto& service : service_list.services()) {
+ // Create an entry for all services, including services that may not
+ // have any declared interfaces.
+ result[service->name()] = std::set<android::FqInstance>();
+ for (const auto& intf : service->interfaces()) {
+ android::FqInstance fqInstance;
+ if (!fqInstance.setTo(intf)) {
+ return android::base::Error() << "Unable to parse interface: '" << intf << "'";
+ }
+ result[service->name()].insert(fqInstance);
+ }
+ }
+ return result;
+}
+
+} // namespace init
+} // namespace android
diff --git a/init/util.cpp b/init/util.cpp
index 8bfb755..0532375 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -41,13 +41,18 @@
#include <selinux/android.h>
#if defined(__ANDROID__)
+#include <android/api-level.h>
+#include <sys/system_properties.h>
+
#include "reboot_utils.h"
#include "selabel.h"
+#include "selinux.h"
#else
#include "host_init_stubs.h"
#endif
using android::base::boot_clock;
+using android::base::StartsWith;
using namespace std::literals::string_literals;
namespace android {
@@ -267,12 +272,10 @@
return S_ISDIR(info.st_mode);
}
-bool expand_props(const std::string& src, std::string* dst) {
+Result<std::string> ExpandProps(const std::string& src) {
const char* src_ptr = src.c_str();
- if (!dst) {
- return false;
- }
+ std::string dst;
/* - variables can either be $x.y or ${x.y}, in case they are only part
* of the string.
@@ -286,19 +289,19 @@
c = strchr(src_ptr, '$');
if (!c) {
- dst->append(src_ptr);
- return true;
+ dst.append(src_ptr);
+ return dst;
}
- dst->append(src_ptr, c);
+ dst.append(src_ptr, c);
c++;
if (*c == '$') {
- dst->push_back(*(c++));
+ dst.push_back(*(c++));
src_ptr = c;
continue;
} else if (*c == '\0') {
- return true;
+ return dst;
}
std::string prop_name;
@@ -308,8 +311,7 @@
const char* end = strchr(c, '}');
if (!end) {
// failed to find closing brace, abort.
- LOG(ERROR) << "unexpected end of string in '" << src << "', looking for }";
- return false;
+ return Error() << "unexpected end of string in '" << src << "', looking for }";
}
prop_name = std::string(c, end);
c = end + 1;
@@ -320,29 +322,34 @@
}
} else {
prop_name = c;
- LOG(ERROR) << "using deprecated syntax for specifying property '" << c << "', use ${name} instead";
+ if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) {
+ return Error() << "using deprecated syntax for specifying property '" << c
+ << "', use ${name} instead";
+ } else {
+ LOG(ERROR) << "using deprecated syntax for specifying property '" << c
+ << "', use ${name} instead";
+ }
c += prop_name.size();
}
if (prop_name.empty()) {
- LOG(ERROR) << "invalid zero-length property name in '" << src << "'";
- return false;
+ return Error() << "invalid zero-length property name in '" << src << "'";
}
std::string prop_val = android::base::GetProperty(prop_name, "");
if (prop_val.empty()) {
if (def_val.empty()) {
- LOG(ERROR) << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'";
- return false;
+ return Error() << "property '" << prop_name << "' doesn't exist while expanding '"
+ << src << "'";
}
prop_val = def_val;
}
- dst->append(prop_val);
+ dst.append(prop_val);
src_ptr = c;
}
- return true;
+ return dst;
}
static std::string init_android_dt_dir() {
@@ -414,6 +421,58 @@
return true;
}
+Result<void> IsLegalPropertyValue(const std::string& name, const std::string& value) {
+ if (value.size() >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
+ return Error() << "Property value too long";
+ }
+
+ if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
+ return Error() << "Value is not a UTF8 encoded string";
+ }
+
+ return {};
+}
+
+Result<std::pair<int, std::vector<std::string>>> ParseRestorecon(
+ const std::vector<std::string>& args) {
+ struct flag_type {
+ const char* name;
+ int value;
+ };
+ static const flag_type flags[] = {
+ {"--recursive", SELINUX_ANDROID_RESTORECON_RECURSE},
+ {"--skip-ce", SELINUX_ANDROID_RESTORECON_SKIPCE},
+ {"--cross-filesystems", SELINUX_ANDROID_RESTORECON_CROSS_FILESYSTEMS},
+ {0, 0}};
+
+ int flag = 0;
+ std::vector<std::string> paths;
+
+ bool in_flags = true;
+ for (size_t i = 1; i < args.size(); ++i) {
+ if (android::base::StartsWith(args[i], "--")) {
+ if (!in_flags) {
+ return Error() << "flags must precede paths";
+ }
+ bool found = false;
+ for (size_t j = 0; flags[j].name; ++j) {
+ if (args[i] == flags[j].name) {
+ flag |= flags[j].value;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return Error() << "bad flag " << args[i];
+ }
+ } else {
+ in_flags = false;
+ paths.emplace_back(args[i]);
+ }
+ }
+ return std::pair(flag, paths);
+}
+
static void InitAborter(const char* abort_message) {
// When init forks, it continues to use this aborter for LOG(FATAL), but we want children to
// simply abort instead of trying to reboot the system.
diff --git a/init/util.h b/init/util.h
index 6a12fb6..4cccefe 100644
--- a/init/util.h
+++ b/init/util.h
@@ -14,24 +14,20 @@
* limitations under the License.
*/
-#ifndef _INIT_UTIL_H_
-#define _INIT_UTIL_H_
+#pragma once
#include <sys/stat.h>
#include <sys/types.h>
#include <chrono>
#include <functional>
-#include <ostream>
#include <string>
#include <android-base/chrono_utils.h>
-#include <selinux/label.h>
#include "result.h"
using android::base::boot_clock;
-using namespace std::chrono_literals;
namespace android {
namespace init {
@@ -52,7 +48,7 @@
const std::function<void(const std::string&, const std::string&, bool)>&);
bool make_dir(const std::string& path, mode_t mode);
bool is_dir(const char* pathname);
-bool expand_props(const std::string& src, std::string* dst);
+Result<std::string> ExpandProps(const std::string& src);
// Returns the platform's Android DT directory as specified in the kernel cmdline.
// If the platform does not configure a custom DT path, returns the standard one (based in procfs).
@@ -62,11 +58,13 @@
bool is_android_dt_value_expected(const std::string& sub_path, const std::string& expected_content);
bool IsLegalPropertyName(const std::string& name);
+Result<void> IsLegalPropertyValue(const std::string& name, const std::string& value);
+
+Result<std::pair<int, std::vector<std::string>>> ParseRestorecon(
+ const std::vector<std::string>& args);
void SetStdioToDevNull(char** argv);
void InitKernelLogging(char** argv);
bool IsRecoveryMode();
} // namespace init
} // namespace android
-
-#endif
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 319a73a..b9420d4 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -67,8 +67,6 @@
"native_handle.cpp",
"record_stream.cpp",
"sockets.cpp",
- "strdup16to8.cpp",
- "strdup8to16.cpp",
"strlcpy.c",
"threads.cpp",
],
diff --git a/libcutils/include/cutils/jstring.h b/libcutils/include/cutils/jstring.h
deleted file mode 100644
index a342608..0000000
--- a/libcutils/include/cutils/jstring.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2006 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 __CUTILS_STRING16_H
-#define __CUTILS_STRING16_H
-
-#include <stdint.h>
-#include <stddef.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#if __STDC_VERSION__ < 201112L && __cplusplus < 201103L
- typedef uint16_t char16_t;
-#endif
- // otherwise char16_t is a keyword with the right semantics
-
-extern char * strndup16to8 (const char16_t* s, size_t n);
-extern size_t strnlen16to8 (const char16_t* s, size_t n);
-extern char * strncpy16to8 (char *dest, const char16_t*s, size_t n);
-
-extern char16_t * strdup8to16 (const char* s, size_t *out_len);
-extern size_t strlen8to16 (const char* utf8Str);
-extern char16_t * strcpy8to16 (char16_t *dest, const char*s, size_t *out_len);
-extern char16_t * strcpylen8to16 (char16_t *dest, const char*s, int length,
- size_t *out_len);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __CUTILS_STRING16_H */
diff --git a/libcutils/include_vndk/cutils/jstring.h b/libcutils/include_vndk/cutils/jstring.h
deleted file mode 120000
index f3fd546..0000000
--- a/libcutils/include_vndk/cutils/jstring.h
+++ /dev/null
@@ -1 +0,0 @@
-../../include/cutils/jstring.h
\ No newline at end of file
diff --git a/libcutils/strdup16to8.cpp b/libcutils/strdup16to8.cpp
deleted file mode 100644
index d89181e..0000000
--- a/libcutils/strdup16to8.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/* libs/cutils/strdup16to8.c
-**
-** Copyright 2006, 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 <cutils/jstring.h>
-
-#include <assert.h>
-#include <limits.h> /* for SIZE_MAX */
-#include <stdlib.h>
-
-
-/**
- * Given a UTF-16 string, compute the length of the corresponding UTF-8
- * string in bytes.
- */
-extern size_t strnlen16to8(const char16_t* utf16Str, size_t len)
-{
- size_t utf8Len = 0;
-
- /* A small note on integer overflow. The result can
- * potentially be as big as 3*len, which will overflow
- * for len > SIZE_MAX/3.
- *
- * Moreover, the result of a strnlen16to8 is typically used
- * to allocate a destination buffer to strncpy16to8 which
- * requires one more byte to terminate the UTF-8 copy, and
- * this is generally done by careless users by incrementing
- * the result without checking for integer overflows, e.g.:
- *
- * dst = malloc(strnlen16to8(utf16,len)+1)
- *
- * Due to this, the following code will try to detect
- * overflows, and never return more than (SIZE_MAX-1)
- * when it detects one. A careless user will try to malloc
- * SIZE_MAX bytes, which will return NULL which can at least
- * be detected appropriately.
- *
- * As far as I know, this function is only used by strndup16(),
- * but better be safe than sorry.
- */
-
- /* Fast path for the usual case where 3*len is < SIZE_MAX-1.
- */
- if (len < (SIZE_MAX-1)/3) {
- while (len != 0) {
- len--;
- unsigned int uic = *utf16Str++;
-
- if (uic > 0x07ff)
- utf8Len += 3;
- else if (uic > 0x7f || uic == 0)
- utf8Len += 2;
- else
- utf8Len++;
- }
- return utf8Len;
- }
-
- /* The slower but paranoid version */
- while (len != 0) {
- len--;
- unsigned int uic = *utf16Str++;
- size_t utf8Cur = utf8Len;
-
- if (uic > 0x07ff)
- utf8Len += 3;
- else if (uic > 0x7f || uic == 0)
- utf8Len += 2;
- else
- utf8Len++;
-
- if (utf8Len < utf8Cur) /* overflow detected */
- return SIZE_MAX-1;
- }
-
- /* don't return SIZE_MAX to avoid common user bug */
- if (utf8Len == SIZE_MAX)
- utf8Len = SIZE_MAX-1;
-
- return utf8Len;
-}
-
-
-/**
- * Convert a Java-Style UTF-16 string + length to a JNI-Style UTF-8 string.
- *
- * This basically means: embedded \0's in the UTF-16 string are encoded
- * as "0xc0 0x80"
- *
- * Make sure you allocate "utf8Str" with the result of strlen16to8() + 1,
- * not just "len".
- *
- * Please note, a terminated \0 is always added, so your result will always
- * be "strlen16to8() + 1" bytes long.
- */
-extern char* strncpy16to8(char* utf8Str, const char16_t* utf16Str, size_t len)
-{
- char* utf8cur = utf8Str;
-
- /* Note on overflows: We assume the user did check the result of
- * strnlen16to8() properly or at a minimum checked the result of
- * its malloc(SIZE_MAX) in case of overflow.
- */
- while (len != 0) {
- len--;
- unsigned int uic = *utf16Str++;
-
- if (uic > 0x07ff) {
- *utf8cur++ = (uic >> 12) | 0xe0;
- *utf8cur++ = ((uic >> 6) & 0x3f) | 0x80;
- *utf8cur++ = (uic & 0x3f) | 0x80;
- } else if (uic > 0x7f || uic == 0) {
- *utf8cur++ = (uic >> 6) | 0xc0;
- *utf8cur++ = (uic & 0x3f) | 0x80;
- } else {
- *utf8cur++ = uic;
-
- if (uic == 0) {
- break;
- }
- }
- }
-
- *utf8cur = '\0';
-
- return utf8Str;
-}
-
-/**
- * Convert a UTF-16 string to UTF-8.
- *
- */
-char * strndup16to8 (const char16_t* s, size_t n)
-{
- if (s == NULL) {
- return NULL;
- }
-
- size_t len = strnlen16to8(s, n);
-
- /* We are paranoid, and we check for SIZE_MAX-1
- * too since it is an overflow value for our
- * strnlen16to8 implementation.
- */
- if (len >= SIZE_MAX-1)
- return NULL;
-
- char* ret = static_cast<char*>(malloc(len + 1));
- if (ret == NULL)
- return NULL;
-
- strncpy16to8 (ret, s, n);
-
- return ret;
-}
diff --git a/libcutils/strdup8to16.cpp b/libcutils/strdup8to16.cpp
deleted file mode 100644
index d1e51b9..0000000
--- a/libcutils/strdup8to16.cpp
+++ /dev/null
@@ -1,215 +0,0 @@
-/* libs/cutils/strdup8to16.c
-**
-** Copyright 2006, 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 <cutils/jstring.h>
-
-#include <assert.h>
-#include <limits.h>
-#include <stdlib.h>
-
-/* See http://www.unicode.org/reports/tr22/ for discussion
- * on invalid sequences
- */
-
-#define UTF16_REPLACEMENT_CHAR 0xfffd
-
-/* Clever trick from Dianne that returns 1-4 depending on leading bit sequence*/
-#define UTF8_SEQ_LENGTH(ch) (((0xe5000000 >> (((ch) >> 3) & 0x1e)) & 3) + 1)
-
-/* note: macro expands to multiple lines */
-#define UTF8_SHIFT_AND_MASK(unicode, byte) \
- (unicode)<<=6; (unicode) |= (0x3f & (byte));
-
-#define UNICODE_UPPER_LIMIT 0x10fffd
-
-/**
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strdup8to16 (const char* s, size_t *out_len)
-{
- char16_t *ret;
- size_t len;
-
- if (s == NULL) return NULL;
-
- len = strlen8to16(s);
-
- // fail on overflow
- if (len && SIZE_MAX/len < sizeof(char16_t))
- return NULL;
-
- // no plus-one here. UTF-16 strings are not null terminated
- ret = (char16_t *) malloc (sizeof(char16_t) * len);
-
- return strcpy8to16 (ret, s, out_len);
-}
-
-/**
- * Like "strlen", but for strings encoded with Java's modified UTF-8.
- *
- * The value returned is the number of UTF-16 characters required
- * to represent this string.
- */
-extern size_t strlen8to16 (const char* utf8Str)
-{
- size_t len = 0;
- int ic;
- int expected = 0;
-
- while ((ic = *utf8Str++) != '\0') {
- /* bytes that start 0? or 11 are lead bytes and count as characters.*/
- /* bytes that start 10 are extention bytes and are not counted */
-
- if ((ic & 0xc0) == 0x80) {
- /* count the 0x80 extention bytes. if we have more than
- * expected, then start counting them because strcpy8to16
- * will insert UTF16_REPLACEMENT_CHAR's
- */
- expected--;
- if (expected < 0) {
- len++;
- }
- } else {
- len++;
- expected = UTF8_SEQ_LENGTH(ic) - 1;
-
- /* this will result in a surrogate pair */
- if (expected == 3) {
- len++;
- }
- }
- }
-
- return len;
-}
-
-
-
-/*
- * Retrieve the next UTF-32 character from a UTF-8 string.
- *
- * Stops at inner \0's
- *
- * Returns UTF16_REPLACEMENT_CHAR if an invalid sequence is encountered
- *
- * Advances "*pUtf8Ptr" to the start of the next character.
- */
-static inline uint32_t getUtf32FromUtf8(const char** pUtf8Ptr)
-{
- uint32_t ret;
- int seq_len;
- int i;
-
- /* Mask for leader byte for lengths 1, 2, 3, and 4 respectively*/
- static const unsigned char leaderMask[4] = {0xff, 0x1f, 0x0f, 0x07};
-
- /* Bytes that start with bits "10" are not leading characters. */
- if (((**pUtf8Ptr) & 0xc0) == 0x80) {
- (*pUtf8Ptr)++;
- return UTF16_REPLACEMENT_CHAR;
- }
-
- /* note we tolerate invalid leader 11111xxx here */
- seq_len = UTF8_SEQ_LENGTH(**pUtf8Ptr);
-
- ret = (**pUtf8Ptr) & leaderMask [seq_len - 1];
-
- if (**pUtf8Ptr == '\0') return ret;
-
- (*pUtf8Ptr)++;
- for (i = 1; i < seq_len ; i++, (*pUtf8Ptr)++) {
- if ((**pUtf8Ptr) == '\0') return UTF16_REPLACEMENT_CHAR;
- if (((**pUtf8Ptr) & 0xc0) != 0x80) return UTF16_REPLACEMENT_CHAR;
-
- UTF8_SHIFT_AND_MASK(ret, **pUtf8Ptr);
- }
-
- return ret;
-}
-
-
-/**
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strcpy8to16 (char16_t *utf16Str, const char*utf8Str,
- size_t *out_len)
-{
- char16_t *dest = utf16Str;
-
- while (*utf8Str != '\0') {
- uint32_t ret;
-
- ret = getUtf32FromUtf8(&utf8Str);
-
- if (ret <= 0xffff) {
- *dest++ = (char16_t) ret;
- } else if (ret <= UNICODE_UPPER_LIMIT) {
- /* Create surrogate pairs */
- /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */
-
- *dest++ = 0xd800 | ((ret - 0x10000) >> 10);
- *dest++ = 0xdc00 | ((ret - 0x10000) & 0x3ff);
- } else {
- *dest++ = UTF16_REPLACEMENT_CHAR;
- }
- }
-
- *out_len = dest - utf16Str;
-
- return utf16Str;
-}
-
-/**
- * length is the number of characters in the UTF-8 string.
- * out_len is an out parameter (which may not be null) containing the
- * length of the UTF-16 string (which may contain embedded \0's)
- */
-
-extern char16_t * strcpylen8to16 (char16_t *utf16Str, const char*utf8Str,
- int length, size_t *out_len)
-{
- /* TODO: Share more of this code with the method above. Only 2 lines changed. */
-
- char16_t *dest = utf16Str;
-
- const char *end = utf8Str + length; /* This line */
- while (utf8Str < end) { /* and this line changed. */
- uint32_t ret;
-
- ret = getUtf32FromUtf8(&utf8Str);
-
- if (ret <= 0xffff) {
- *dest++ = (char16_t) ret;
- } else if (ret <= UNICODE_UPPER_LIMIT) {
- /* Create surrogate pairs */
- /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */
-
- *dest++ = 0xd800 | ((ret - 0x10000) >> 10);
- *dest++ = 0xdc00 | ((ret - 0x10000) & 0x3ff);
- } else {
- *dest++ = UTF16_REPLACEMENT_CHAR;
- }
- }
-
- *out_len = dest - utf16Str;
-
- return utf16Str;
-}
diff --git a/libion/OWNERS b/libion/OWNERS
new file mode 100644
index 0000000..143ad2d
--- /dev/null
+++ b/libion/OWNERS
@@ -0,0 +1,2 @@
+sspatil@google.com
+hridya@google.com
diff --git a/libion/ion.c b/libion/ion.c
index b8de5a4..1ecfc78 100644
--- a/libion/ion.c
+++ b/libion/ion.c
@@ -152,6 +152,8 @@
ion_user_handle_t handle;
int ret;
+ if (!handle_fd) return -EINVAL;
+
if (!ion_is_legacy(fd)) {
struct ion_new_allocation_data data = {
.len = len,
@@ -201,6 +203,7 @@
int ret;
struct ion_heap_query query;
+ if (!cnt) return -EINVAL;
memset(&query, 0, sizeof(query));
ret = ion_ioctl(fd, ION_IOC_HEAP_QUERY, &query);
diff --git a/libion/tests/Android.bp b/libion/tests/Android.bp
index b3fcb3b..d3b4688 100644
--- a/libion/tests/Android.bp
+++ b/libion/tests/Android.bp
@@ -18,18 +18,15 @@
name: "ion-unit-tests",
cflags: [
"-g",
- "-Wall",
- "-Werror",
"-Wno-missing-field-initializers",
],
shared_libs: ["libion"],
srcs: [
- "ion_test_fixture.cpp",
"allocate_test.cpp",
- "formerly_valid_handle_test.cpp",
- "invalid_values_test.cpp",
- "map_test.cpp",
- "device_test.cpp",
"exit_test.cpp",
+ "heap_query.cpp",
+ "invalid_values_test.cpp",
+ "ion_test_fixture.cpp",
+ "map_test.cpp",
],
}
diff --git a/libion/tests/allocate_test.cpp b/libion/tests/allocate_test.cpp
index 3c4524e..5ed01bb 100644
--- a/libion/tests/allocate_test.cpp
+++ b/libion/tests/allocate_test.cpp
@@ -14,95 +14,106 @@
* limitations under the License.
*/
-#include <memory>
#include <sys/mman.h>
+#include <memory>
#include <gtest/gtest.h>
#include <ion/ion.h>
#include "ion_test_fixture.h"
-class Allocate : public IonAllHeapsTest {
-};
+class Allocate : public IonTest {};
-TEST_F(Allocate, Allocate)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, Allocate) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- ion_user_handle_t handle = 0;
- ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
- ASSERT_TRUE(handle != 0);
- ASSERT_EQ(0, ion_free(m_ionFd, handle));
+ int fd;
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &fd));
+ ASSERT_TRUE(fd != 0);
+ ASSERT_EQ(close(fd), 0); // free the buffer
}
}
}
-TEST_F(Allocate, AllocateCached)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, AllocateCached) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- ion_user_handle_t handle = 0;
- ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED, &handle));
- ASSERT_TRUE(handle != 0);
- ASSERT_EQ(0, ion_free(m_ionFd, handle));
+ int fd;
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), ION_FLAG_CACHED, &fd));
+ ASSERT_TRUE(fd != 0);
+ ASSERT_EQ(close(fd), 0); // free the buffer
}
}
}
-TEST_F(Allocate, AllocateCachedNeedsSync)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, AllocateCachedNeedsSync) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- ion_user_handle_t handle = 0;
- ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED_NEEDS_SYNC, &handle));
- ASSERT_TRUE(handle != 0);
- ASSERT_EQ(0, ion_free(m_ionFd, handle));
+ int fd;
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+ ION_FLAG_CACHED_NEEDS_SYNC, &fd));
+ ASSERT_TRUE(fd != 0);
+ ASSERT_EQ(close(fd), 0); // free the buffer
}
}
}
-TEST_F(Allocate, RepeatedAllocate)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Allocate, RepeatedAllocate) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- ion_user_handle_t handle = 0;
+ int fd;
for (unsigned int i = 0; i < 1024; i++) {
SCOPED_TRACE(::testing::Message() << "iteration " << i);
- ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
- ASSERT_TRUE(handle != 0);
- ASSERT_EQ(0, ion_free(m_ionFd, handle));
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &fd));
+ ASSERT_TRUE(fd != 0);
+ ASSERT_EQ(close(fd), 0); // free the buffer
}
}
}
}
-TEST_F(Allocate, Zeroed)
-{
+TEST_F(Allocate, Large) {
+ for (const auto& heap : ion_heaps) {
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+ int fd;
+ ASSERT_EQ(-ENOMEM,
+ ion_alloc_fd(ionfd, 3UL * 1024 * 1024 * 1024, 0, (1 << heap.heap_id), 0, &fd));
+ }
+}
+
+// Make sure all heaps always return zeroed pages
+TEST_F(Allocate, Zeroed) {
auto zeroes_ptr = std::make_unique<char[]>(4096);
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ for (const auto& heap : ion_heaps) {
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
int fds[16];
for (unsigned int i = 0; i < 16; i++) {
int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, 0, &map_fd));
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, 4096, 0, (1 << heap.heap_id), 0, &map_fd));
ASSERT_GE(map_fd, 0);
- void *ptr = NULL;
+ void* ptr = NULL;
ptr = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED, map_fd, 0);
ASSERT_TRUE(ptr != NULL);
@@ -116,13 +127,13 @@
ASSERT_EQ(0, close(fds[i]));
}
- int newIonFd = ion_open();
+ int new_ionfd = ion_open();
int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(newIonFd, 4096, 0, heapMask, 0, &map_fd));
+ ASSERT_EQ(0, ion_alloc_fd(new_ionfd, 4096, 0, (1 << heap.heap_id), 0, &map_fd));
ASSERT_GE(map_fd, 0);
- void *ptr = NULL;
+ void* ptr = NULL;
ptr = mmap(NULL, 4096, PROT_READ, MAP_SHARED, map_fd, 0);
ASSERT_TRUE(ptr != NULL);
@@ -130,14 +141,6 @@
ASSERT_EQ(0, munmap(ptr, 4096));
ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Allocate, Large)
-{
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- ion_user_handle_t handle = 0;
- ASSERT_EQ(-ENOMEM, ion_alloc(m_ionFd, 3UL*1024*1024*1024, 0, heapMask, 0, &handle));
+ ASSERT_EQ(0, ion_close(new_ionfd));
}
}
diff --git a/libion/tests/device_test.cpp b/libion/tests/device_test.cpp
deleted file mode 100644
index eb3f7b6..0000000
--- a/libion/tests/device_test.cpp
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * Copyright (C) 2013 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 <memory>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <linux/ion_test.h>
-
-#include <gtest/gtest.h>
-
-#include <ion/ion.h>
-
-#include "ion_test_fixture.h"
-
-#define ALIGN(x,y) (((x) + ((y) - 1)) & ~((y) - 1))
-
-class Device : public IonAllHeapsTest {
- public:
- virtual void SetUp();
- virtual void TearDown();
- int m_deviceFd;
- void readDMA(int fd, void *buf, size_t size);
- void writeDMA(int fd, void *buf, size_t size);
- void readKernel(int fd, void *buf, size_t size);
- void writeKernel(int fd, void *buf, size_t size);
- void blowCache();
- void dirtyCache(void *ptr, size_t size);
-};
-
-void Device::SetUp()
-{
- IonAllHeapsTest::SetUp();
- m_deviceFd = open("/dev/ion-test", O_RDONLY);
- ASSERT_GE(m_deviceFd, 0);
-}
-
-void Device::TearDown()
-{
- ASSERT_EQ(0, close(m_deviceFd));
- IonAllHeapsTest::TearDown();
-}
-
-void Device::readDMA(int fd, void *buf, size_t size)
-{
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
- struct ion_test_rw_data ion_test_rw_data = {
- .ptr = (uint64_t)buf,
- .offset = 0,
- .size = size,
- .write = 0,
- };
-
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_DMA_MAPPING, &ion_test_rw_data));
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::writeDMA(int fd, void *buf, size_t size)
-{
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
- struct ion_test_rw_data ion_test_rw_data = {
- .ptr = (uint64_t)buf,
- .offset = 0,
- .size = size,
- .write = 1,
- };
-
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_DMA_MAPPING, &ion_test_rw_data));
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::readKernel(int fd, void *buf, size_t size)
-{
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
- struct ion_test_rw_data ion_test_rw_data = {
- .ptr = (uint64_t)buf,
- .offset = 0,
- .size = size,
- .write = 0,
- };
-
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_KERNEL_MAPPING, &ion_test_rw_data));
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::writeKernel(int fd, void *buf, size_t size)
-{
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, fd));
- struct ion_test_rw_data ion_test_rw_data = {
- .ptr = (uint64_t)buf,
- .offset = 0,
- .size = size,
- .write = 1,
- };
-
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_KERNEL_MAPPING, &ion_test_rw_data));
- ASSERT_EQ(0, ioctl(m_deviceFd, ION_IOC_TEST_SET_FD, -1));
-}
-
-void Device::blowCache()
-{
- const size_t bigger_than_cache = 8*1024*1024;
- void *buf1 = malloc(bigger_than_cache);
- void *buf2 = malloc(bigger_than_cache);
- memset(buf1, 0xaa, bigger_than_cache);
- memcpy(buf2, buf1, bigger_than_cache);
- free(buf1);
- free(buf2);
-}
-
-void Device::dirtyCache(void *ptr, size_t size)
-{
- /* try to dirty cache lines */
- for (size_t i = size-1; i > 0; i--) {
- ((volatile char *)ptr)[i];
- ((char *)ptr)[i] = i;
- }
-}
-
-TEST_F(Device, KernelReadCached)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- for (int i = 0; i < 4096; i++)
- ((char *)ptr)[i] = i;
-
- ((char*)buf)[4096] = 0x12;
- readKernel(map_fd, buf, 4096);
- ASSERT_EQ(((char*)buf)[4096], 0x12);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)buf)[i]);
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, KernelWriteCached)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (int i = 0; i < 4096; i++)
- ((char *)buf)[i] = i;
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- dirtyCache(ptr, 4096);
-
- writeKernel(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, DMAReadCached)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- for (int i = 0; i < 4096; i++)
- ((char *)ptr)[i] = i;
-
- readDMA(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)buf)[i]);
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, DMAWriteCached)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (int i = 0; i < 4096; i++)
- ((char *)buf)[i] = i;
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- dirtyCache(ptr, 4096);
-
- writeDMA(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, KernelReadCachedNeedsSync)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- for (int i = 0; i < 4096; i++)
- ((char *)ptr)[i] = i;
-
- ((char*)buf)[4096] = 0x12;
- readKernel(map_fd, buf, 4096);
- ASSERT_EQ(((char*)buf)[4096], 0x12);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)buf)[i]);
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, KernelWriteCachedNeedsSync)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (int i = 0; i < 4096; i++)
- ((char *)buf)[i] = i;
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- dirtyCache(ptr, 4096);
-
- writeKernel(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, DMAReadCachedNeedsSync)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- for (int i = 0; i < 4096; i++)
- ((char *)ptr)[i] = i;
-
- ion_sync_fd(m_ionFd, map_fd);
-
- readDMA(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)buf)[i]);
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, DMAWriteCachedNeedsSync)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (int i = 0; i < 4096; i++)
- ((char *)buf)[i] = i;
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- dirtyCache(ptr, 4096);
-
- writeDMA(map_fd, buf, 4096);
-
- ion_sync_fd(m_ionFd, map_fd);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-TEST_F(Device, KernelRead)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = 0;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- for (int i = 0; i < 4096; i++)
- ((char *)ptr)[i] = i;
-
- ((char*)buf)[4096] = 0x12;
- readKernel(map_fd, buf, 4096);
- ASSERT_EQ(((char*)buf)[4096], 0x12);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)buf)[i]);
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, KernelWrite)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (int i = 0; i < 4096; i++)
- ((char *)buf)[i] = i;
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = 0;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- dirtyCache(ptr, 4096);
-
- writeKernel(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, DMARead)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = 0;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- for (int i = 0; i < 4096; i++)
- ((char *)ptr)[i] = i;
-
- readDMA(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)buf)[i]);
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, DMAWrite)
-{
- auto alloc_ptr = std::make_unique<char[]>(8192 + 1024);
- void *buf = (void *)(ALIGN((unsigned long)alloc_ptr.get(), 4096) + 1024);
-
- for (int i = 0; i < 4096; i++)
- ((char *)buf)[i] = i;
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = 0;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- dirtyCache(ptr, 4096);
-
- writeDMA(map_fd, buf, 4096);
-
- for (int i = 0; i < 4096; i++)
- ASSERT_EQ((char)i, ((char *)ptr)[i]) << i;
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
-
-TEST_F(Device, IsCached)
-{
- auto buf_ptr = std::make_unique<char[]>(4096);
- void *buf = buf_ptr.get();
-
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- int map_fd = -1;
- unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, flags, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- dirtyCache(ptr, 4096);
-
- readDMA(map_fd, buf, 4096);
-
- bool same = true;
- for (int i = 4096-16; i >= 0; i -= 16)
- if (((char *)buf)[i] != i)
- same = false;
- ASSERT_FALSE(same);
-
- ASSERT_EQ(0, munmap(ptr, 4096));
- ASSERT_EQ(0, close(map_fd));
- }
-}
diff --git a/libion/tests/exit_test.cpp b/libion/tests/exit_test.cpp
index cdd3e27..f312389 100644
--- a/libion/tests/exit_test.cpp
+++ b/libion/tests/exit_test.cpp
@@ -22,206 +22,206 @@
#include "ion_test_fixture.h"
-class Exit : public IonAllHeapsTest {
-};
+class Exit : public IonTest {};
-TEST_F(Exit, WithAlloc)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithAllocFd) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- ion_user_handle_t handle = 0;
+ EXPECT_EXIT(
+ {
+ int handle_fd = -1;
- ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
- ASSERT_TRUE(handle != 0);
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
+ ASSERT_EQ(0,
+ ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &handle_fd));
+ ASSERT_NE(-1, handle_fd);
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
}
}
}
-TEST_F(Exit, WithAllocFd)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
- for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- int handle_fd = -1;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &handle_fd));
- ASSERT_NE(-1, handle_fd);
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
- }
- }
-}
-
-TEST_F(Exit, WithRepeatedAllocFd)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithRepeatedAllocFd) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
for (unsigned int i = 0; i < 1024; i++) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- ASSERT_EXIT({
- int handle_fd = -1;
+ ASSERT_EXIT(
+ {
+ int handle_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &handle_fd));
- ASSERT_NE(-1, handle_fd);
- exit(0);
- }, ::testing::ExitedWithCode(0), "")
- << "failed on heap " << heapMask
- << " and size " << size
- << " on iteration " << i;
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0,
+ &handle_fd));
+ ASSERT_NE(-1, handle_fd);
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "")
+ << "failed on heap " << heap.name << ":" << heap.type << ":" << heap.heap_id
+ << " and size " << size << " on iteration " << i;
}
}
}
}
-
-TEST_F(Exit, WithMapping)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithMapping) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- int map_fd = -1;
+ EXPECT_EXIT(
+ {
+ int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &map_fd));
- ASSERT_GE(map_fd, 0);
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &map_fd));
+ ASSERT_GE(map_fd, 0);
- void *ptr;
- ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
- }
- }
-
-}
-
-TEST_F(Exit, WithPartialMapping)
-{
- static const size_t allocationSizes[] = {64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
- for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- int map_fd = -1;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- ASSERT_EQ(0, munmap(ptr, size / 2));
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
+ void* ptr;
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ ASSERT_TRUE(ptr != NULL);
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
}
}
}
-TEST_F(Exit, WithMappingCached)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithPartialMapping) {
+ static const size_t allocationSizes[] = {64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- int map_fd = -1;
+ EXPECT_EXIT(
+ {
+ int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED, &map_fd));
- ASSERT_GE(map_fd, 0);
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &map_fd));
+ ASSERT_GE(map_fd, 0);
- void *ptr;
- ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
- }
- }
+ void* ptr;
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ ASSERT_TRUE(ptr != NULL);
-}
-
-TEST_F(Exit, WithPartialMappingCached)
-{
- static const size_t allocationSizes[] = {64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
- for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- int map_fd = -1;
-
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED, &map_fd));
- ASSERT_GE(map_fd, 0);
-
- void *ptr;
- ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
-
- ASSERT_EQ(0, munmap(ptr, size / 2));
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
+ ASSERT_EQ(0, munmap(ptr, size / 2));
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
}
}
}
-TEST_F(Exit, WithMappingNeedsSync)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithMappingCached) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- int map_fd = -1;
+ EXPECT_EXIT(
+ {
+ int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC, &map_fd));
- ASSERT_GE(map_fd, 0);
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+ ION_FLAG_CACHED, &map_fd));
+ ASSERT_GE(map_fd, 0);
- void *ptr;
- ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
+ void* ptr;
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ ASSERT_TRUE(ptr != NULL);
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
}
}
-
}
-TEST_F(Exit, WithPartialMappingNeedsSync)
-{
- static const size_t allocationSizes[] = {64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Exit, WithPartialMappingCached) {
+ static const size_t allocationSizes[] = {64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
- EXPECT_EXIT({
- int map_fd = -1;
+ EXPECT_EXIT(
+ {
+ int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC, &map_fd));
- ASSERT_GE(map_fd, 0);
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+ ION_FLAG_CACHED, &map_fd));
+ ASSERT_GE(map_fd, 0);
- void *ptr;
- ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
- ASSERT_TRUE(ptr != NULL);
+ void* ptr;
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ ASSERT_TRUE(ptr != NULL);
- ASSERT_EQ(0, munmap(ptr, size / 2));
- exit(0);
- }, ::testing::ExitedWithCode(0), "");
+ ASSERT_EQ(0, munmap(ptr, size / 2));
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
+ }
+ }
+}
+
+TEST_F(Exit, WithMappingNeedsSync) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
+ for (size_t size : allocationSizes) {
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+ SCOPED_TRACE(::testing::Message() << "size " << size);
+ EXPECT_EXIT(
+ {
+ int map_fd = -1;
+
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+ ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC,
+ &map_fd));
+ ASSERT_GE(map_fd, 0);
+
+ void* ptr;
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ ASSERT_TRUE(ptr != NULL);
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
+ }
+ }
+}
+
+TEST_F(Exit, WithPartialMappingNeedsSync) {
+ static const size_t allocationSizes[] = {64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
+ for (size_t size : allocationSizes) {
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+ SCOPED_TRACE(::testing::Message() << "size " << size);
+ EXPECT_EXIT(
+ {
+ int map_fd = -1;
+
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id),
+ ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC,
+ &map_fd));
+ ASSERT_GE(map_fd, 0);
+
+ void* ptr;
+ ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ ASSERT_TRUE(ptr != NULL);
+
+ ASSERT_EQ(0, munmap(ptr, size / 2));
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
}
}
}
diff --git a/libion/tests/formerly_valid_handle_test.cpp b/libion/tests/formerly_valid_handle_test.cpp
deleted file mode 100644
index 01ab8f3..0000000
--- a/libion/tests/formerly_valid_handle_test.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2013 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 <sys/mman.h>
-
-#include <gtest/gtest.h>
-
-#include <ion/ion.h>
-
-#include "ion_test_fixture.h"
-
-class FormerlyValidHandle : public IonTest {
- public:
- virtual void SetUp();
- virtual void TearDown();
- ion_user_handle_t m_handle;
-};
-
-void FormerlyValidHandle::SetUp()
-{
- IonTest::SetUp();
- ASSERT_EQ(0, ion_alloc(m_ionFd, 4096, 0, 1/* ion_env->m_firstHeap */, 0, &m_handle));
- ASSERT_TRUE(m_handle != 0);
- ASSERT_EQ(0, ion_free(m_ionFd, m_handle));
-}
-
-void FormerlyValidHandle::TearDown()
-{
- m_handle = 0;
-}
-
-TEST_F(FormerlyValidHandle, free)
-{
- ASSERT_EQ(-EINVAL, ion_free(m_ionFd, m_handle));
-}
-
-TEST_F(FormerlyValidHandle, map)
-{
- int map_fd;
- unsigned char *ptr;
-
- ASSERT_EQ(-EINVAL, ion_map(m_ionFd, m_handle, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
-}
-
-TEST_F(FormerlyValidHandle, share)
-{
- int share_fd;
-
- ASSERT_EQ(-EINVAL, ion_share(m_ionFd, m_handle, &share_fd));
-}
diff --git a/libion/tests/heap_query.cpp b/libion/tests/heap_query.cpp
new file mode 100644
index 0000000..bad3bbf
--- /dev/null
+++ b/libion/tests/heap_query.cpp
@@ -0,0 +1,27 @@
+/*
+ * 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 <gtest/gtest.h>
+#include "ion_test_fixture.h"
+
+class HeapQuery : public IonTest {};
+
+TEST_F(HeapQuery, AtleastOneHeap) {
+ ASSERT_GT(ion_heaps.size(), 0);
+}
+
+// TODO: Check if we expect some of the default
+// heap types to be present on all devices.
diff --git a/libion/tests/invalid_values_test.cpp b/libion/tests/invalid_values_test.cpp
index 77fea17..48fcd72 100644
--- a/libion/tests/invalid_values_test.cpp
+++ b/libion/tests/invalid_values_test.cpp
@@ -16,171 +16,71 @@
#include <sys/mman.h>
+#include <memory>
+#include <vector>
+
#include <gtest/gtest.h>
#include <ion/ion.h>
-
#include "ion_test_fixture.h"
-class InvalidValues : public IonAllHeapsTest {
- public:
- virtual void SetUp();
- virtual void TearDown();
- ion_user_handle_t m_validHandle;
- int m_validShareFd;
- ion_user_handle_t const m_badHandle = -1;
-};
+class InvalidValues : public IonTest {};
-void InvalidValues::SetUp()
-{
- IonAllHeapsTest::SetUp();
- ASSERT_EQ(0, ion_alloc(m_ionFd, 4096, 0, m_firstHeap, 0, &m_validHandle))
- << m_ionFd << " " << m_firstHeap;
- ASSERT_TRUE(m_validHandle != 0);
- ASSERT_EQ(0, ion_share(m_ionFd, m_validHandle, &m_validShareFd));
-}
-
-void InvalidValues::TearDown()
-{
- ASSERT_EQ(0, ion_free(m_ionFd, m_validHandle));
- ASSERT_EQ(0, close(m_validShareFd));
- m_validHandle = 0;
- IonAllHeapsTest::TearDown();
-}
-
-TEST_F(InvalidValues, ion_close)
-{
+TEST_F(InvalidValues, ion_close) {
EXPECT_EQ(-EBADF, ion_close(-1));
}
-TEST_F(InvalidValues, ion_alloc)
-{
- ion_user_handle_t handle;
- /* invalid ion_fd */
- int ret = ion_alloc(0, 4096, 0, m_firstHeap, 0, &handle);
- EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
- /* invalid ion_fd */
- EXPECT_EQ(-EBADF, ion_alloc(-1, 4096, 0, m_firstHeap, 0, &handle));
- /* no heaps */
- EXPECT_EQ(-ENODEV, ion_alloc(m_ionFd, 4096, 0, 0, 0, &handle));
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- /* zero size */
- EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, 0, 0, heapMask, 0, &handle));
- /* too large size */
- EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, -1, 0, heapMask, 0, &handle));
- /* bad alignment */
- EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, 4096, -1, heapMask, 0, &handle));
- /* NULL handle */
- EXPECT_EQ(-EINVAL, ion_alloc(m_ionFd, 4096, 0, heapMask, 0, NULL));
- }
-}
-
-TEST_F(InvalidValues, ion_alloc_fd)
-{
+TEST_F(InvalidValues, ion_alloc_fd) {
int fd;
- /* invalid ion_fd */
- int ret = ion_alloc_fd(0, 4096, 0, m_firstHeap, 0, &fd);
- EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
- /* invalid ion_fd */
- EXPECT_EQ(-EBADF, ion_alloc_fd(-1, 4096, 0, m_firstHeap, 0, &fd));
- /* no heaps */
- EXPECT_EQ(-ENODEV, ion_alloc_fd(m_ionFd, 4096, 0, 0, 0, &fd));
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- /* zero size */
- EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, 0, 0, heapMask, 0, &fd));
- /* too large size */
- EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, -1, 0, heapMask, 0, &fd));
- /* bad alignment */
- EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, 4096, -1, heapMask, 0, &fd));
- /* NULL handle */
- EXPECT_EQ(-EINVAL, ion_alloc_fd(m_ionFd, 4096, 0, heapMask, 0, NULL));
+ // no heaps
+ EXPECT_EQ(-ENODEV, ion_alloc_fd(ionfd, 4096, 0, 0, 0, &fd));
+ for (const auto& heap : ion_heaps) {
+ // invalid ion_fd
+ int ret = ion_alloc_fd(0, 4096, 0, (1 << heap.heap_id), 0, &fd);
+ EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
+ // invalid ion_fd
+ EXPECT_EQ(-EBADF, ion_alloc_fd(-1, 4096, 0, (1 << heap.heap_id), 0, &fd));
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
+ // zero size
+ EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, 0, 0, (1 << heap.heap_id), 0, &fd));
+ // too large size
+ EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, -1, 0, (1 << heap.heap_id), 0, &fd));
+ // bad alignment
+ // TODO: Current userspace and kernel code completely ignores alignment. So this
+ // test is going to fail. We need to completely remove alignment from the API.
+ // All memory by default is always page aligned. OR actually pass the alignment
+ // down into the kernel and make kernel respect the alignment.
+ // EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, 4096, -1, (1 << heap.heap_id), 0, &fd));
+
+ // NULL fd
+ EXPECT_EQ(-EINVAL, ion_alloc_fd(ionfd, 4096, 0, (1 << heap.heap_id), 0, nullptr));
}
}
-TEST_F(InvalidValues, ion_free)
-{
- /* invalid ion fd */
- int ret = ion_free(0, m_validHandle);
- EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
- /* invalid ion fd */
- EXPECT_EQ(-EBADF, ion_free(-1, m_validHandle));
- /* zero handle */
- EXPECT_EQ(-EINVAL, ion_free(m_ionFd, 0));
- /* bad handle */
- EXPECT_EQ(-EINVAL, ion_free(m_ionFd, m_badHandle));
+TEST_F(InvalidValues, ion_query_heap_cnt) {
+ // NULL count
+ EXPECT_EQ(-EINVAL, ion_query_heap_cnt(ionfd, nullptr));
+
+ int heap_count;
+ // bad fd
+ EXPECT_EQ(-EBADF, ion_query_heap_cnt(-1, &heap_count));
}
-TEST_F(InvalidValues, ion_map)
-{
- int map_fd;
- unsigned char *ptr;
+TEST_F(InvalidValues, ion_query_get_heaps) {
+ int heap_count;
+ ASSERT_EQ(0, ion_query_heap_cnt(ionfd, &heap_count));
+ ASSERT_GT(heap_count, 0);
- /* invalid ion fd */
- int ret = ion_map(0, m_validHandle, 4096, PROT_READ, 0, 0, &ptr, &map_fd);
- EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
- /* invalid ion fd */
- EXPECT_EQ(-EBADF, ion_map(-1, m_validHandle, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
- /* zero handle */
- EXPECT_EQ(-EINVAL, ion_map(m_ionFd, 0, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
- /* bad handle */
- EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_badHandle, 4096, PROT_READ, 0, 0, &ptr, &map_fd));
- /* zero length */
- EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 0, PROT_READ, 0, 0, &ptr, &map_fd));
- /* bad prot */
- EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, -1, 0, 0, &ptr, &map_fd));
- /* bad offset */
- EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, PROT_READ, 0, -1, &ptr, &map_fd));
- /* NULL ptr */
- EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, PROT_READ, 0, 0, NULL, &map_fd));
- /* NULL map_fd */
- EXPECT_EQ(-EINVAL, ion_map(m_ionFd, m_validHandle, 4096, PROT_READ, 0, 0, &ptr, NULL));
-}
+ // nullptr buffers, still returns success but without
+ // the ion_heap_data.
+ EXPECT_EQ(0, ion_query_get_heaps(ionfd, heap_count, nullptr));
-TEST_F(InvalidValues, ion_share)
-{
- int share_fd;
+ std::unique_ptr<struct ion_heap_data[]> heaps =
+ std::make_unique<struct ion_heap_data[]>(heap_count);
+ // bad fd
+ EXPECT_EQ(-EBADF, ion_query_get_heaps(-1, heap_count, heaps.get()));
- /* invalid ion fd */
- int ret = ion_share(0, m_validHandle, &share_fd);
- EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
- /* invalid ion fd */
- EXPECT_EQ(-EBADF, ion_share(-1, m_validHandle, &share_fd));
- /* zero handle */
- EXPECT_EQ(-EINVAL, ion_share(m_ionFd, 0, &share_fd));
- /* bad handle */
- EXPECT_EQ(-EINVAL, ion_share(m_ionFd, m_badHandle, &share_fd));
- /* NULL share_fd */
- EXPECT_EQ(-EINVAL, ion_share(m_ionFd, m_validHandle, NULL));
-}
-
-TEST_F(InvalidValues, ion_import)
-{
- ion_user_handle_t handle;
-
- /* invalid ion fd */
- int ret = ion_import(0, m_validShareFd, &handle);
- EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
- /* invalid ion fd */
- EXPECT_EQ(-EBADF, ion_import(-1, m_validShareFd, &handle));
- /* bad share_fd */
- EXPECT_EQ(-EINVAL, ion_import(m_ionFd, 0, &handle));
- /* invalid share_fd */
- EXPECT_EQ(-EBADF, ion_import(m_ionFd, -1, &handle));
- /* NULL handle */
- EXPECT_EQ(-EINVAL, ion_import(m_ionFd, m_validShareFd, NULL));
-}
-
-TEST_F(InvalidValues, ion_sync_fd)
-{
- /* invalid ion fd */
- int ret = ion_sync_fd(0, m_validShareFd);
- EXPECT_TRUE(ret == -EINVAL || ret == -ENOTTY);
- /* invalid ion fd */
- EXPECT_EQ(-EBADF, ion_sync_fd(-1, m_validShareFd));
- /* bad share_fd */
- EXPECT_EQ(-EINVAL, ion_sync_fd(m_ionFd, 0));
- /* invalid share_fd */
- EXPECT_EQ(-EBADF, ion_sync_fd(m_ionFd, -1));
+ // invalid heap data pointer
+ EXPECT_EQ(-EFAULT, ion_query_get_heaps(ionfd, heap_count, reinterpret_cast<void*>(0xdeadf00d)));
}
diff --git a/libion/tests/ion_4.12.h b/libion/tests/ion_4.12.h
new file mode 100644
index 0000000..614510c
--- /dev/null
+++ b/libion/tests/ion_4.12.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ *** This header was automatically generated from a Linux kernel header
+ *** of the same name, to make information necessary for userspace to
+ *** call into the kernel available to libc. It contains only constants,
+ *** structures, and macros generated from the original header, and thus,
+ *** contains no copyrightable information.
+ ***
+ *** To edit the content of this header, modify the corresponding
+ *** source file (e.g. under external/kernel-headers/original/) then
+ *** run bionic/libc/kernel/tools/update_all.py
+ ***
+ *** Any manual change here will be lost the next time this script will
+ *** be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _UAPI_LINUX_ION_NEW_H
+#define _UAPI_LINUX_ION_NEW_H
+#include <linux/ioctl.h>
+#include <linux/types.h>
+#define ION_NUM_HEAP_IDS (sizeof(unsigned int) * 8)
+struct ion_new_allocation_data {
+ __u64 len;
+ __u32 heap_id_mask;
+ __u32 flags;
+ __u32 fd;
+ __u32 unused;
+};
+#define MAX_HEAP_NAME 32
+struct ion_heap_data {
+ char name[MAX_HEAP_NAME];
+ __u32 type;
+ __u32 heap_id;
+ __u32 reserved0;
+ __u32 reserved1;
+ __u32 reserved2;
+};
+struct ion_heap_query {
+ __u32 cnt;
+ __u32 reserved0;
+ __u64 heaps;
+ __u32 reserved1;
+ __u32 reserved2;
+};
+#define ION_IOC_MAGIC 'I'
+#define ION_IOC_NEW_ALLOC _IOWR(ION_IOC_MAGIC, 0, struct ion_new_allocation_data)
+#define ION_IOC_HEAP_QUERY _IOWR(ION_IOC_MAGIC, 8, struct ion_heap_query)
+#endif
diff --git a/libion/tests/ion_test_fixture.cpp b/libion/tests/ion_test_fixture.cpp
index e20c730..935fe5c 100644
--- a/libion/tests/ion_test_fixture.cpp
+++ b/libion/tests/ion_test_fixture.cpp
@@ -15,59 +15,26 @@
*/
#include <gtest/gtest.h>
-
#include <ion/ion.h>
#include "ion_test_fixture.h"
-IonTest::IonTest() : m_ionFd(-1)
-{
-}
+IonTest::IonTest() : ionfd(-1), ion_heaps() {}
void IonTest::SetUp() {
- m_ionFd = ion_open();
- ASSERT_GE(m_ionFd, 0);
+ ionfd = ion_open();
+ ASSERT_GE(ionfd, 0);
+
+ int heap_count;
+ int ret = ion_query_heap_cnt(ionfd, &heap_count);
+ ASSERT_EQ(ret, 0);
+ ASSERT_GT(heap_count, 0);
+
+ ion_heaps.resize(heap_count, {});
+ ret = ion_query_get_heaps(ionfd, heap_count, ion_heaps.data());
+ ASSERT_EQ(ret, 0);
}
void IonTest::TearDown() {
- ion_close(m_ionFd);
-}
-
-IonAllHeapsTest::IonAllHeapsTest() :
- m_firstHeap(0),
- m_lastHeap(0),
- m_allHeaps()
-{
-}
-
-void IonAllHeapsTest::SetUp() {
- int fd = ion_open();
- ASSERT_GE(fd, 0);
-
- for (int i = 1; i != 0; i <<= 1) {
- ion_user_handle_t handle = 0;
- int ret;
- ret = ion_alloc(fd, 4096, 0, i, 0, &handle);
- if (ret == 0 && handle != 0) {
- ion_free(fd, handle);
- if (!m_firstHeap) {
- m_firstHeap = i;
- }
- m_lastHeap = i;
- m_allHeaps.push_back(i);
- } else {
- ASSERT_EQ(-ENODEV, ret);
- }
- }
- ion_close(fd);
-
- EXPECT_NE(0U, m_firstHeap);
- EXPECT_NE(0U, m_lastHeap);
-
- RecordProperty("Heaps", m_allHeaps.size());
- IonTest::SetUp();
-}
-
-void IonAllHeapsTest::TearDown() {
- IonTest::TearDown();
+ ion_close(ionfd);
}
diff --git a/libion/tests/ion_test_fixture.h b/libion/tests/ion_test_fixture.h
index 4098214..4f254b8 100644
--- a/libion/tests/ion_test_fixture.h
+++ b/libion/tests/ion_test_fixture.h
@@ -18,29 +18,19 @@
#define ION_TEST_FIXTURE_H_
#include <gtest/gtest.h>
+#include <vector>
+#include "ion_4.12.h"
using ::testing::Test;
class IonTest : public virtual Test {
- public:
+ public:
IonTest();
- virtual ~IonTest() {};
- virtual void SetUp();
- virtual void TearDown();
- int m_ionFd;
-};
-
-class IonAllHeapsTest : public IonTest {
- public:
- IonAllHeapsTest();
- virtual ~IonAllHeapsTest() {};
+ virtual ~IonTest(){};
virtual void SetUp();
virtual void TearDown();
-
- unsigned int m_firstHeap;
- unsigned int m_lastHeap;
-
- std::vector<unsigned int> m_allHeaps;
+ int ionfd;
+ std::vector<struct ion_heap_data> ion_heaps;
};
#endif /* ION_TEST_FIXTURE_H_ */
diff --git a/libion/tests/map_test.cpp b/libion/tests/map_test.cpp
index c006dc8..f1b47b7 100644
--- a/libion/tests/map_test.cpp
+++ b/libion/tests/map_test.cpp
@@ -15,61 +15,30 @@
*/
#include <sys/mman.h>
+#include <unistd.h>
#include <gtest/gtest.h>
#include <ion/ion.h>
-
#include "ion_test_fixture.h"
-class Map : public IonAllHeapsTest {
-};
+class Map : public IonTest {};
-TEST_F(Map, MapHandle)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Map, MapFd) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
- SCOPED_TRACE(::testing::Message() << "size " << size);
- ion_user_handle_t handle = 0;
-
- ASSERT_EQ(0, ion_alloc(m_ionFd, size, 0, heapMask, 0, &handle));
- ASSERT_TRUE(handle != 0);
-
- int map_fd = -1;
- unsigned char *ptr = NULL;
- ASSERT_EQ(0, ion_map(m_ionFd, handle, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0, &ptr, &map_fd));
- ASSERT_TRUE(ptr != NULL);
- ASSERT_GE(map_fd, 0);
-
- ASSERT_EQ(0, close(map_fd));
-
- ASSERT_EQ(0, ion_free(m_ionFd, handle));
-
- memset(ptr, 0xaa, size);
-
- ASSERT_EQ(0, munmap(ptr, size));
- }
- }
-}
-
-TEST_F(Map, MapFd)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
- for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, 0, &map_fd));
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), 0, &map_fd));
ASSERT_GE(map_fd, 0);
- void *ptr;
+ void* ptr;
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
ASSERT_TRUE(ptr != NULL);
-
ASSERT_EQ(0, close(map_fd));
memset(ptr, 0xaa, size);
@@ -79,53 +48,51 @@
}
}
-TEST_F(Map, MapOffset)
-{
- for (unsigned int heapMask : m_allHeaps) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+TEST_F(Map, MapOffset) {
+ for (const auto& heap : ion_heaps) {
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
int map_fd = -1;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, PAGE_SIZE * 2, 0, heapMask, 0, &map_fd));
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, getpagesize() * 2, 0, (1 << heap.heap_id), 0, &map_fd));
ASSERT_GE(map_fd, 0);
- unsigned char *ptr;
- ptr = (unsigned char *)mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
+ unsigned char* ptr;
+ ptr = (unsigned char*)mmap(NULL, getpagesize() * 2, PROT_READ | PROT_WRITE, MAP_SHARED,
+ map_fd, 0);
ASSERT_TRUE(ptr != NULL);
- memset(ptr, 0, PAGE_SIZE);
- memset(ptr + PAGE_SIZE, 0xaa, PAGE_SIZE);
+ memset(ptr, 0, getpagesize());
+ memset(ptr + getpagesize(), 0xaa, getpagesize());
- ASSERT_EQ(0, munmap(ptr, PAGE_SIZE * 2));
+ ASSERT_EQ(0, munmap(ptr, getpagesize() * 2));
- ptr = (unsigned char *)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, PAGE_SIZE);
+ ptr = (unsigned char*)mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, map_fd,
+ getpagesize());
ASSERT_TRUE(ptr != NULL);
-
ASSERT_EQ(ptr[0], 0xaa);
- ASSERT_EQ(ptr[PAGE_SIZE - 1], 0xaa);
-
- ASSERT_EQ(0, munmap(ptr, PAGE_SIZE));
-
+ ASSERT_EQ(ptr[getpagesize() - 1], 0xaa);
+ ASSERT_EQ(0, munmap(ptr, getpagesize()));
ASSERT_EQ(0, close(map_fd));
}
}
-TEST_F(Map, MapCached)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Map, MapCached) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
int map_fd = -1;
unsigned int flags = ION_FLAG_CACHED;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, flags, &map_fd));
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), flags, &map_fd));
ASSERT_GE(map_fd, 0);
- void *ptr;
+ void* ptr;
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
ASSERT_TRUE(ptr != NULL);
-
ASSERT_EQ(0, close(map_fd));
memset(ptr, 0xaa, size);
@@ -135,23 +102,22 @@
}
}
-TEST_F(Map, MapCachedNeedsSync)
-{
- static const size_t allocationSizes[] = {4*1024, 64*1024, 1024*1024, 2*1024*1024};
- for (unsigned int heapMask : m_allHeaps) {
+TEST_F(Map, MapCachedNeedsSync) {
+ static const size_t allocationSizes[] = {4 * 1024, 64 * 1024, 1024 * 1024, 2 * 1024 * 1024};
+ for (const auto& heap : ion_heaps) {
for (size_t size : allocationSizes) {
- SCOPED_TRACE(::testing::Message() << "heap " << heapMask);
+ SCOPED_TRACE(::testing::Message()
+ << "heap:" << heap.name << ":" << heap.type << ":" << heap.heap_id);
SCOPED_TRACE(::testing::Message() << "size " << size);
int map_fd = -1;
unsigned int flags = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
- ASSERT_EQ(0, ion_alloc_fd(m_ionFd, size, 0, heapMask, flags, &map_fd));
+ ASSERT_EQ(0, ion_alloc_fd(ionfd, size, 0, (1 << heap.heap_id), flags, &map_fd));
ASSERT_GE(map_fd, 0);
- void *ptr;
+ void* ptr;
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, map_fd, 0);
ASSERT_TRUE(ptr != NULL);
-
ASSERT_EQ(0, close(map_fd));
memset(ptr, 0xaa, size);
diff --git a/liblog/logger.h b/liblog/logger.h
index 1f632c0..accb6e7 100644
--- a/liblog/logger.h
+++ b/liblog/logger.h
@@ -17,7 +17,6 @@
#pragma once
#include <stdatomic.h>
-#include <stdbool.h>
#include <cutils/list.h>
#include <log/log.h>
diff --git a/liblog/logprint.cpp b/liblog/logprint.cpp
index 6b5ea4c..3a54445 100644
--- a/liblog/logprint.cpp
+++ b/liblog/logprint.cpp
@@ -26,7 +26,6 @@
#ifndef __MINGW32__
#include <pwd.h>
#endif
-#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
index ba27fd7..eaac97f 100644
--- a/liblog/pmsg_reader.cpp
+++ b/liblog/pmsg_reader.cpp
@@ -17,7 +17,6 @@
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
-#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
diff --git a/liblog/pmsg_writer.cpp b/liblog/pmsg_writer.cpp
index e851100..4632b32 100644
--- a/liblog/pmsg_writer.cpp
+++ b/liblog/pmsg_writer.cpp
@@ -20,7 +20,6 @@
#include <errno.h>
#include <fcntl.h>
-#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
diff --git a/liblog/stderr_write.cpp b/liblog/stderr_write.cpp
index e324a7c..e76673f 100644
--- a/liblog/stderr_write.cpp
+++ b/liblog/stderr_write.cpp
@@ -27,7 +27,6 @@
*/
#include <errno.h>
-#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
index eb53e57..7bba599 100644
--- a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
+++ b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
@@ -17,6 +17,7 @@
#include <inttypes.h>
#include <linux/dma-buf.h>
#include <poll.h>
+#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
@@ -230,7 +231,7 @@
DmaBufTester() : ion_fd(ion_open()), ion_heap_mask(get_ion_heap_mask()) {}
~DmaBufTester() {
- if (is_valid()) {
+ if (ion_fd >= 0) {
ion_close(ion_fd);
}
}
@@ -241,12 +242,16 @@
int fd;
int err = ion_alloc_fd(ion_fd, size, 0, ion_heap_mask, 0, &fd);
if (err < 0) {
- return unique_fd{err};
+ printf("Failed ion_alloc_fd, return value: %d\n", err);
+ return unique_fd{};
}
if (!name.empty()) {
- err = ioctl(fd, DMA_BUF_SET_NAME, name.c_str());
- if (err < 0) return unique_fd{-errno};
+ if (ioctl(fd, DMA_BUF_SET_NAME, name.c_str()) == -1) {
+ printf("Failed ioctl(DMA_BUF_SET_NAME): %s\n", strerror(errno));
+ close(fd);
+ return unique_fd{};
+ }
}
return unique_fd{fd};
@@ -306,7 +311,7 @@
return ret;
}
- unique_fd ion_fd;
+ int ion_fd;
const int ion_heap_mask;
};
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index a2824d1..78da46c 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -3,6 +3,7 @@
cflags: [
"-Werror",
],
+ vendor_available: true,
recovery_available: true,
srcs: [
"libmodprobe.cpp",
diff --git a/libmodprobe/OWNERS b/libmodprobe/OWNERS
new file mode 100644
index 0000000..4b770b1
--- /dev/null
+++ b/libmodprobe/OWNERS
@@ -0,0 +1,2 @@
+tomcherry@google.com
+smuckle@google.com
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index 0ec766a..dcb4ffb 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -16,6 +16,7 @@
#pragma once
+#include <set>
#include <string>
#include <unordered_map>
#include <vector>
@@ -25,12 +26,21 @@
Modprobe(const std::vector<std::string>&);
bool LoadListedModules();
- bool LoadWithAliases(const std::string& module_name, bool strict);
+ bool LoadWithAliases(const std::string& module_name, bool strict,
+ const std::string& parameters = "");
+ bool Remove(const std::string& module_name);
+ std::vector<std::string> ListModules(const std::string& pattern);
+ bool GetAllDependencies(const std::string& module, std::vector<std::string>* pre_dependencies,
+ std::vector<std::string>* dependencies,
+ std::vector<std::string>* post_dependencies);
+ void EnableBlacklist(bool enable);
+ void EnableVerbose(bool enable);
private:
std::string MakeCanonical(const std::string& module_path);
- bool InsmodWithDeps(const std::string& module_name);
- bool Insmod(const std::string& path_name);
+ bool InsmodWithDeps(const std::string& module_name, const std::string& parameters);
+ bool Insmod(const std::string& path_name, const std::string& parameters);
+ bool Rmmod(const std::string& module_name);
std::vector<std::string> GetDependencies(const std::string& module);
bool ModuleExists(const std::string& module_name);
@@ -39,6 +49,7 @@
bool ParseSoftdepCallback(const std::vector<std::string>& args);
bool ParseLoadCallback(const std::vector<std::string>& args);
bool ParseOptionsCallback(const std::vector<std::string>& args);
+ bool ParseBlacklistCallback(const std::vector<std::string>& args);
void ParseCfg(const std::string& cfg, std::function<bool(const std::vector<std::string>&)> f);
std::vector<std::pair<std::string, std::string>> module_aliases_;
@@ -47,4 +58,6 @@
std::vector<std::pair<std::string, std::string>> module_post_softdep_;
std::vector<std::string> module_load_;
std::unordered_map<std::string, std::string> module_options_;
+ std::set<std::string> module_blacklist_;
+ bool blacklist_enabled = false;
};
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 01cf2e3..73ae15b 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -194,6 +194,31 @@
return true;
}
+bool Modprobe::ParseBlacklistCallback(const std::vector<std::string>& args) {
+ auto it = args.begin();
+ const std::string& type = *it++;
+
+ if (type != "blacklist") {
+ LOG(ERROR) << "non-blacklist line encountered in modules.blacklist";
+ return false;
+ }
+
+ if (args.size() != 2) {
+ LOG(ERROR) << "lines in modules.blacklist must have exactly 2 entries, not " << args.size();
+ return false;
+ }
+
+ const std::string& module = *it++;
+
+ const std::string& canonical_name = MakeCanonical(module);
+ if (canonical_name.empty()) {
+ return false;
+ }
+ this->module_blacklist_.emplace(canonical_name);
+
+ return true;
+}
+
void Modprobe::ParseCfg(const std::string& cfg,
std::function<bool(const std::vector<std::string>&)> f) {
std::string cfg_contents;
@@ -231,6 +256,23 @@
auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
ParseCfg(base_path + "/modules.options", options_callback);
+
+ auto blacklist_callback = std::bind(&Modprobe::ParseBlacklistCallback, this, _1);
+ ParseCfg(base_path + "/modules.blacklist", blacklist_callback);
+ }
+
+ android::base::SetMinimumLogSeverity(android::base::INFO);
+}
+
+void Modprobe::EnableBlacklist(bool enable) {
+ blacklist_enabled = enable;
+}
+
+void Modprobe::EnableVerbose(bool enable) {
+ if (enable) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+ } else {
+ android::base::SetMinimumLogSeverity(android::base::INFO);
}
}
@@ -242,7 +284,7 @@
return it->second;
}
-bool Modprobe::InsmodWithDeps(const std::string& module_name) {
+bool Modprobe::InsmodWithDeps(const std::string& module_name, const std::string& parameters) {
if (module_name.empty()) {
LOG(ERROR) << "Need valid module name, given: " << module_name;
return false;
@@ -256,11 +298,8 @@
// load module dependencies in reverse order
for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep) {
- const std::string& canonical_name = MakeCanonical(*dep);
- if (canonical_name.empty()) {
- return false;
- }
- if (!LoadWithAliases(canonical_name, true)) {
+ LOG(VERBOSE) << "Loading hard dep for '" << module_name << "': " << *dep;
+ if (!LoadWithAliases(*dep, true)) {
return false;
}
}
@@ -268,18 +307,20 @@
// try to load soft pre-dependencies
for (const auto& [module, softdep] : module_pre_softdep_) {
if (module_name == module) {
+ LOG(VERBOSE) << "Loading soft pre-dep for '" << module << "': " << softdep;
LoadWithAliases(softdep, false);
}
}
// load target module itself with args
- if (!Insmod(dependencies[0])) {
+ if (!Insmod(dependencies[0], parameters)) {
return false;
}
// try to load soft post-dependencies
for (const auto& [module, softdep] : module_post_softdep_) {
if (module_name == module) {
+ LOG(VERBOSE) << "Loading soft post-dep for '" << module << "': " << softdep;
LoadWithAliases(softdep, false);
}
}
@@ -287,25 +328,27 @@
return true;
}
-bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict) {
- std::set<std::string> modules_to_load = {module_name};
+bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict,
+ const std::string& parameters) {
+ std::set<std::string> modules_to_load = {MakeCanonical(module_name)};
bool module_loaded = false;
// use aliases to expand list of modules to load (multiple modules
// may alias themselves to the requested name)
for (const auto& [alias, aliased_module] : module_aliases_) {
if (fnmatch(alias.c_str(), module_name.c_str(), 0) != 0) continue;
+ LOG(VERBOSE) << "Found alias for '" << module_name << "': '" << aliased_module;
modules_to_load.emplace(aliased_module);
}
// attempt to load all modules aliased to this name
for (const auto& module : modules_to_load) {
if (!ModuleExists(module)) continue;
- if (InsmodWithDeps(module)) module_loaded = true;
+ if (InsmodWithDeps(module, parameters)) module_loaded = true;
}
if (strict && !module_loaded) {
- LOG(ERROR) << "LoadWithAliases did not find a module for " << module_name;
+ LOG(ERROR) << "LoadWithAliases was unable to load " << module_name;
return false;
}
return true;
@@ -319,3 +362,64 @@
}
return true;
}
+
+bool Modprobe::Remove(const std::string& module_name) {
+ auto dependencies = GetDependencies(MakeCanonical(module_name));
+ if (dependencies.empty()) {
+ LOG(ERROR) << "Empty dependencies for module " << module_name;
+ return false;
+ }
+ if (!Rmmod(dependencies[0])) {
+ return false;
+ }
+ for (auto dep = dependencies.begin() + 1; dep != dependencies.end(); ++dep) {
+ Rmmod(*dep);
+ }
+ return true;
+}
+
+std::vector<std::string> Modprobe::ListModules(const std::string& pattern) {
+ std::vector<std::string> rv;
+ for (const auto& [module, deps] : module_deps_) {
+ // Attempt to match both the canonical module name and the module filename.
+ if (!fnmatch(pattern.c_str(), module.c_str(), 0)) {
+ rv.emplace_back(module);
+ } else if (!fnmatch(pattern.c_str(), basename(deps[0].c_str()), 0)) {
+ rv.emplace_back(deps[0]);
+ }
+ }
+ return rv;
+}
+
+bool Modprobe::GetAllDependencies(const std::string& module,
+ std::vector<std::string>* pre_dependencies,
+ std::vector<std::string>* dependencies,
+ std::vector<std::string>* post_dependencies) {
+ std::string canonical_name = MakeCanonical(module);
+ if (pre_dependencies) {
+ pre_dependencies->clear();
+ for (const auto& [it_module, it_softdep] : module_pre_softdep_) {
+ if (canonical_name == it_module) {
+ pre_dependencies->emplace_back(it_softdep);
+ }
+ }
+ }
+ if (dependencies) {
+ dependencies->clear();
+ auto hard_deps = GetDependencies(canonical_name);
+ if (hard_deps.empty()) {
+ return false;
+ }
+ for (auto dep = hard_deps.rbegin(); dep != hard_deps.rend(); dep++) {
+ dependencies->emplace_back(*dep);
+ }
+ }
+ if (post_dependencies) {
+ for (const auto& [it_module, it_softdep] : module_post_softdep_) {
+ if (canonical_name == it_module) {
+ post_dependencies->emplace_back(it_softdep);
+ }
+ }
+ }
+ return true;
+}
diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp
index 5f3a04d..2efcac2 100644
--- a/libmodprobe/libmodprobe_ext.cpp
+++ b/libmodprobe/libmodprobe_ext.cpp
@@ -22,7 +22,7 @@
#include <modprobe/modprobe.h>
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
android::base::unique_fd fd(
TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
if (fd == -1) {
@@ -35,6 +35,9 @@
if (options_iter != module_options_.end()) {
options = options_iter->second;
}
+ if (!parameters.empty()) {
+ options = options + " " + parameters;
+ }
LOG(INFO) << "Loading module " << path_name << " with args \"" << options << "\"";
int ret = syscall(__NR_finit_module, fd.get(), options.c_str(), 0);
@@ -51,17 +54,32 @@
return true;
}
+bool Modprobe::Rmmod(const std::string& module_name) {
+ int ret = syscall(__NR_delete_module, MakeCanonical(module_name).c_str(), O_NONBLOCK);
+ if (ret != 0) {
+ PLOG(ERROR) << "Failed to remove module '" << module_name << "'";
+ return false;
+ }
+ return true;
+}
+
bool Modprobe::ModuleExists(const std::string& module_name) {
struct stat fileStat;
+ if (blacklist_enabled && module_blacklist_.count(module_name)) {
+ LOG(INFO) << "module " << module_name << " is blacklisted";
+ return false;
+ }
auto deps = GetDependencies(module_name);
if (deps.empty()) {
// missing deps can happen in the case of an alias
return false;
}
if (stat(deps.front().c_str(), &fileStat)) {
+ LOG(INFO) << "module " << module_name << " does not exist";
return false;
}
if (!S_ISREG(fileStat.st_mode)) {
+ LOG(INFO) << "module " << module_name << " is not a regular file";
return false;
}
return true;
diff --git a/libmodprobe/libmodprobe_ext_test.cpp b/libmodprobe/libmodprobe_ext_test.cpp
index 0f073cb..7d817b1 100644
--- a/libmodprobe/libmodprobe_ext_test.cpp
+++ b/libmodprobe/libmodprobe_ext_test.cpp
@@ -29,7 +29,7 @@
#include "libmodprobe_test.h"
-bool Modprobe::Insmod(const std::string& path_name) {
+bool Modprobe::Insmod(const std::string& path_name, const std::string& parameters) {
auto deps = GetDependencies(MakeCanonical(path_name));
if (deps.empty()) {
return false;
@@ -47,12 +47,29 @@
if (options_iter != module_options_.end()) {
options = " " + options_iter->second;
}
+ if (!parameters.empty()) {
+ options = options + " " + parameters;
+ }
+
modules_loaded.emplace_back(path_name + options);
return true;
}
+bool Modprobe::Rmmod(const std::string& module_name) {
+ for (auto it = modules_loaded.begin(); it != modules_loaded.end(); it++) {
+ if (*it == module_name) {
+ modules_loaded.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
bool Modprobe::ModuleExists(const std::string& module_name) {
auto deps = GetDependencies(module_name);
+ if (blacklist_enabled && module_blacklist_.count(module_name)) {
+ return false;
+ }
if (deps.empty()) {
// missing deps can happen in the case of an alias
return false;
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index 481658d..a711631 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -56,6 +56,14 @@
"/test13.ko",
};
+ std::vector<std::string> expected_after_remove = {
+ "/test14.ko", "/test15.ko", "/test1.ko",
+ "/test6.ko", "/test2.ko", "/test5.ko",
+ "/test8.ko", "/test7.ko param1=4", "/test9.ko param_x=1 param_y=2 param_z=3",
+ "/test10.ko", "/test12.ko", "/test11.ko",
+ "/test13.ko",
+ };
+
const std::string modules_dep =
"test1.ko:\n"
"test2.ko:\n"
@@ -91,6 +99,10 @@
"options test9.ko param_x=1 param_y=2 param_z=3\n"
"options test100.ko param_1=1\n";
+ const std::string modules_blacklist =
+ "blacklist test9.ko\n"
+ "blacklist test3.ko\n";
+
const std::string modules_load =
"test4.ko\n"
"test1.ko\n"
@@ -101,17 +113,20 @@
"test11.ko\n";
TemporaryDir dir;
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_alias, std::string(dir.path) + "/modules.alias", 0600, getuid(), getgid()));
+ auto dir_path = std::string(dir.path);
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_alias, dir_path + "/modules.alias", 0600,
+ getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_dep, std::string(dir.path) + "/modules.dep", 0600, getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_softdep, std::string(dir.path) + "/modules.softdep", 0600, getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_options, std::string(dir.path) + "/modules.options", 0600, getuid(), getgid()));
- ASSERT_TRUE(android::base::WriteStringToFile(
- modules_load, std::string(dir.path) + "/modules.load", 0600, getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_dep, dir_path + "/modules.dep", 0600,
+ getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_softdep, dir_path + "/modules.softdep",
+ 0600, getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_options, dir_path + "/modules.options",
+ 0600, getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_load, dir_path + "/modules.load", 0600,
+ getuid(), getgid()));
+ ASSERT_TRUE(android::base::WriteStringToFile(modules_blacklist, dir_path + "/modules.blacklist",
+ 0600, getuid(), getgid()));
for (auto i = test_modules.begin(); i != test_modules.end(); ++i) {
*i = dir.path + *i;
@@ -131,4 +146,21 @@
}
EXPECT_TRUE(modules_loaded == expected_modules_loaded);
+
+ EXPECT_TRUE(m.Remove("test4"));
+
+ GTEST_LOG_(INFO) << "Expected modules loaded after removing test4 (in order):";
+ for (auto i = expected_after_remove.begin(); i != expected_after_remove.end(); ++i) {
+ *i = dir.path + *i;
+ GTEST_LOG_(INFO) << "\"" << *i << "\"";
+ }
+ GTEST_LOG_(INFO) << "Actual modules loaded after removing test4 (in order):";
+ for (auto i = modules_loaded.begin(); i != modules_loaded.end(); ++i) {
+ GTEST_LOG_(INFO) << "\"" << *i << "\"";
+ }
+
+ EXPECT_TRUE(modules_loaded == expected_after_remove);
+
+ m.EnableBlacklist(true);
+ EXPECT_FALSE(m.LoadWithAliases("test4", true));
}
diff --git a/libnativeloader/native_loader_namespace.cpp b/libnativeloader/native_loader_namespace.cpp
index 4b02116..4add6e6 100644
--- a/libnativeloader/native_loader_namespace.cpp
+++ b/libnativeloader/native_loader_namespace.cpp
@@ -69,10 +69,18 @@
// "default" always exists.
Result<NativeLoaderNamespace> NativeLoaderNamespace::GetPlatformNamespace(bool is_bridged) {
auto ns = GetExportedNamespace(kPlatformNamespaceName, is_bridged);
- if (!ns) {
- ns = GetExportedNamespace(kDefaultNamespaceName, is_bridged);
+ if (ns) return ns;
+ ns = GetExportedNamespace(kDefaultNamespaceName, is_bridged);
+ if (ns) return ns;
+
+ // If nothing is found, return NativeLoaderNamespace constructed from nullptr.
+ // nullptr also means default namespace to the linker.
+ if (!is_bridged) {
+ return NativeLoaderNamespace(kDefaultNamespaceName, static_cast<android_namespace_t*>(nullptr));
+ } else {
+ return NativeLoaderNamespace(kDefaultNamespaceName,
+ static_cast<native_bridge_namespace_t*>(nullptr));
}
- return ns;
}
Result<NativeLoaderNamespace> NativeLoaderNamespace::Create(
diff --git a/libpackagelistparser/.clang-format b/libpackagelistparser/.clang-format
new file mode 120000
index 0000000..fd0645f
--- /dev/null
+++ b/libpackagelistparser/.clang-format
@@ -0,0 +1 @@
+../.clang-format-2
\ No newline at end of file
diff --git a/libpackagelistparser/Android.bp b/libpackagelistparser/Android.bp
index c38594a..0740e7d 100644
--- a/libpackagelistparser/Android.bp
+++ b/libpackagelistparser/Android.bp
@@ -1,12 +1,7 @@
cc_library {
-
name: "libpackagelistparser",
recovery_available: true,
- srcs: ["packagelistparser.c"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
+ srcs: ["packagelistparser.cpp"],
shared_libs: ["liblog"],
local_include_dirs: ["include"],
export_include_dirs: ["include"],
@@ -15,3 +10,13 @@
misc_undefined: ["integer"],
},
}
+
+cc_test {
+ name: "libpackagelistparser_test",
+ srcs: ["packagelistparser_test.cpp"],
+ shared_libs: [
+ "libbase",
+ "libpackagelistparser",
+ ],
+ test_suites: ["device-tests"],
+}
diff --git a/libpackagelistparser/include/packagelistparser/packagelistparser.h b/libpackagelistparser/include/packagelistparser/packagelistparser.h
index 3cb6b9a..e89cb54 100644
--- a/libpackagelistparser/include/packagelistparser/packagelistparser.h
+++ b/libpackagelistparser/include/packagelistparser/packagelistparser.h
@@ -1,94 +1,81 @@
/*
- * Copyright 2015, Intel Corporation
- * Copyright (C) 2015 The Android Open Source Project
+ * 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
+ * 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.
- *
- * Written by William Roberts <william.c.roberts@intel.com>
- *
- * This is a parser library for parsing the packages.list file generated
- * by PackageManager service.
- *
- * This simple parser is sensitive to format changes in
- * frameworks/base/services/core/java/com/android/server/pm/Settings.java
- * A dependency note has been added to that file to correct
- * this parser.
*/
-#ifndef PACKAGELISTPARSER_H_
-#define PACKAGELISTPARSER_H_
+#pragma once
#include <stdbool.h>
-#include <sys/cdefs.h>
#include <sys/types.h>
__BEGIN_DECLS
-/** The file containing the list of installed packages on the system */
-#define PACKAGES_LIST_FILE "/data/system/packages.list"
+typedef struct gid_list {
+ /** Number of gids. */
+ size_t cnt;
-typedef struct pkg_info pkg_info;
-typedef struct gid_list gid_list;
+ /** Array of gids. */
+ gid_t* gids;
+} gid_list;
-struct gid_list {
- size_t cnt;
- gid_t *gids;
-};
+typedef struct pkg_info {
+ /** Package name like "com.android.blah". */
+ char* name;
-struct pkg_info {
- char *name;
- uid_t uid;
- bool debuggable;
- char *data_dir;
- char *seinfo;
- gid_list gids;
- void *private_data;
- bool profileable_from_shell;
- long version_code;
-};
+ /** Package uid like 10014. */
+ uid_t uid;
+
+ /** Package's AndroidManifest.xml debuggable flag. */
+ bool debuggable;
+
+ /** Package data directory like "/data/user/0/com.android.blah" */
+ char* data_dir;
+
+ /** Package SELinux info. */
+ char* seinfo;
+
+ /** Package's list of gids. */
+ gid_list gids;
+
+ /** Spare pointer for the caller to stash extra data off. */
+ void* private_data;
+
+ /** Package's AndroidManifest.xml profileable flag. */
+ bool profileable_from_shell;
+
+ /** Package's AndroidManifest.xml version code. */
+ long version_code;
+} pkg_info;
/**
- * Callback function to be used by packagelist_parse() routine.
- * @param info
- * The parsed package information
- * @param userdata
- * The supplied userdata pointer to packagelist_parse()
- * @return
- * true to keep processing, false to stop.
+ * Parses the system's default package list.
+ * Invokes `callback` once for each package.
+ * The callback owns the `pkg_info*` and should call packagelist_free().
+ * The callback should return `false` to exit early or `true` to continue.
*/
-typedef bool (*pfn_on_package)(pkg_info *info, void *userdata);
+bool packagelist_parse(bool (*callback)(pkg_info* info, void* user_data), void* user_data);
/**
- * Parses the file specified by PACKAGES_LIST_FILE and invokes the callback on
- * each entry found. Once the callback is invoked, ownership of the pkg_info pointer
- * is passed to the callback routine, thus they are required to perform any cleanup
- * desired.
- * @param callback
- * The callback function called on each parsed line of the packages list.
- * @param userdata
- * An optional userdata supplied pointer to pass to the callback function.
- * @return
- * true on success false on failure.
+ * Parses the given package list.
+ * Invokes `callback` once for each package.
+ * The callback owns the `pkg_info*` and should call packagelist_free().
+ * The callback should return `false` to exit early or `true` to continue.
*/
-extern bool packagelist_parse(pfn_on_package callback, void *userdata);
+bool packagelist_parse_file(const char* path, bool (*callback)(pkg_info* info, void* user_data),
+ void* user_data);
-/**
- * Frees a pkg_info structure.
- * @param info
- * The struct to free
- */
-extern void packagelist_free(pkg_info *info);
+/** Frees the given `pkg_info`. */
+void packagelist_free(pkg_info* info);
__END_DECLS
-
-#endif /* PACKAGELISTPARSER_H_ */
diff --git a/libpackagelistparser/packagelistparser.c b/libpackagelistparser/packagelistparser.c
deleted file mode 100644
index edc533c..0000000
--- a/libpackagelistparser/packagelistparser.c
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2015, Intel Corporation
- * 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.
- *
- * Written by William Roberts <william.c.roberts@intel.com>
- *
- */
-
-#define LOG_TAG "packagelistparser"
-
-#include <errno.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/limits.h>
-
-#include <log/log.h>
-#include <packagelistparser/packagelistparser.h>
-
-#define CLOGE(fmt, ...) \
- do {\
- IF_ALOGE() {\
- ALOGE(fmt, ##__VA_ARGS__);\
- }\
- } while(0)
-
-static size_t get_gid_cnt(const char *gids)
-{
- size_t cnt;
-
- if (*gids == '\0') {
- return 0;
- }
-
- if (!strcmp(gids, "none")) {
- return 0;
- }
-
- for (cnt = 1; gids[cnt]; gids[cnt] == ',' ? cnt++ : *gids++)
- ;
-
- return cnt;
-}
-
-static bool parse_gids(char *gids, gid_t *gid_list, size_t *cnt)
-{
- gid_t gid;
- char* token;
- char *endptr;
- size_t cmp = 0;
-
- while ((token = strsep(&gids, ",\r\n"))) {
-
- if (cmp > *cnt) {
- return false;
- }
-
- gid = strtoul(token, &endptr, 10);
- if (*endptr != '\0') {
- return false;
- }
-
- /*
- * if unsigned long is greater than size of gid_t,
- * prevent a truncation based roll-over
- */
- if (gid > GID_MAX) {
- CLOGE("A gid in field \"gid list\" greater than GID_MAX");
- return false;
- }
-
- gid_list[cmp++] = gid;
- }
- return true;
-}
-
-extern bool packagelist_parse(pfn_on_package callback, void *userdata)
-{
-
- FILE *fp;
- char *cur;
- char *next;
- char *endptr;
- unsigned long tmp;
- ssize_t bytesread;
-
- bool rc = false;
- char *buf = NULL;
- size_t buflen = 0;
- unsigned long lineno = 1;
- const char *errmsg = NULL;
- struct pkg_info *pkg_info = NULL;
-
- fp = fopen(PACKAGES_LIST_FILE, "re");
- if (!fp) {
- CLOGE("Could not open: \"%s\", error: \"%s\"\n", PACKAGES_LIST_FILE,
- strerror(errno));
- return false;
- }
-
- while ((bytesread = getline(&buf, &buflen, fp)) > 0) {
-
- pkg_info = calloc(1, sizeof(*pkg_info));
- if (!pkg_info) {
- goto err;
- }
-
- next = buf;
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for \"package name\"";
- goto err;
- }
-
- pkg_info->name = strdup(cur);
- if (!pkg_info->name) {
- goto err;
- }
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"uid\"";
- goto err;
- }
-
- tmp = strtoul(cur, &endptr, 10);
- if (*endptr != '\0') {
- errmsg = "Could not convert field \"uid\" to integer value";
- goto err;
- }
-
- /*
- * if unsigned long is greater than size of uid_t,
- * prevent a truncation based roll-over
- */
- if (tmp > UID_MAX) {
- errmsg = "Field \"uid\" greater than UID_MAX";
- goto err;
- }
-
- pkg_info->uid = (uid_t) tmp;
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"debuggable\"";
- goto err;
- }
-
- tmp = strtoul(cur, &endptr, 10);
- if (*endptr != '\0') {
- errmsg = "Could not convert field \"debuggable\" to integer value";
- goto err;
- }
-
- /* should be a valid boolean of 1 or 0 */
- if (!(tmp == 0 || tmp == 1)) {
- errmsg = "Field \"debuggable\" is not 0 or 1 boolean value";
- goto err;
- }
-
- pkg_info->debuggable = (bool) tmp;
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"data dir\"";
- goto err;
- }
-
- pkg_info->data_dir = strdup(cur);
- if (!pkg_info->data_dir) {
- goto err;
- }
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"seinfo\"";
- goto err;
- }
-
- pkg_info->seinfo = strdup(cur);
- if (!pkg_info->seinfo) {
- goto err;
- }
-
- cur = strsep(&next, " \t\r\n");
- if (!cur) {
- errmsg = "Could not get next token for field \"gid(s)\"";
- goto err;
- }
-
- /*
- * Parse the gid list, could be in the form of none, single gid or list:
- * none
- * gid
- * gid, gid ...
- */
- pkg_info->gids.cnt = get_gid_cnt(cur);
- if (pkg_info->gids.cnt > 0) {
-
- pkg_info->gids.gids = calloc(pkg_info->gids.cnt, sizeof(gid_t));
- if (!pkg_info->gids.gids) {
- goto err;
- }
-
- rc = parse_gids(cur, pkg_info->gids.gids, &pkg_info->gids.cnt);
- if (!rc) {
- errmsg = "Could not parse field \"gid list\"";
- goto err;
- }
- }
-
- cur = strsep(&next, " \t\r\n");
- if (cur) {
- tmp = strtoul(cur, &endptr, 10);
- if (*endptr != '\0') {
- errmsg = "Could not convert field \"profileable_from_shell\" to integer value";
- goto err;
- }
-
- /* should be a valid boolean of 1 or 0 */
- if (!(tmp == 0 || tmp == 1)) {
- errmsg = "Field \"profileable_from_shell\" is not 0 or 1 boolean value";
- goto err;
- }
-
- pkg_info->profileable_from_shell = (bool)tmp;
- }
- cur = strsep(&next, " \t\r\n");
- if (cur) {
- tmp = strtoul(cur, &endptr, 10);
- if (*endptr != '\0') {
- errmsg = "Could not convert field \"versionCode\" to integer value";
- goto err;
- }
- pkg_info->version_code = tmp;
- }
-
- rc = callback(pkg_info, userdata);
- if (rc == false) {
- /*
- * We do not log this as this can be intentional from
- * callback to abort processing. We go to out to not
- * free the pkg_info
- */
- rc = true;
- goto out;
- }
- lineno++;
- }
-
- rc = true;
-
-out:
- free(buf);
- fclose(fp);
- return rc;
-
-err:
- if (errmsg) {
- CLOGE("Error Parsing \"%s\" on line: %lu for reason: %s",
- PACKAGES_LIST_FILE, lineno, errmsg);
- }
- rc = false;
- packagelist_free(pkg_info);
- goto out;
-}
-
-void packagelist_free(pkg_info *info)
-{
- if (info) {
- free(info->name);
- free(info->data_dir);
- free(info->seinfo);
- free(info->gids.gids);
- free(info);
- }
-}
diff --git a/libpackagelistparser/packagelistparser.cpp b/libpackagelistparser/packagelistparser.cpp
new file mode 100644
index 0000000..59c3a74
--- /dev/null
+++ b/libpackagelistparser/packagelistparser.cpp
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "packagelistparser"
+
+#include <packagelistparser/packagelistparser.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/limits.h>
+
+#include <memory>
+
+#include <log/log.h>
+
+static bool parse_gids(const char* path, size_t line_number, const char* gids, pkg_info* info) {
+ // Nothing to do?
+ if (!gids || !strcmp(gids, "none")) return true;
+
+ // How much space do we need?
+ info->gids.cnt = 1;
+ for (const char* p = gids; *p; ++p) {
+ if (*p == ',') ++info->gids.cnt;
+ }
+
+ // Allocate the space.
+ info->gids.gids = new gid_t[info->gids.cnt];
+ if (!info->gids.gids) return false;
+
+ // And parse the individual gids.
+ size_t i = 0;
+ while (true) {
+ char* end;
+ unsigned long gid = strtoul(gids, &end, 10);
+ if (gid > GID_MAX) {
+ ALOGE("%s:%zu: gid %lu > GID_MAX", path, line_number, gid);
+ return false;
+ }
+
+ if (i >= info->gids.cnt) return false;
+ info->gids.gids[i++] = gid;
+
+ if (*end == '\0') return true;
+ if (*end != ',') return false;
+ gids = end + 1;
+ }
+ return true;
+}
+
+static bool parse_line(const char* path, size_t line_number, const char* line, pkg_info* info) {
+ unsigned long uid;
+ int debuggable;
+ char* gid_list;
+ int profileable_from_shell = 0;
+
+ int fields =
+ sscanf(line, "%ms %lu %d %ms %ms %ms %d %ld", &info->name, &uid, &debuggable, &info->data_dir,
+ &info->seinfo, &gid_list, &profileable_from_shell, &info->version_code);
+
+ // Handle the more complicated gids field and free the temporary string.
+ bool gids_okay = parse_gids(path, line_number, gid_list, info);
+ free(gid_list);
+ if (!gids_okay) return false;
+
+ // Did we see enough fields to be getting on with?
+ // The final fields are optional (and not usually present).
+ if (fields < 6) {
+ ALOGE("%s:%zu: too few fields in line", path, line_number);
+ return false;
+ }
+
+ // Extra validation.
+ if (uid > UID_MAX) {
+ ALOGE("%s:%zu: uid %lu > UID_MAX", path, line_number, uid);
+ return false;
+ }
+ info->uid = uid;
+
+ // Integer to bool conversions.
+ info->debuggable = debuggable;
+ info->profileable_from_shell = profileable_from_shell;
+
+ return true;
+}
+
+bool packagelist_parse_file(const char* path, bool (*callback)(pkg_info*, void*), void* user_data) {
+ std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(path, "re"), &fclose);
+ if (!fp) {
+ ALOGE("couldn't open '%s': %s", path, strerror(errno));
+ return false;
+ }
+
+ size_t line_number = 0;
+ char* line = nullptr;
+ size_t allocated_length = 0;
+ while (getline(&line, &allocated_length, fp.get()) > 0) {
+ ++line_number;
+ std::unique_ptr<pkg_info, decltype(&packagelist_free)> info(
+ static_cast<pkg_info*>(calloc(1, sizeof(pkg_info))), &packagelist_free);
+ if (!info) {
+ ALOGE("%s:%zu: couldn't allocate pkg_info", path, line_number);
+ return false;
+ }
+
+ if (!parse_line(path, line_number, line, info.get())) return false;
+
+ if (!callback(info.release(), user_data)) break;
+ }
+ free(line);
+ return true;
+}
+
+bool packagelist_parse(bool (*callback)(pkg_info*, void*), void* user_data) {
+ return packagelist_parse_file("/data/system/packages.list", callback, user_data);
+}
+
+void packagelist_free(pkg_info* info) {
+ if (!info) return;
+
+ free(info->name);
+ free(info->data_dir);
+ free(info->seinfo);
+ delete[] info->gids.gids;
+ free(info);
+}
diff --git a/libpackagelistparser/packagelistparser_test.cpp b/libpackagelistparser/packagelistparser_test.cpp
new file mode 100644
index 0000000..76cb886
--- /dev/null
+++ b/libpackagelistparser/packagelistparser_test.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 <packagelistparser/packagelistparser.h>
+
+#include <memory>
+
+#include <android-base/file.h>
+
+#include <gtest/gtest.h>
+
+TEST(packagelistparser, smoke) {
+ TemporaryFile tf;
+ android::base::WriteStringToFile(
+ // No gids.
+ "com.test.a0 10014 0 /data/user/0/com.test.a0 platform:privapp:targetSdkVersion=19 none\n"
+ // One gid.
+ "com.test.a1 10007 1 /data/user/0/com.test.a1 platform:privapp:targetSdkVersion=21 1023\n"
+ // Multiple gids.
+ "com.test.a2 10011 0 /data/user/0/com.test.a2 media:privapp:targetSdkVersion=30 "
+ "2001,1065,1023,3003,3007,1024\n"
+ // The two new fields (profileable flag and version code).
+ "com.test.a3 10022 0 /data/user/0/com.test.a3 selabel:blah none 1 123\n",
+ tf.path);
+
+ std::vector<pkg_info*> packages;
+ packagelist_parse_file(
+ tf.path,
+ [](pkg_info* info, void* user_data) -> bool {
+ reinterpret_cast<std::vector<pkg_info*>*>(user_data)->push_back(info);
+ return true;
+ },
+ &packages);
+
+ ASSERT_EQ(4U, packages.size());
+
+ ASSERT_STREQ("com.test.a0", packages[0]->name);
+ ASSERT_EQ(10014, packages[0]->uid);
+ ASSERT_FALSE(packages[0]->debuggable);
+ ASSERT_STREQ("/data/user/0/com.test.a0", packages[0]->data_dir);
+ ASSERT_STREQ("platform:privapp:targetSdkVersion=19", packages[0]->seinfo);
+ ASSERT_EQ(0U, packages[0]->gids.cnt);
+ ASSERT_FALSE(packages[0]->profileable_from_shell);
+ ASSERT_EQ(0, packages[0]->version_code);
+
+ ASSERT_STREQ("com.test.a1", packages[1]->name);
+ ASSERT_EQ(10007, packages[1]->uid);
+ ASSERT_TRUE(packages[1]->debuggable);
+ ASSERT_STREQ("/data/user/0/com.test.a1", packages[1]->data_dir);
+ ASSERT_STREQ("platform:privapp:targetSdkVersion=21", packages[1]->seinfo);
+ ASSERT_EQ(1U, packages[1]->gids.cnt);
+ ASSERT_EQ(1023U, packages[1]->gids.gids[0]);
+ ASSERT_FALSE(packages[0]->profileable_from_shell);
+ ASSERT_EQ(0, packages[0]->version_code);
+
+ ASSERT_STREQ("com.test.a2", packages[2]->name);
+ ASSERT_EQ(10011, packages[2]->uid);
+ ASSERT_FALSE(packages[2]->debuggable);
+ ASSERT_STREQ("/data/user/0/com.test.a2", packages[2]->data_dir);
+ ASSERT_STREQ("media:privapp:targetSdkVersion=30", packages[2]->seinfo);
+ ASSERT_EQ(6U, packages[2]->gids.cnt);
+ ASSERT_EQ(2001U, packages[2]->gids.gids[0]);
+ ASSERT_EQ(1024U, packages[2]->gids.gids[5]);
+ ASSERT_FALSE(packages[0]->profileable_from_shell);
+ ASSERT_EQ(0, packages[0]->version_code);
+
+ ASSERT_STREQ("com.test.a3", packages[3]->name);
+ ASSERT_EQ(10022, packages[3]->uid);
+ ASSERT_FALSE(packages[3]->debuggable);
+ ASSERT_STREQ("/data/user/0/com.test.a3", packages[3]->data_dir);
+ ASSERT_STREQ("selabel:blah", packages[3]->seinfo);
+ ASSERT_EQ(0U, packages[3]->gids.cnt);
+ ASSERT_TRUE(packages[3]->profileable_from_shell);
+ ASSERT_EQ(123, packages[3]->version_code);
+
+ for (auto& package : packages) packagelist_free(package);
+}
+
+TEST(packagelistparser, early_exit) {
+ TemporaryFile tf;
+ android::base::WriteStringToFile(
+ "com.test.a0 1 0 / a none\n"
+ "com.test.a1 1 0 / a none\n"
+ "com.test.a2 1 0 / a none\n",
+ tf.path);
+
+ std::vector<pkg_info*> packages;
+ packagelist_parse_file(
+ tf.path,
+ [](pkg_info* info, void* user_data) -> bool {
+ std::vector<pkg_info*>* p = reinterpret_cast<std::vector<pkg_info*>*>(user_data);
+ p->push_back(info);
+ return p->size() < 2;
+ },
+ &packages);
+
+ ASSERT_EQ(2U, packages.size());
+
+ ASSERT_STREQ("com.test.a0", packages[0]->name);
+ ASSERT_STREQ("com.test.a1", packages[1]->name);
+
+ for (auto& package : packages) packagelist_free(package);
+}
+
+TEST(packagelistparser, system_package_list) {
+ // Check that we can actually read the packages.list installed on the device.
+ std::vector<pkg_info*> packages;
+ packagelist_parse(
+ [](pkg_info* info, void* user_data) -> bool {
+ reinterpret_cast<std::vector<pkg_info*>*>(user_data)->push_back(info);
+ return true;
+ },
+ &packages);
+ // Not much we can say for sure about what we expect, other than that there
+ // are likely to be lots of packages...
+ ASSERT_GT(packages.size(), 10U);
+}
+
+TEST(packagelistparser, packagelist_free_nullptr) {
+ packagelist_free(nullptr);
+}
diff --git a/libstats/include/stats_event_list.h b/libstats/include/stats_event_list.h
index 845a197..b7ada0c 100644
--- a/libstats/include/stats_event_list.h
+++ b/libstats/include/stats_event_list.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef ANDROID_STATS_LOG_STATS_EVENT_LIST_H
-#define ANDROID_STATS_LOG_STATS_EVENT_LIST_H
+#pragma once
#include <log/log_event_list.h>
#include <sys/uio.h>
@@ -133,7 +132,6 @@
return *this;
}
-#if defined(_USING_LIBCXX)
stats_event_list& operator<<(const std::string& value) {
int retval = android_log_write_string8_len(ctx, value.data(), value.length());
if (retval < 0) {
@@ -141,7 +139,6 @@
}
return *this;
}
-#endif
stats_event_list& operator<<(float value) {
int retval = android_log_write_float32(ctx, value);
@@ -203,7 +200,6 @@
return ret >= 0;
}
-#if defined(_USING_LIBCXX)
bool AppendString(const std::string& value) {
int retval = android_log_write_string8_len(ctx, value.data(), value.length());
if (retval < 0) {
@@ -219,7 +215,6 @@
}
return ret;
}
-#endif
bool AppendFloat(float value) {
int retval = android_log_write_float32(ctx, value);
@@ -253,4 +248,3 @@
};
#endif
-#endif // ANDROID_STATS_LOG_STATS_EVENT_LIST_H
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 24a745a..b08e061 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -452,48 +452,6 @@
*codePoint |= 0x3F & byte;
}
-size_t utf8_to_utf32_length(const char *src, size_t src_len)
-{
- if (src == nullptr || src_len == 0) {
- return 0;
- }
- size_t ret = 0;
- const char* cur;
- const char* end;
- size_t num_to_skip;
- for (cur = src, end = src + src_len, num_to_skip = 1;
- cur < end;
- cur += num_to_skip, ret++) {
- const char first_char = *cur;
- num_to_skip = 1;
- if ((first_char & 0x80) == 0) { // ASCII
- continue;
- }
- int32_t mask;
-
- for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) {
- }
- }
- return ret;
-}
-
-void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst)
-{
- if (src == nullptr || src_len == 0 || dst == nullptr) {
- return;
- }
-
- const char* cur = src;
- const char* const end = src + src_len;
- char32_t* cur_utf32 = dst;
- while (cur < end) {
- size_t num_read;
- *cur_utf32++ = static_cast<char32_t>(utf32_at_internal(cur, &num_read));
- cur += num_read;
- }
- *cur_utf32 = 0;
-}
-
static inline uint32_t utf8_to_utf32_codepoint(const uint8_t *src, size_t length)
{
uint32_t unicode;
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index a2aaa47..fc6712d 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -129,18 +129,6 @@
ssize_t utf8_length(const char *src);
/**
- * Measure the length of a UTF-32 string.
- */
-size_t utf8_to_utf32_length(const char *src, size_t src_len);
-
-/**
- * Stores a UTF-32 string converted from "src" in "dst". "dst" must be large
- * enough to store the entire converted string as measured by
- * utf8_to_utf32_length plus space for a NUL terminator.
- */
-void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst);
-
-/**
* Returns the UTF-16 length of UTF-8 string "src". Returns -1 in case
* it's invalid utf8. No buffer over-read occurs because of bound checks. Using overreadIsFatal you
* can ask to log a message and fail in case the invalid utf8 could have caused an override if no
diff --git a/logcat/Android.bp b/logcat/Android.bp
index 5030b15..f1b18b2 100644
--- a/logcat/Android.bp
+++ b/logcat/Android.bp
@@ -24,7 +24,6 @@
],
shared_libs: [
"libbase",
- "libpcrecpp",
"libprocessgroup",
],
static_libs: ["liblog"],
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 6e38d95..e2711af 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -41,6 +41,7 @@
#include <atomic>
#include <memory>
+#include <regex>
#include <string>
#include <utility>
#include <vector>
@@ -56,8 +57,6 @@
#include <processgroup/sched_policy.h>
#include <system/thread_defs.h>
-#include <pcrecpp.h>
-
#define DEFAULT_MAX_ROTATED_LOGS 4
struct log_device_t {
@@ -113,7 +112,7 @@
size_t outByteCount;
int printBinary;
int devCount; // >1 means multiple
- pcrecpp::RE* regex;
+ std::unique_ptr<std::regex> regex;
log_device_t* devices;
EventTagMap* eventTagMap;
// 0 means "infinite"
@@ -307,9 +306,7 @@
const AndroidLogEntry& entry) {
if (!context->regex) return true;
- std::string messageString(entry.message, entry.messageLen);
-
- return context->regex->PartialMatch(messageString);
+ return std::regex_search(entry.message, entry.message + entry.messageLen, *context->regex);
}
static void processBuffer(android_logcat_context_internal* context,
@@ -460,7 +457,7 @@
" -d Dump the log and then exit (don't block)\n"
" -e <expr>, --regex=<expr>\n"
" Only print lines where the log message matches <expr>\n"
- " where <expr> is a Perl-compatible regular expression\n"
+ " where <expr> is a regular expression\n"
// Leave --head undocumented as alias for -m
" -m <count>, --max-count=<count>\n"
" Quit after printing <count> lines. This is meant to be\n"
@@ -899,6 +896,11 @@
case 0:
// only long options
if (long_options[option_index].name == pid_str) {
+ if (pid != 0) {
+ logcat_panic(context, HELP_TRUE, "Only supports one PID argument.\n");
+ goto exit;
+ }
+
// ToDo: determine runtime PID_MAX?
if (!getSizeTArg(optarg, &pid, 1)) {
logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
@@ -995,7 +997,7 @@
break;
case 'e':
- context->regex = new pcrecpp::RE(optarg);
+ context->regex.reset(new std::regex(optarg));
break;
case 'm': {
@@ -1696,7 +1698,6 @@
sched_yield();
}
- delete context->regex;
context->argv_hold.clear();
context->args.clear();
context->envp_hold.clear();
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 76d6f7e..4559050 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -230,7 +230,7 @@
# A symlink can't overwrite a directory and the /system/usr/icu directory once
# existed so the required structure must be created whatever we find.
LOCAL_POST_INSTALL_CMD = mkdir -p $(TARGET_OUT)/usr && rm -rf $(TARGET_OUT)/usr/icu
-LOCAL_POST_INSTALL_CMD += && ln -sf /apex/com.android.runtime/etc/icu $(TARGET_OUT)/usr/icu
+LOCAL_POST_INSTALL_CMD += && ln -sf /apex/com.android.i18n/etc/icu $(TARGET_OUT)/usr/icu
# TODO(b/124106384): Clean up compat symlinks for ART binaries.
ART_BINARIES := \
diff --git a/rootdir/init.environ.rc.in b/rootdir/init.environ.rc.in
index 455c9a8..93b7f43 100644
--- a/rootdir/init.environ.rc.in
+++ b/rootdir/init.environ.rc.in
@@ -6,6 +6,7 @@
export ANDROID_DATA /data
export ANDROID_STORAGE /storage
export ANDROID_RUNTIME_ROOT /apex/com.android.runtime
+ export ANDROID_I18N_ROOT /apex/com.android.i18n
export ANDROID_TZDATA_ROOT /apex/com.android.tzdata
export EXTERNAL_STORAGE /sdcard
export ASEC_MOUNTPOINT /mnt/asec
diff --git a/rootdir/init.rc b/rootdir/init.rc
index d22e9a7..4d34b67 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -121,6 +121,9 @@
mkdir /mnt/media_rw 0750 root media_rw
mkdir /mnt/user 0755 root root
mkdir /mnt/user/0 0755 root root
+ mkdir /mnt/user/0/self 0755 root root
+ mkdir /mnt/user/0/emulated 0755 root root
+ mkdir /mnt/user/0/emulated/0 0755 root root
mkdir /mnt/expand 0771 system system
mkdir /mnt/appfuse 0711 root root
@@ -367,9 +370,6 @@
# Once everything is setup, no need to modify /.
# The bind+remount combination allows this to work in containers.
mount rootfs rootfs / remount bind ro nodev
- # Mount default storage into root namespace
- mount none /mnt/runtime/default /storage bind rec
- mount none none /storage slave rec
# Make sure /sys/kernel/debug (if present) is labeled properly
# Note that tracefs may be mounted under debug, so we need to cross filesystems
@@ -415,6 +415,7 @@
chmod 0700 /metadata/vold
mkdir /metadata/password_slots 0771 root system
mkdir /metadata/ota 0700 root system
+ mkdir /metadata/ota/snapshots 0700 root system
mkdir /metadata/apex 0700 root system
mkdir /metadata/apex/sessions 0700 root system
@@ -641,6 +642,22 @@
chown root system /dev/fscklogs/log
chmod 0770 /dev/fscklogs/log
+# Switch between sdcardfs and FUSE depending on persist property
+# TODO: Move this to ro property before launch because FDE devices
+# interact with persistent properties differently during boot
+on zygote-start && property:persist.sys.fuse=true
+ # Mount default storage into root namespace
+ mount none /mnt/user/0 /storage bind rec
+ mount none none /storage slave rec
+on zygote-start && property:persist.sys.fuse=false
+ # Mount default storage into root namespace
+ mount none /mnt/runtime/default /storage bind rec
+ mount none none /storage slave rec
+on zygote-start && property:persist.sys.fuse=""
+ # Mount default storage into root namespace
+ mount none /mnt/runtime/default /storage bind rec
+ mount none none /storage slave rec
+
# It is recommended to put unnecessary data/ initialization from post-fs-data
# to start-zygote in device's init.rc to unblock zygote start.
on zygote-start && property:ro.crypto.state=unencrypted
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index 6897663..1d934a2 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -164,8 +164,10 @@
}
void storaged_t::add_user_ce(userid_t user_id) {
- load_proto(user_id);
- proto_loaded[user_id] = true;
+ if (!proto_loaded[user_id]) {
+ load_proto(user_id);
+ proto_loaded[user_id] = true;
+ }
}
void storaged_t::remove_user_ce(userid_t user_id) {
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index 0cc603a..4ca5f5a 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -24,6 +24,7 @@
"toolbox.c",
"getevent.c",
"getprop.cpp",
+ "modprobe.cpp",
"setprop.cpp",
"start.cpp",
],
@@ -33,11 +34,15 @@
shared_libs: [
"libbase",
],
- static_libs: ["libpropertyinfoparser"],
+ static_libs: [
+ "libmodprobe",
+ "libpropertyinfoparser",
+ ],
symlinks: [
"getevent",
"getprop",
+ "modprobe",
"setprop",
"start",
"stop",
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
new file mode 100644
index 0000000..1b5f54e
--- /dev/null
+++ b/toolbox/modprobe.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 <ctype.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <iostream>
+
+#include <android-base/strings.h>
+#include <modprobe/modprobe.h>
+
+enum modprobe_mode {
+ AddModulesMode,
+ RemoveModulesMode,
+ ListModulesMode,
+ ShowDependenciesMode,
+};
+
+static void print_usage(void) {
+ std::cerr << "Usage:" << std::endl;
+ std::cerr << std::endl;
+ std::cerr << " modprobe [-alrqvsDb] [-d DIR] [MODULE]+" << std::endl;
+ std::cerr << " modprobe [-alrqvsDb] [-d DIR] MODULE [symbol=value][...]" << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "Options:" << std::endl;
+ std::cerr << " -b: Apply blacklist to module names too" << std::endl;
+ std::cerr << " -d: Load modules from DIR, option may be used multiple times" << std::endl;
+ std::cerr << " -D: Print dependencies for modules only, do not load";
+ std::cerr << " -h: Print this help" << std::endl;
+ std::cerr << " -l: List modules matching pattern" << std::endl;
+ std::cerr << " -r: Remove MODULE (multiple modules may be specified)" << std::endl;
+ std::cerr << " -q: Quiet" << std::endl;
+ std::cerr << " -v: Verbose" << std::endl;
+ std::cerr << std::endl;
+}
+
+#define check_mode() \
+ if (mode != AddModulesMode) { \
+ std::cerr << "Error, multiple mode flags specified" << std::endl; \
+ print_usage(); \
+ return EXIT_FAILURE; \
+ }
+
+extern "C" int modprobe_main(int argc, char** argv) {
+ std::vector<std::string> modules;
+ std::string module_parameters;
+ std::vector<std::string> mod_dirs;
+ modprobe_mode mode = AddModulesMode;
+ bool blacklist = false;
+ bool verbose = false;
+ int rv = EXIT_SUCCESS;
+
+ int opt;
+ while ((opt = getopt(argc, argv, "abd:Dhlqrv")) != -1) {
+ switch (opt) {
+ case 'a':
+ // toybox modprobe supported -a to load multiple modules, this
+ // is supported here by default, ignore flag
+ check_mode();
+ break;
+ case 'b':
+ blacklist = true;
+ break;
+ case 'd':
+ mod_dirs.emplace_back(optarg);
+ break;
+ case 'D':
+ check_mode();
+ mode = ShowDependenciesMode;
+ break;
+ case 'h':
+ print_usage();
+ return EXIT_SUCCESS;
+ case 'l':
+ check_mode();
+ mode = ListModulesMode;
+ break;
+ case 'q':
+ verbose = false;
+ break;
+ case 'r':
+ check_mode();
+ mode = RemoveModulesMode;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ std::cerr << "Unrecognized option: " << opt << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+
+ int parameter_count = 0;
+ for (opt = optind; opt < argc; opt++) {
+ if (!strchr(argv[opt], '=')) {
+ modules.emplace_back(argv[opt]);
+ } else {
+ parameter_count++;
+ if (module_parameters.empty()) {
+ module_parameters = argv[opt];
+ } else {
+ module_parameters = module_parameters + " " + argv[opt];
+ }
+ }
+ }
+
+ if (verbose) {
+ std::cout << "mode is " << mode << std::endl;
+ std::cout << "verbose is " << verbose << std::endl;
+ std::cout << "mod_dirs is: " << android::base::Join(mod_dirs, "") << std::endl;
+ std::cout << "modules is: " << android::base::Join(modules, "") << std::endl;
+ std::cout << "module parameters is: " << android::base::Join(module_parameters, "")
+ << std::endl;
+ }
+
+ if (modules.empty()) {
+ if (mode == ListModulesMode) {
+ // emulate toybox modprobe list with no pattern (list all)
+ modules.emplace_back("*");
+ } else {
+ std::cerr << "No modules given." << std::endl;
+ print_usage();
+ return EXIT_FAILURE;
+ }
+ }
+ if (mod_dirs.empty()) {
+ std::cerr << "No module configuration directories given." << std::endl;
+ print_usage();
+ return EXIT_FAILURE;
+ }
+ if (parameter_count && modules.size() > 1) {
+ std::cerr << "Only one module may be loaded when specifying module parameters."
+ << std::endl;
+ print_usage();
+ return EXIT_FAILURE;
+ }
+
+ Modprobe m(mod_dirs);
+ m.EnableVerbose(verbose);
+ if (blacklist) {
+ m.EnableBlacklist(true);
+ }
+
+ for (const auto& module : modules) {
+ switch (mode) {
+ case AddModulesMode:
+ if (!m.LoadWithAliases(module, true, module_parameters)) {
+ std::cerr << "Failed to load module " << module;
+ rv = EXIT_FAILURE;
+ }
+ break;
+ case RemoveModulesMode:
+ if (!m.Remove(module)) {
+ std::cerr << "Failed to remove module " << module;
+ rv = EXIT_FAILURE;
+ }
+ break;
+ case ListModulesMode: {
+ std::vector<std::string> list = m.ListModules(module);
+ std::cout << android::base::Join(list, "\n") << std::endl;
+ break;
+ }
+ case ShowDependenciesMode: {
+ std::vector<std::string> pre_deps;
+ std::vector<std::string> deps;
+ std::vector<std::string> post_deps;
+ if (!m.GetAllDependencies(module, &pre_deps, &deps, &post_deps)) {
+ rv = EXIT_FAILURE;
+ break;
+ }
+ std::cout << "Dependencies for " << module << ":" << std::endl;
+ std::cout << "Soft pre-dependencies:" << std::endl;
+ std::cout << android::base::Join(pre_deps, "\n") << std::endl;
+ std::cout << "Hard dependencies:" << std::endl;
+ std::cout << android::base::Join(deps, "\n") << std::endl;
+ std::cout << "Soft post-dependencies:" << std::endl;
+ std::cout << android::base::Join(post_deps, "\n") << std::endl;
+ break;
+ }
+ default:
+ std::cerr << "Bad mode";
+ rv = EXIT_FAILURE;
+ }
+ }
+
+ return rv;
+}
diff --git a/toolbox/tools.h b/toolbox/tools.h
index 9a7ebd2..bb57e67 100644
--- a/toolbox/tools.h
+++ b/toolbox/tools.h
@@ -1,5 +1,6 @@
TOOL(getevent)
TOOL(getprop)
+TOOL(modprobe)
TOOL(setprop)
TOOL(start)
TOOL(stop)