Merge "installd: Simplify Dalvik cache path creation"
diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk
index 6cd0689..d29e5ba 100644
--- a/cmds/installd/Android.mk
+++ b/cmds/installd/Android.mk
@@ -45,8 +45,8 @@
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := otapreopt_script.sh
-# Let this depend on otapreopt and the chroot tool, so we just have to mention one in a
-# configuration.
-LOCAL_REQUIRED_MODULES := otapreopt otapreopt_chroot
+# Let this depend on otapreopt, the chroot tool and the slot script, so we just have to mention one
+# in a configuration.
+LOCAL_REQUIRED_MODULES := otapreopt otapreopt_chroot otapreopt_slot
include $(BUILD_PREBUILT)
diff --git a/cmds/installd/commands.cpp b/cmds/installd/commands.cpp
index e6680ed..95451bf 100644
--- a/cmds/installd/commands.cpp
+++ b/cmds/installd/commands.cpp
@@ -18,6 +18,7 @@
#include <errno.h>
#include <inttypes.h>
+#include <regex>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/file.h>
@@ -28,9 +29,9 @@
#include <sys/xattr.h>
#include <unistd.h>
+#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <cutils/fs.h>
#include <cutils/log.h> // TODO: Move everything to base/logging.
@@ -43,12 +44,14 @@
#include <globals.h>
#include <installd_deps.h>
+#include <otapreopt_utils.h>
#include <utils.h>
#ifndef LOG_TAG
#define LOG_TAG "installd"
#endif
+using android::base::EndsWith;
using android::base::StringPrintf;
namespace android {
@@ -57,6 +60,26 @@
static constexpr const char* kCpPath = "/system/bin/cp";
static constexpr const char* kXattrDefault = "user.default";
+static constexpr const char* PKG_LIB_POSTFIX = "/lib";
+static constexpr const char* CACHE_DIR_POSTFIX = "/cache";
+static constexpr const char* CODE_CACHE_DIR_POSTFIX = "/code_cache";
+
+static constexpr const char* IDMAP_PREFIX = "/data/resource-cache/";
+static constexpr const char* IDMAP_SUFFIX = "@idmap";
+
+// NOTE: keep in sync with StorageManager
+static constexpr int FLAG_STORAGE_DE = 1 << 0;
+static constexpr int FLAG_STORAGE_CE = 1 << 1;
+
+// NOTE: keep in sync with Installer
+static constexpr int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
+static constexpr int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
+
+/* dexopt needed flags matching those in dalvik.system.DexFile */
+static constexpr int DEXOPT_DEX2OAT_NEEDED = 1;
+static constexpr int DEXOPT_PATCHOAT_NEEDED = 2;
+static constexpr int DEXOPT_SELF_PATCHOAT_NEEDED = 3;
+
#define MIN_RESTRICTED_HOME_SDK_VERSION 24 // > M
typedef int fd_t;
@@ -754,6 +777,16 @@
sprintf(image_format_arg, "--image-format=%s", app_image_format);
}
+ char dex2oat_large_app_threshold[kPropertyValueMax];
+ bool have_dex2oat_large_app_threshold =
+ get_property("dalvik.vm.dex2oat-very-large", dex2oat_large_app_threshold, NULL) > 0;
+ char dex2oat_large_app_threshold_arg[strlen("--very-large-app-threshold=") + kPropertyValueMax];
+ if (have_dex2oat_large_app_threshold) {
+ sprintf(dex2oat_large_app_threshold_arg,
+ "--very-large-app-threshold=%s",
+ dex2oat_large_app_threshold);
+ }
+
static const char* DEX2OAT_BIN = "/system/bin/dex2oat";
static const char* RUNTIME_ARG = "--runtime-arg";
@@ -854,7 +887,8 @@
+ (have_app_image_format ? 1 : 0)
+ dex2oat_flags_count
+ (profile_fd == -1 ? 0 : 1)
- + (shared_libraries != nullptr ? 4 : 0)];
+ + (shared_libraries != nullptr ? 4 : 0)
+ + (have_dex2oat_large_app_threshold ? 1 : 0)];
int i = 0;
argv[i++] = DEX2OAT_BIN;
argv[i++] = zip_fd_arg;
@@ -897,6 +931,9 @@
if (have_app_image_format) {
argv[i++] = image_format_arg;
}
+ if (have_dex2oat_large_app_threshold) {
+ argv[i++] = dex2oat_large_app_threshold_arg;
+ }
if (dex2oat_flags_count) {
i += split(dex2oat_flags, argv + i);
}
@@ -1313,13 +1350,29 @@
return true;
}
-static void trim_extension(char* path) {
- // Trim the extension.
- int pos = strlen(path);
- for (; pos >= 0 && path[pos] != '.'; --pos) {}
- if (pos >= 0) {
- path[pos] = '\0'; // Trim extension
+// Translate the given oat path to an art (app image) path. An empty string
+// denotes an error.
+static std::string create_image_filename(const std::string& oat_path) {
+ // A standard dalvik-cache entry. Replace ".dex" with ".art."
+ if (EndsWith(oat_path, ".dex")) {
+ std::string art_path = oat_path;
+ art_path.replace(art_path.length() - strlen("dex"), strlen("dex"), "art");
+ CHECK(EndsWith(art_path, ".art"));
+ return art_path;
}
+
+ // An odex entry. Not that this may not be an extension, e.g., in the OTA
+ // case (where the base name will have an extension for the B artifact).
+ size_t odex_pos = oat_path.rfind(".odex");
+ if (odex_pos != std::string::npos) {
+ std::string art_path = oat_path;
+ art_path.replace(odex_pos, strlen(".odex"), ".art");
+ CHECK_NE(art_path.find(".art"), std::string::npos);
+ return art_path;
+ }
+
+ // Don't know how to handle this.
+ return "";
}
static bool add_extension_to_file_name(char* file_name, const char* extension) {
@@ -1330,7 +1383,7 @@
return true;
}
-static int open_output_file(char* file_name, bool recreate, int permissions) {
+static int open_output_file(const char* file_name, bool recreate, int permissions) {
int flags = O_RDWR | O_CREAT;
if (recreate) {
if (unlink(file_name) < 0) {
@@ -1388,19 +1441,110 @@
return analyse_profiles(uid, pkgname);
}
+static const char* parse_null(const char* arg) {
+ if (strcmp(arg, "!") == 0) {
+ return nullptr;
+ } else {
+ return arg;
+ }
+}
+
+int dexopt(const char* const params[DEXOPT_PARAM_COUNT]) {
+ return dexopt(params[0], // apk_path
+ atoi(params[1]), // uid
+ params[2], // pkgname
+ params[3], // instruction_set
+ atoi(params[4]), // dexopt_needed
+ params[5], // oat_dir
+ atoi(params[6]), // dexopt_flags
+ params[7], // compiler_filter
+ parse_null(params[8]), // volume_uuid
+ parse_null(params[9])); // shared_libraries
+ static_assert(DEXOPT_PARAM_COUNT == 10U, "Unexpected dexopt param count");
+}
+
+// Helper for fd management. This is similar to a unique_fd in that it closes the file descriptor
+// on destruction. It will also run the given cleanup (unless told not to) after closing.
+//
+// Usage example:
+//
+// Dex2oatFileWrapper<std::function<void ()>> file(open(...),
+// [name]() {
+// unlink(name.c_str());
+// });
+// // Note: care needs to be taken about name, as it needs to have a lifetime longer than the
+// wrapper if captured as a reference.
+//
+// if (file.get() == -1) {
+// // Error opening...
+// }
+//
+// ...
+// if (error) {
+// // At this point, when the Dex2oatFileWrapper is destructed, the cleanup function will run
+// // and delete the file (after the fd is closed).
+// return -1;
+// }
+//
+// (Success case)
+// file.SetCleanup(false);
+// // At this point, when the Dex2oatFileWrapper is destructed, the cleanup function will not run
+// // (leaving the file around; after the fd is closed).
+//
+template <typename Cleanup>
+class Dex2oatFileWrapper {
+ public:
+ Dex2oatFileWrapper() : value_(-1), cleanup_(), do_cleanup_(true) {
+ }
+
+ Dex2oatFileWrapper(int value, Cleanup cleanup)
+ : value_(value), cleanup_(cleanup), do_cleanup_(true) {}
+
+ ~Dex2oatFileWrapper() {
+ reset(-1);
+ }
+
+ int get() {
+ return value_;
+ }
+
+ void SetCleanup(bool cleanup) {
+ do_cleanup_ = cleanup;
+ }
+
+ void reset(int new_value) {
+ if (value_ >= 0) {
+ close(value_);
+ }
+ if (do_cleanup_ && cleanup_ != nullptr) {
+ cleanup_();
+ }
+
+ value_ = new_value;
+ }
+
+ void reset(int new_value, Cleanup new_cleanup) {
+ if (value_ >= 0) {
+ close(value_);
+ }
+ if (do_cleanup_ && cleanup_ != nullptr) {
+ cleanup_();
+ }
+
+ value_ = new_value;
+ cleanup_ = new_cleanup;
+ }
+
+ private:
+ int value_;
+ Cleanup cleanup_;
+ bool do_cleanup_;
+};
+
int dexopt(const char* apk_path, uid_t uid, const char* pkgname, const char* instruction_set,
int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
const char* volume_uuid ATTRIBUTE_UNUSED, const char* shared_libraries)
{
- struct utimbuf ut;
- struct stat input_stat;
- char out_path[PKG_PATH_MAX];
- char swap_file_name[PKG_PATH_MAX];
- char image_path[PKG_PATH_MAX];
- const char *input_file;
- char in_odex_path[PKG_PATH_MAX];
- int res;
- fd_t input_fd=-1, out_fd=-1, image_fd=-1, swap_fd=-1;
bool is_public = ((dexopt_flags & DEXOPT_PUBLIC) != 0);
bool vm_safe_mode = (dexopt_flags & DEXOPT_SAFEMODE) != 0;
bool debuggable = (dexopt_flags & DEXOPT_DEBUGGABLE) != 0;
@@ -1410,12 +1554,16 @@
CHECK(pkgname != nullptr);
CHECK(pkgname[0] != 0);
- fd_t reference_profile_fd = -1;
// Public apps should not be compiled with profile information ever. Same goes for the special
// package '*' used for the system server.
+ Dex2oatFileWrapper<std::function<void ()>> reference_profile_fd;
if (!is_public && pkgname[0] != '*') {
// Open reference profile in read only mode as dex2oat does not get write permissions.
- reference_profile_fd = open_reference_profile(uid, pkgname, /*read_write*/ false);
+ const std::string pkgname_str(pkgname);
+ reference_profile_fd.reset(open_reference_profile(uid, pkgname, /*read_write*/ false),
+ [pkgname_str]() {
+ clear_reference_profile(pkgname_str.c_str());
+ });
// Note: it's OK to not find a profile here.
}
@@ -1423,10 +1571,13 @@
LOG_FATAL("dexopt flags contains unknown fields\n");
}
+ char out_path[PKG_PATH_MAX];
if (!create_oat_out_path(apk_path, instruction_set, oat_dir, out_path)) {
return false;
}
+ const char *input_file;
+ char in_odex_path[PKG_PATH_MAX];
switch (dexopt_needed) {
case DEXOPT_DEX2OAT_NEEDED:
input_file = apk_path;
@@ -1445,35 +1596,41 @@
default:
ALOGE("Invalid dexopt needed: %d\n", dexopt_needed);
- exit(72);
+ return 72;
}
+ struct stat input_stat;
memset(&input_stat, 0, sizeof(input_stat));
stat(input_file, &input_stat);
- input_fd = open(input_file, O_RDONLY, 0);
- if (input_fd < 0) {
+ base::unique_fd input_fd(open(input_file, O_RDONLY, 0));
+ if (input_fd.get() < 0) {
ALOGE("installd cannot open '%s' for input during dexopt\n", input_file);
return -1;
}
- out_fd = open_output_file(out_path, /*recreate*/true, /*permissions*/0644);
- if (out_fd < 0) {
+ const std::string out_path_str(out_path);
+ Dex2oatFileWrapper<std::function<void ()>> out_fd(
+ open_output_file(out_path, /*recreate*/true, /*permissions*/0644),
+ [out_path_str]() { unlink(out_path_str.c_str()); });
+ if (out_fd.get() < 0) {
ALOGE("installd cannot open '%s' for output during dexopt\n", out_path);
- goto fail;
+ return -1;
}
- if (!set_permissions_and_ownership(out_fd, is_public, uid, out_path)) {
- goto fail;
+ if (!set_permissions_and_ownership(out_fd.get(), is_public, uid, out_path)) {
+ return -1;
}
// Create a swap file if necessary.
+ base::unique_fd swap_fd;
if (ShouldUseSwapFileForDexopt()) {
// Make sure there really is enough space.
+ char swap_file_name[PKG_PATH_MAX];
strcpy(swap_file_name, out_path);
if (add_extension_to_file_name(swap_file_name, ".swap")) {
- swap_fd = open_output_file(swap_file_name, /*recreate*/true, /*permissions*/0600);
+ swap_fd.reset(open_output_file(swap_file_name, /*recreate*/true, /*permissions*/0600));
}
- if (swap_fd < 0) {
+ if (swap_fd.get() < 0) {
// Could not create swap file. Optimistically go on and hope that we can compile
// without it.
ALOGE("installd could not create '%s' for swap during dexopt\n", swap_file_name);
@@ -1486,111 +1643,108 @@
}
// Avoid generating an app image for extract only since it will not contain any classes.
- strcpy(image_path, out_path);
- trim_extension(image_path);
- if (add_extension_to_file_name(image_path, ".art")) {
- char app_image_format[kPropertyValueMax];
- bool have_app_image_format =
- get_property("dalvik.vm.appimageformat", app_image_format, NULL) > 0;
- // Use app images only if it is enabled (by a set image format) and we are compiling
- // profile-guided (so the app image doesn't conservatively contain all classes).
- if (profile_guided && have_app_image_format) {
- // Recreate is true since we do not want to modify a mapped image. If the app is already
- // running and we modify the image file, it can cause crashes (b/27493510).
- image_fd = open_output_file(image_path, /*recreate*/true, /*permissions*/0600);
- if (image_fd < 0) {
- // Could not create application image file. Go on since we can compile without it.
- ALOGE("installd could not create '%s' for image file during dexopt\n", image_path);
- } else if (!set_permissions_and_ownership(image_fd, is_public, uid, image_path)) {
- image_fd = -1;
- }
- }
- // If we have a valid image file path but no image fd, erase the image file.
- if (image_fd < 0) {
- if (unlink(image_path) < 0) {
- if (errno != ENOENT) {
- PLOG(ERROR) << "Couldn't unlink image file " << image_path;
- }
- }
- }
+ Dex2oatFileWrapper<std::function<void ()>> image_fd;
+ const std::string image_path = create_image_filename(out_path);
+ if (!image_path.empty()) {
+ char app_image_format[kPropertyValueMax];
+ bool have_app_image_format =
+ get_property("dalvik.vm.appimageformat", app_image_format, NULL) > 0;
+ // Use app images only if it is enabled (by a set image format) and we are compiling
+ // profile-guided (so the app image doesn't conservatively contain all classes).
+ if (profile_guided && have_app_image_format) {
+ // Recreate is true since we do not want to modify a mapped image. If the app is
+ // already running and we modify the image file, it can cause crashes (b/27493510).
+ image_fd.reset(open_output_file(image_path.c_str(),
+ true /*recreate*/,
+ 0600 /*permissions*/),
+ [image_path]() { unlink(image_path.c_str()); }
+ );
+ if (image_fd.get() < 0) {
+ // Could not create application image file. Go on since we can compile without
+ // it.
+ LOG(ERROR) << "installd could not create '"
+ << image_path
+ << "' for image file during dexopt";
+ } else if (!set_permissions_and_ownership(image_fd.get(),
+ is_public,
+ uid,
+ image_path.c_str())) {
+ image_fd.reset(-1);
+ }
+ }
+ // If we have a valid image file path but no image fd, explicitly erase the image file.
+ if (image_fd.get() < 0) {
+ if (unlink(image_path.c_str()) < 0) {
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "Couldn't unlink image file " << image_path;
+ }
+ }
+ }
}
ALOGV("DexInv: --- BEGIN '%s' ---\n", input_file);
- pid_t pid;
- pid = fork();
+ pid_t pid = fork();
if (pid == 0) {
/* child -- drop privileges before continuing */
drop_capabilities(uid);
SetDex2OatAndPatchOatScheduling(boot_complete);
- if (flock(out_fd, LOCK_EX | LOCK_NB) != 0) {
+ if (flock(out_fd.get(), LOCK_EX | LOCK_NB) != 0) {
ALOGE("flock(%s) failed: %s\n", out_path, strerror(errno));
- exit(67);
+ _exit(67);
}
if (dexopt_needed == DEXOPT_PATCHOAT_NEEDED
|| dexopt_needed == DEXOPT_SELF_PATCHOAT_NEEDED) {
- run_patchoat(input_fd, out_fd, input_file, out_path, pkgname, instruction_set);
+ run_patchoat(input_fd.get(),
+ out_fd.get(),
+ input_file,
+ out_path,
+ pkgname,
+ instruction_set);
} else if (dexopt_needed == DEXOPT_DEX2OAT_NEEDED) {
// Pass dex2oat the relative path to the input file.
const char *input_file_name = get_location_from_path(input_file);
- run_dex2oat(input_fd, out_fd, image_fd, input_file_name, out_path, swap_fd,
- instruction_set, compiler_filter, vm_safe_mode, debuggable, boot_complete,
- reference_profile_fd, shared_libraries);
+ run_dex2oat(input_fd.get(),
+ out_fd.get(),
+ image_fd.get(),
+ input_file_name,
+ out_path,
+ swap_fd.get(),
+ instruction_set,
+ compiler_filter,
+ vm_safe_mode,
+ debuggable,
+ boot_complete,
+ reference_profile_fd.get(),
+ shared_libraries);
} else {
ALOGE("Invalid dexopt needed: %d\n", dexopt_needed);
- exit(73);
+ _exit(73);
}
- exit(68); /* only get here on exec failure */
+ _exit(68); /* only get here on exec failure */
} else {
- res = wait_child(pid);
+ int res = wait_child(pid);
if (res == 0) {
ALOGV("DexInv: --- END '%s' (success) ---\n", input_file);
} else {
ALOGE("DexInv: --- END '%s' --- status=0x%04x, process failed\n", input_file, res);
- goto fail;
+ return -1;
}
}
+ struct utimbuf ut;
ut.actime = input_stat.st_atime;
ut.modtime = input_stat.st_mtime;
utime(out_path, &ut);
- close(out_fd);
- close(input_fd);
- if (swap_fd >= 0) {
- close(swap_fd);
- }
- if (reference_profile_fd >= 0) {
- close(reference_profile_fd);
- }
- if (image_fd >= 0) {
- close(image_fd);
- }
- return 0;
+ // We've been successful, don't delete output.
+ out_fd.SetCleanup(false);
+ image_fd.SetCleanup(false);
+ reference_profile_fd.SetCleanup(false);
-fail:
- if (out_fd >= 0) {
- close(out_fd);
- unlink(out_path);
- }
- if (input_fd >= 0) {
- close(input_fd);
- }
- if (reference_profile_fd >= 0) {
- close(reference_profile_fd);
- // We failed to compile. Unlink the reference profile. Current profiles are already unlinked
- // when profmoan advises compilation.
- clear_reference_profile(pkgname);
- }
- if (swap_fd >= 0) {
- close(swap_fd);
- }
- if (image_fd >= 0) {
- close(image_fd);
- }
- return -1;
+ return 0;
}
int mark_boot_complete(const char* instruction_set)
@@ -1925,11 +2079,59 @@
return true;
}
+// Move/rename a B artifact (from) to an A artifact (to).
+static bool move_ab_path(const std::string& b_path, const std::string& a_path) {
+ // Check whether B exists.
+ {
+ struct stat s;
+ if (stat(b_path.c_str(), &s) != 0) {
+ // Silently ignore for now. The service calling this isn't smart enough to understand
+ // lack of artifacts at the moment.
+ return false;
+ }
+ if (!S_ISREG(s.st_mode)) {
+ LOG(ERROR) << "A/B artifact " << b_path << " is not a regular file.";
+ // Try to unlink, but swallow errors.
+ unlink(b_path.c_str());
+ return false;
+ }
+ }
+
+ // Rename B to A.
+ if (!unlink_and_rename(b_path.c_str(), a_path.c_str())) {
+ // Delete the b_path so we don't try again (or fail earlier).
+ if (unlink(b_path.c_str()) != 0) {
+ PLOG(ERROR) << "Could not unlink " << b_path;
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
int move_ab(const char* apk_path, const char* instruction_set, const char* oat_dir) {
if (apk_path == nullptr || instruction_set == nullptr || oat_dir == nullptr) {
LOG(ERROR) << "Cannot move_ab with null input";
return -1;
}
+
+ // Get the current slot suffix. No suffix, no A/B.
+ std::string slot_suffix;
+ {
+ char buf[kPropertyValueMax];
+ if (get_property("ro.boot.slot_suffix", buf, nullptr) <= 0) {
+ return -1;
+ }
+ slot_suffix = buf;
+
+ if (!ValidateTargetSlotSuffix(slot_suffix)) {
+ LOG(ERROR) << "Target slot suffix not legal: " << slot_suffix;
+ return -1;
+ }
+ }
+
+ // Validate other inputs.
if (validate_apk_path(apk_path) != 0) {
LOG(ERROR) << "invalid apk_path " << apk_path;
return -1;
@@ -1943,37 +2145,37 @@
if (!calculate_oat_file_path(a_path, oat_dir, apk_path, instruction_set)) {
return -1;
}
+ const std::string a_image_path = create_image_filename(a_path);
- // B path = A path + ".b"
- std::string b_path = StringPrintf("%s.b", a_path);
+ // B path = A path + slot suffix.
+ const std::string b_path = StringPrintf("%s.%s", a_path, slot_suffix.c_str());
+ const std::string b_image_path = StringPrintf("%s.%s",
+ a_image_path.c_str(),
+ slot_suffix.c_str());
- // Check whether B exists.
- {
- struct stat s;
- if (stat(b_path.c_str(), &s) != 0) {
- // Silently ignore for now. The service calling this isn't smart enough to understand
- // lack of artifacts at the moment.
- return -1;
+ bool oat_success = move_ab_path(b_path, a_path);
+ bool success;
+
+ if (oat_success) {
+ // Note: we can live without an app image. As such, ignore failure to move the image file.
+ // If we decide to require the app image, or the app image being moved correctly,
+ // then change accordingly.
+ constexpr bool kIgnoreAppImageFailure = true;
+
+ bool art_success = true;
+ if (!a_image_path.empty()) {
+ art_success = move_ab_path(b_image_path, a_image_path);
}
- if (!S_ISREG(s.st_mode)) {
- LOG(ERROR) << "A/B artifact " << b_path << " is not a regular file.";
- // Try to unlink, but swallow errors.
- unlink(b_path.c_str());
- return -1;
- }
+
+ success = art_success || kIgnoreAppImageFailure;
+ } else {
+ // Cleanup: delete B image, ignore errors.
+ unlink(b_image_path.c_str());
+
+ success = false;
}
- // Rename B to A.
- if (!unlink_and_rename(b_path.c_str(), a_path)) {
- // Delete the b_path so we don't try again (or fail earlier).
- if (unlink(b_path.c_str()) != 0) {
- PLOG(ERROR) << "Could not unlink " << b_path;
- }
-
- return -1;
- }
-
- return 0;
+ return success ? 0 : -1;
}
} // namespace installd
diff --git a/cmds/installd/commands.h b/cmds/installd/commands.h
index 7a42c5c..e990f1b 100644
--- a/cmds/installd/commands.h
+++ b/cmds/installd/commands.h
@@ -28,6 +28,8 @@
namespace android {
namespace installd {
+static constexpr size_t DEXOPT_PARAM_COUNT = 10U;
+
int create_app_data(const char *uuid, const char *pkgname, userid_t userid, int flags,
appid_t appid, const char* seinfo, int target_sdk_version);
int restorecon_app_data(const char* uuid, const char* pkgName, userid_t userid, int flags,
@@ -56,9 +58,21 @@
bool dump_profile(uid_t uid, const char *pkgname, const char *dex_files);
-int dexopt(const char *apk_path, uid_t uid, const char *pkgName, const char *instruction_set,
- int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
- const char* volume_uuid, const char* shared_libraries);
+int dexopt(const char *apk_path,
+ uid_t uid,
+ const char *pkgName,
+ const char *instruction_set,
+ int dexopt_needed,
+ const char* oat_dir,
+ int dexopt_flags,
+ const char* compiler_filter,
+ const char* volume_uuid,
+ const char* shared_libraries);
+static_assert(DEXOPT_PARAM_COUNT == 10U, "Unexpected dexopt param size");
+
+// Helper for the above, converting arguments.
+int dexopt(const char* const params[DEXOPT_PARAM_COUNT]);
+
int mark_boot_complete(const char *instruction_set);
int linklib(const char* uuid, const char* pkgname, const char* asecLibDir, int userId);
int idmap(const char *target_path, const char *overlay_path, uid_t uid);
diff --git a/cmds/installd/globals.cpp b/cmds/installd/globals.cpp
index 6a67e29..93e1ce5 100644
--- a/cmds/installd/globals.cpp
+++ b/cmds/installd/globals.cpp
@@ -30,6 +30,22 @@
namespace android {
namespace installd {
+static constexpr const char* APP_SUBDIR = "app/"; // sub-directory under ANDROID_DATA
+
+static constexpr const char* PRIV_APP_SUBDIR = "priv-app/"; // sub-directory under ANDROID_DATA
+
+static constexpr const char* EPHEMERAL_APP_SUBDIR = "app-ephemeral/"; // sub-directory under
+ // ANDROID_DATA
+
+static constexpr const char* APP_LIB_SUBDIR = "app-lib/"; // sub-directory under ANDROID_DATA
+
+static constexpr const char* MEDIA_SUBDIR = "media/"; // sub-directory under ANDROID_DATA
+
+static constexpr const char* PROFILES_SUBDIR = "misc/profiles"; // sub-directory under ANDROID_DATA
+
+static constexpr const char* PRIVATE_APP_SUBDIR = "app-private/"; // sub-directory under
+ // ANDROID_DATA
+
/* Directory records that are used in execution of commands. */
dir_rec_t android_app_dir;
dir_rec_t android_app_ephemeral_dir;
@@ -77,7 +93,7 @@
// Get the android ephemeral app directory.
if (copy_and_append(&android_app_ephemeral_dir, &android_data_dir, EPHEMERAL_APP_SUBDIR) < 0) {
- return -1;
+ return false;
}
// Get the android app native library directory.
@@ -86,7 +102,7 @@
}
// Get the sd-card ASEC mount point.
- if (get_path_from_env(&android_asec_dir, "ASEC_MOUNTPOINT") < 0) {
+ if (get_path_from_env(&android_asec_dir, ASEC_MOUNTPOINT_ENV_NAME) < 0) {
return false;
}
diff --git a/cmds/installd/globals.h b/cmds/installd/globals.h
index 3e52346..c90beec 100644
--- a/cmds/installd/globals.h
+++ b/cmds/installd/globals.h
@@ -23,6 +23,11 @@
namespace android {
namespace installd {
+/* constants */
+
+// Name of the environment variable that contains the asec mountpoint.
+static constexpr const char* ASEC_MOUNTPOINT_ENV_NAME = "ASEC_MOUNTPOINT";
+
/* data structures */
struct dir_rec_t {
diff --git a/cmds/installd/installd.cpp b/cmds/installd/installd.cpp
index d5c3ccd..531c6df 100644
--- a/cmds/installd/installd.cpp
+++ b/cmds/installd/installd.cpp
@@ -219,7 +219,8 @@
// We use otapreopt_chroot to get into the chroot.
static constexpr const char* kOtaPreopt = "/system/bin/otapreopt_chroot";
-static int do_ota_dexopt(char **arg, char reply[REPLY_MAX] ATTRIBUTE_UNUSED) {
+static int do_ota_dexopt(const char* args[DEXOPT_PARAM_COUNT],
+ char reply[REPLY_MAX] ATTRIBUTE_UNUSED) {
// Time to fork and run otapreopt.
// Check that the tool exists.
@@ -231,12 +232,14 @@
pid_t pid = fork();
if (pid == 0) {
- const char* argv[1 + 9 + 1];
+ const char* argv[1 + DEXOPT_PARAM_COUNT + 1];
argv[0] = kOtaPreopt;
- for (size_t i = 1; i <= 9; ++i) {
- argv[i] = arg[i - 1];
+
+ for (size_t i = 0; i < DEXOPT_PARAM_COUNT; ++i) {
+ argv[i + 1] = args[i];
}
- argv[10] = nullptr;
+
+ argv[DEXOPT_PARAM_COUNT + 1] = nullptr;
execv(argv[0], (char * const *)argv);
PLOG(ERROR) << "execv(OTAPREOPT_CHROOT) failed";
@@ -252,22 +255,30 @@
}
}
+static int do_regular_dexopt(const char* args[DEXOPT_PARAM_COUNT],
+ char reply[REPLY_MAX] ATTRIBUTE_UNUSED) {
+ return dexopt(args);
+}
+
+using DexoptFn = int (*)(const char* args[DEXOPT_PARAM_COUNT],
+ char reply[REPLY_MAX]);
+
static int do_dexopt(char **arg, char reply[REPLY_MAX])
{
- int dexopt_flags = atoi(arg[6]);
- if ((dexopt_flags & DEXOPT_OTA) != 0) {
- return do_ota_dexopt(arg, reply);
+ const char* args[DEXOPT_PARAM_COUNT];
+ for (size_t i = 0; i < DEXOPT_PARAM_COUNT; ++i) {
+ CHECK(arg[i] != nullptr);
+ args[i] = arg[i];
}
- return dexopt(arg[0], // apk_path
- atoi(arg[1]), // uid
- arg[2], // pkgname
- arg[3], // instruction_set
- atoi(arg[4]), // dexopt_needed
- arg[5], // oat_dir
- dexopt_flags,
- arg[7], // compiler_filter
- parse_null(arg[8]), // volume_uuid
- parse_null(arg[9])); // shared_libraries
+
+ int dexopt_flags = atoi(arg[6]);
+ DexoptFn dexopt_fn;
+ if ((dexopt_flags & DEXOPT_OTA) != 0) {
+ dexopt_fn = do_ota_dexopt;
+ } else {
+ dexopt_fn = do_regular_dexopt;
+ }
+ return dexopt_fn(args, reply);
}
static int do_merge_profiles(char **arg, char reply[REPLY_MAX])
@@ -546,7 +557,7 @@
return 0;
}
-bool initialize_globals() {
+static bool initialize_globals() {
const char* data_path = getenv("ANDROID_DATA");
if (data_path == nullptr) {
ALOGE("Could not find ANDROID_DATA");
diff --git a/cmds/installd/installd_constants.h b/cmds/installd/installd_constants.h
index dfde727..b0bcce9 100644
--- a/cmds/installd/installd_constants.h
+++ b/cmds/installd/installd_constants.h
@@ -26,50 +26,13 @@
constexpr const char* PRIMARY_USER_PREFIX = "data/";
constexpr const char* SECONDARY_USER_PREFIX = "user/";
-constexpr const char* PKG_DIR_POSTFIX = "";
-
-constexpr const char* PKG_LIB_POSTFIX = "/lib";
-
-constexpr const char* CACHE_DIR_POSTFIX = "/cache";
-constexpr const char* CODE_CACHE_DIR_POSTFIX = "/code_cache";
-
-constexpr const char* APP_SUBDIR = "app/"; // sub-directory under ANDROID_DATA
-constexpr const char* PRIV_APP_SUBDIR = "priv-app/"; // sub-directory under ANDROID_DATA
-constexpr const char* EPHEMERAL_APP_SUBDIR = "app-ephemeral/"; // sub-directory under ANDROID_DATA
-
-constexpr const char* APP_LIB_SUBDIR = "app-lib/"; // sub-directory under ANDROID_DATA
-
-constexpr const char* MEDIA_SUBDIR = "media/"; // sub-directory under ANDROID_DATA
-
-constexpr const char* PROFILES_SUBDIR = "misc/profiles"; // sub-directory under ANDROID_DATA
-
-/* other handy constants */
-
-constexpr const char* PRIVATE_APP_SUBDIR = "app-private/"; // sub-directory under ANDROID_DATA
-
// This is used as a string literal, can't be constants. TODO: std::string...
#define DALVIK_CACHE "dalvik-cache"
constexpr const char* DALVIK_CACHE_POSTFIX = "@classes.dex";
-constexpr const char* IDMAP_PREFIX = "/data/resource-cache/";
-constexpr const char* IDMAP_SUFFIX = "@idmap";
-
constexpr size_t PKG_NAME_MAX = 128u; /* largest allowed package name */
constexpr size_t PKG_PATH_MAX = 256u; /* max size of any path we use */
-// NOTE: keep in sync with StorageManager
-constexpr int FLAG_STORAGE_DE = 1 << 0;
-constexpr int FLAG_STORAGE_CE = 1 << 1;
-
-// NOTE: keep in sync with Installer
-constexpr int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
-constexpr int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
-
-/* dexopt needed flags matching those in dalvik.system.DexFile */
-constexpr int DEXOPT_DEX2OAT_NEEDED = 1;
-constexpr int DEXOPT_PATCHOAT_NEEDED = 2;
-constexpr int DEXOPT_SELF_PATCHOAT_NEEDED = 3;
-
/****************************************************************************
* IMPORTANT: These values are passed from Java code. Keep them in sync with
* frameworks/base/services/core/java/com/android/server/pm/Installer.java
diff --git a/cmds/installd/installd_deps.h b/cmds/installd/installd_deps.h
index 5ff46e6..5093178 100644
--- a/cmds/installd/installd_deps.h
+++ b/cmds/installd/installd_deps.h
@@ -57,9 +57,6 @@
const char *src,
const char *instruction_set);
-// Initialize globals. May be implemented with the helper in globals.h.
-extern bool initialize_globals();
-
} // namespace installd
} // namespace android
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index 8b9c931..7e02b6b 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -30,6 +30,7 @@
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <cutils/fs.h>
#include <cutils/log.h>
#include <cutils/properties.h>
@@ -39,7 +40,7 @@
#include <file_parsing.h>
#include <globals.h>
#include <installd_deps.h> // Need to fill in requirements of commands.
-#include <string_helpers.h>
+#include <otapreopt_utils.h>
#include <system_properties.h>
#include <utils.h>
@@ -51,16 +52,15 @@
#define TOKEN_MAX 16 /* max number of arguments in buffer */
#define REPLY_MAX 256 /* largest reply allowed */
+using android::base::EndsWith;
+using android::base::Join;
+using android::base::Split;
+using android::base::StartsWith;
using android::base::StringPrintf;
namespace android {
namespace installd {
-static constexpr const char* kBootClassPathPropertyName = "BOOTCLASSPATH";
-static constexpr const char* kAndroidRootPathPropertyName = "ANDROID_ROOT";
-static constexpr const char* kOTARootDirectory = "/system-b";
-static constexpr size_t kISAIndex = 3;
-
template<typename T>
static constexpr T RoundDown(T x, typename std::decay<T>::type n) {
return DCHECK_CONSTEXPR(IsPowerOfTwo(n), , T(0))(x & -n);
@@ -73,8 +73,6 @@
class OTAPreoptService {
public:
- static constexpr const char* kOTADataDirectory = "/data/ota";
-
// Main driver. Performs the following steps.
//
// 1) Parse options (read system properties etc from B partition).
@@ -87,26 +85,31 @@
//
// 5) Run update.
int Main(int argc, char** argv) {
+ if (!ReadArguments(argc, argv)) {
+ LOG(ERROR) << "Failed reading command line.";
+ return 1;
+ }
+
if (!ReadSystemProperties()) {
LOG(ERROR)<< "Failed reading system properties.";
- return 1;
+ return 2;
}
if (!ReadEnvironment()) {
LOG(ERROR) << "Failed reading environment properties.";
- return 2;
+ return 3;
}
- if (!ReadPackage(argc, argv)) {
- LOG(ERROR) << "Failed reading command line file.";
- return 3;
+ if (!CheckAndInitializeInstalldGlobals()) {
+ LOG(ERROR) << "Failed initializing globals.";
+ return 4;
}
PrepareEnvironment();
- if (!PrepareBootImage()) {
+ if (!PrepareBootImage(/* force */ false)) {
LOG(ERROR) << "Failed preparing boot image.";
- return 4;
+ return 5;
}
int dexopt_retcode = RunPreopt();
@@ -114,7 +117,7 @@
return dexopt_retcode;
}
- int GetProperty(const char* key, char* value, const char* default_value) {
+ int GetProperty(const char* key, char* value, const char* default_value) const {
const std::string* prop_value = system_properties_.GetProperty(key);
if (prop_value == nullptr) {
if (default_value == nullptr) {
@@ -131,7 +134,16 @@
return static_cast<int>(size);
}
+ std::string GetOTADataDirectory() const {
+ return StringPrintf("%s/%s", GetOtaDirectoryPrefix().c_str(), target_slot_.c_str());
+ }
+
+ const std::string& GetTargetSlot() const {
+ return target_slot_;
+ }
+
private:
+
bool ReadSystemProperties() {
static constexpr const char* kPropertyFiles[] = {
"/default.prop", "/system/build.prop"
@@ -173,27 +185,106 @@
return false;
}
- // Check that we found important properties.
- constexpr const char* kRequiredProperties[] = {
- kBootClassPathPropertyName, kAndroidRootPathPropertyName
- };
- for (size_t i = 0; i < arraysize(kRequiredProperties); ++i) {
- if (system_properties_.GetProperty(kRequiredProperties[i]) == nullptr) {
- return false;
- }
+ if (system_properties_.GetProperty(kAndroidDataPathPropertyName) == nullptr) {
+ return false;
+ }
+ android_data_ = *system_properties_.GetProperty(kAndroidDataPathPropertyName);
+
+ if (system_properties_.GetProperty(kAndroidRootPathPropertyName) == nullptr) {
+ return false;
+ }
+ android_root_ = *system_properties_.GetProperty(kAndroidRootPathPropertyName);
+
+ if (system_properties_.GetProperty(kBootClassPathPropertyName) == nullptr) {
+ return false;
+ }
+ boot_classpath_ = *system_properties_.GetProperty(kBootClassPathPropertyName);
+
+ if (system_properties_.GetProperty(ASEC_MOUNTPOINT_ENV_NAME) == nullptr) {
+ return false;
+ }
+ asec_mountpoint_ = *system_properties_.GetProperty(ASEC_MOUNTPOINT_ENV_NAME);
+
+ return true;
+ }
+
+ const std::string& GetAndroidData() const {
+ return android_data_;
+ }
+
+ const std::string& GetAndroidRoot() const {
+ return android_root_;
+ }
+
+ const std::string GetOtaDirectoryPrefix() const {
+ return GetAndroidData() + "/ota";
+ }
+
+ bool CheckAndInitializeInstalldGlobals() {
+ // init_globals_from_data_and_root requires "ASEC_MOUNTPOINT" in the environment. We
+ // do not use any datapath that includes this, but we'll still have to set it.
+ CHECK(system_properties_.GetProperty(ASEC_MOUNTPOINT_ENV_NAME) != nullptr);
+ int result = setenv(ASEC_MOUNTPOINT_ENV_NAME, asec_mountpoint_.c_str(), 0);
+ if (result != 0) {
+ LOG(ERROR) << "Could not set ASEC_MOUNTPOINT environment variable";
+ return false;
+ }
+
+ if (!init_globals_from_data_and_root(GetAndroidData().c_str(), GetAndroidRoot().c_str())) {
+ LOG(ERROR) << "Could not initialize globals; exiting.";
+ return false;
+ }
+
+ // This is different from the normal installd. We only do the base
+ // directory, the rest will be created on demand when each app is compiled.
+ if (access(GetOtaDirectoryPrefix().c_str(), R_OK) < 0) {
+ LOG(ERROR) << "Could not access " << GetOtaDirectoryPrefix();
+ return false;
}
return true;
}
- bool ReadPackage(int argc ATTRIBUTE_UNUSED, char** argv) {
+ bool ReadArguments(int argc ATTRIBUTE_UNUSED, char** argv) {
+ // Expected command line:
+ // target-slot dexopt {DEXOPT_PARAMETERS}
+ // The DEXOPT_PARAMETERS are passed on to dexopt(), so we expect DEXOPT_PARAM_COUNT
+ // of them. We store them in package_parameters_ (size checks are done when
+ // parsing the special parameters and when copying into package_parameters_.
+
+ static_assert(DEXOPT_PARAM_COUNT == ARRAY_SIZE(package_parameters_),
+ "Unexpected dexopt param count");
+
+ const char* target_slot_arg = argv[1];
+ if (target_slot_arg == nullptr) {
+ LOG(ERROR) << "Missing parameters";
+ return false;
+ }
+ // Sanitize value. Only allow (a-zA-Z0-9_)+.
+ target_slot_ = target_slot_arg;
+ if (!ValidateTargetSlotSuffix(target_slot_)) {
+ LOG(ERROR) << "Target slot suffix not legal: " << target_slot_;
+ return false;
+ }
+
+ // Check for "dexopt" next.
+ if (argv[2] == nullptr) {
+ LOG(ERROR) << "Missing parameters";
+ return false;
+ }
+ if (std::string("dexopt").compare(argv[2]) != 0) {
+ LOG(ERROR) << "Second parameter not dexopt: " << argv[2];
+ return false;
+ }
+
+ // Copy the rest into package_parameters_, but be careful about over- and underflow.
size_t index = 0;
- while (index < ARRAY_SIZE(package_parameters_) &&
- argv[index + 1] != nullptr) {
- package_parameters_[index] = argv[index + 1];
+ while (index < DEXOPT_PARAM_COUNT &&
+ argv[index + 3] != nullptr) {
+ package_parameters_[index] = argv[index + 3];
index++;
}
- if (index != ARRAY_SIZE(package_parameters_)) {
+ if (index != ARRAY_SIZE(package_parameters_) || argv[index + 3] != nullptr) {
LOG(ERROR) << "Wrong number of parameters";
return false;
}
@@ -202,15 +293,9 @@
}
void PrepareEnvironment() {
- CHECK(system_properties_.GetProperty(kBootClassPathPropertyName) != nullptr);
- const std::string& boot_cp =
- *system_properties_.GetProperty(kBootClassPathPropertyName);
- environ_.push_back(StringPrintf("BOOTCLASSPATH=%s", boot_cp.c_str()));
- environ_.push_back(StringPrintf("ANDROID_DATA=%s", kOTADataDirectory));
- CHECK(system_properties_.GetProperty(kAndroidRootPathPropertyName) != nullptr);
- const std::string& android_root =
- *system_properties_.GetProperty(kAndroidRootPathPropertyName);
- environ_.push_back(StringPrintf("ANDROID_ROOT=%s", android_root.c_str()));
+ environ_.push_back(StringPrintf("BOOTCLASSPATH=%s", boot_classpath_.c_str()));
+ environ_.push_back(StringPrintf("ANDROID_DATA=%s", GetOTADataDirectory().c_str()));
+ environ_.push_back(StringPrintf("ANDROID_ROOT=%s", android_root_.c_str()));
for (const std::string& e : environ_) {
putenv(const_cast<char*>(e.c_str()));
@@ -219,7 +304,7 @@
// Ensure that we have the right boot image. The first time any app is
// compiled, we'll try to generate it.
- bool PrepareBootImage() {
+ bool PrepareBootImage(bool force) const {
if (package_parameters_[kISAIndex] == nullptr) {
LOG(ERROR) << "Instruction set missing.";
return false;
@@ -227,44 +312,114 @@
const char* isa = package_parameters_[kISAIndex];
// Check whether the file exists where expected.
- std::string dalvik_cache = std::string(kOTADataDirectory) + "/" + DALVIK_CACHE;
+ std::string dalvik_cache = GetOTADataDirectory() + "/" + DALVIK_CACHE;
std::string isa_path = dalvik_cache + "/" + isa;
std::string art_path = isa_path + "/system@framework@boot.art";
std::string oat_path = isa_path + "/system@framework@boot.oat";
- if (access(art_path.c_str(), F_OK) == 0 &&
- access(oat_path.c_str(), F_OK) == 0) {
- // Files exist, assume everything is alright.
- return true;
+ bool cleared = false;
+ if (access(art_path.c_str(), F_OK) == 0 && access(oat_path.c_str(), F_OK) == 0) {
+ // Files exist, assume everything is alright if not forced. Otherwise clean up.
+ if (!force) {
+ return true;
+ }
+ ClearDirectory(isa_path);
+ cleared = true;
}
+ // Reset umask in otapreopt, so that we control the the access for the files we create.
+ umask(0);
+
// Create the directories, if necessary.
if (access(dalvik_cache.c_str(), F_OK) != 0) {
- if (mkdir(dalvik_cache.c_str(), 0711) != 0) {
- PLOG(ERROR) << "Could not create dalvik-cache dir";
+ if (!CreatePath(dalvik_cache)) {
+ PLOG(ERROR) << "Could not create dalvik-cache dir " << dalvik_cache;
return false;
}
}
if (access(isa_path.c_str(), F_OK) != 0) {
- if (mkdir(isa_path.c_str(), 0711) != 0) {
+ if (!CreatePath(isa_path)) {
PLOG(ERROR) << "Could not create dalvik-cache isa dir";
return false;
}
}
// Prepare to create.
- // TODO: Delete files, just for a blank slate.
- const std::string& boot_cp = *system_properties_.GetProperty(kBootClassPathPropertyName);
+ if (!cleared) {
+ ClearDirectory(isa_path);
+ }
std::string preopted_boot_art_path = StringPrintf("/system/framework/%s/boot.art", isa);
if (access(preopted_boot_art_path.c_str(), F_OK) == 0) {
return PatchoatBootImage(art_path, isa);
} else {
// No preopted boot image. Try to compile.
- return Dex2oatBootImage(boot_cp, art_path, oat_path, isa);
+ return Dex2oatBootImage(boot_classpath_, art_path, oat_path, isa);
}
}
- bool PatchoatBootImage(const std::string& art_path, const char* isa) {
+ static bool CreatePath(const std::string& path) {
+ // Create the given path. Use string processing instead of dirname, as dirname's need for
+ // a writable char buffer is painful.
+
+ // First, try to use the full path.
+ if (mkdir(path.c_str(), 0711) == 0) {
+ return true;
+ }
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "Could not create path " << path;
+ return false;
+ }
+
+ // Now find the parent and try that first.
+ size_t last_slash = path.find_last_of('/');
+ if (last_slash == std::string::npos || last_slash == 0) {
+ PLOG(ERROR) << "Could not create " << path;
+ return false;
+ }
+
+ if (!CreatePath(path.substr(0, last_slash))) {
+ return false;
+ }
+
+ if (mkdir(path.c_str(), 0711) == 0) {
+ return true;
+ }
+ PLOG(ERROR) << "Could not create " << path;
+ return false;
+ }
+
+ static void ClearDirectory(const std::string& dir) {
+ DIR* c_dir = opendir(dir.c_str());
+ if (c_dir == nullptr) {
+ PLOG(WARNING) << "Unable to open " << dir << " to delete it's contents";
+ return;
+ }
+
+ for (struct dirent* de = readdir(c_dir); de != nullptr; de = readdir(c_dir)) {
+ const char* name = de->d_name;
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
+ continue;
+ }
+ // We only want to delete regular files and symbolic links.
+ std::string file = StringPrintf("%s/%s", dir.c_str(), name);
+ if (de->d_type != DT_REG && de->d_type != DT_LNK) {
+ LOG(WARNING) << "Unexpected file "
+ << file
+ << " of type "
+ << std::hex
+ << de->d_type
+ << " encountered.";
+ } else {
+ // Try to unlink the file.
+ if (unlink(file.c_str()) != 0) {
+ PLOG(ERROR) << "Unable to unlink " << file;
+ }
+ }
+ }
+ CHECK_EQ(0, closedir(c_dir)) << "Unable to close directory.";
+ }
+
+ bool PatchoatBootImage(const std::string& art_path, const char* isa) const {
// This needs to be kept in sync with ART, see art/runtime/gc/space/image_space.cc.
std::vector<std::string> cmd;
@@ -290,12 +445,12 @@
bool Dex2oatBootImage(const std::string& boot_cp,
const std::string& art_path,
const std::string& oat_path,
- const char* isa) {
+ const char* isa) const {
// This needs to be kept in sync with ART, see art/runtime/gc/space/image_space.cc.
std::vector<std::string> cmd;
cmd.push_back("/system/bin/dex2oat");
cmd.push_back(StringPrintf("--image=%s", art_path.c_str()));
- for (const std::string& boot_part : Split(boot_cp, ':')) {
+ for (const std::string& boot_part : Split(boot_cp, ":")) {
cmd.push_back(StringPrintf("--dex-file=%s", boot_part.c_str()));
}
cmd.push_back(StringPrintf("--oat-file=%s", oat_path.c_str()));
@@ -324,7 +479,7 @@
const std::string* extra_opts =
system_properties_.GetProperty("dalvik.vm.image-dex2oat-flags");
if (extra_opts != nullptr) {
- std::vector<std::string> extra_vals = Split(*extra_opts, ' ');
+ std::vector<std::string> extra_vals = Split(*extra_opts, " ");
cmd.insert(cmd.end(), extra_vals.begin(), extra_vals.end());
}
// TODO: Should we lower this? It's usually set close to max, because
@@ -356,18 +511,81 @@
return (strcmp(arg, "!") == 0) ? nullptr : arg;
}
+ bool ShouldSkipPreopt() const {
+ // There's one thing we have to be careful about: we may/will be asked to compile an app
+ // living in the system image. This may be a valid request - if the app wasn't compiled,
+ // e.g., if the system image wasn't large enough to include preopted files. However, the
+ // data we have is from the old system, so the driver (the OTA service) can't actually
+ // know. Thus, we will get requests for apps that have preopted components. To avoid
+ // duplication (we'd generate files that are not used and are *not* cleaned up), do two
+ // simple checks:
+ //
+ // 1) Does the apk_path start with the value of ANDROID_ROOT? (~in the system image)
+ // (For simplicity, assume the value of ANDROID_ROOT does not contain a symlink.)
+ //
+ // 2) If you replace the name in the apk_path with "oat," does the path exist?
+ // (=have a subdirectory for preopted files)
+ //
+ // If the answer to both is yes, skip the dexopt.
+ //
+ // Note: while one may think it's OK to call dexopt and it will fail (because APKs should
+ // be stripped), that's not true for APKs signed outside the build system (so the
+ // jar content must be exactly the same).
+
+ // (This is ugly as it's the only thing where we need to understand the contents
+ // of package_parameters_, but it beats postponing the decision or using the call-
+ // backs to do weird things.)
+ constexpr size_t kApkPathIndex = 0;
+ CHECK_GT(DEXOPT_PARAM_COUNT, kApkPathIndex);
+ CHECK(package_parameters_[kApkPathIndex] != nullptr);
+ if (StartsWith(package_parameters_[kApkPathIndex], android_root_.c_str())) {
+ const char* last_slash = strrchr(package_parameters_[kApkPathIndex], '/');
+ if (last_slash != nullptr) {
+ std::string path(package_parameters_[kApkPathIndex],
+ last_slash - package_parameters_[kApkPathIndex] + 1);
+ CHECK(EndsWith(path, "/"));
+ path = path + "oat";
+ if (access(path.c_str(), F_OK) == 0) {
+ return true;
+ }
+ }
+ }
+
+ // Another issue is unavailability of files in the new system. If the partition
+ // layout changes, otapreopt_chroot may not know about this. Then files from that
+ // partition will not be available and fail to build. This is problematic, as
+ // this tool will wipe the OTA artifact cache and try again (for robustness after
+ // a failed OTA with remaining cache artifacts).
+ if (access(package_parameters_[kApkPathIndex], F_OK) != 0) {
+ LOG(WARNING) << "Skipping preopt of non-existing package "
+ << package_parameters_[kApkPathIndex];
+ return true;
+ }
+
+ return false;
+ }
+
int RunPreopt() {
- int ret = dexopt(package_parameters_[0], // apk_path
- atoi(package_parameters_[1]), // uid
- package_parameters_[2], // pkgname
- package_parameters_[3], // instruction_set
- atoi(package_parameters_[4]), // dexopt_needed
- package_parameters_[5], // oat_dir
- atoi(package_parameters_[6]), // dexopt_flags
- package_parameters_[7], // compiler_filter
- ParseNull(package_parameters_[8]), // volume_uuid
- ParseNull(package_parameters_[9])); // shared_libraries
- return ret;
+ if (ShouldSkipPreopt()) {
+ return 0;
+ }
+
+ int dexopt_result = dexopt(package_parameters_);
+ if (dexopt_result == 0) {
+ return 0;
+ }
+
+ // If the dexopt failed, we may have a stale boot image from a previous OTA run.
+ // Try to delete and retry.
+
+ if (!PrepareBootImage(/* force */ true)) {
+ LOG(ERROR) << "Forced boot image creating failed. Original error return was "
+ << dexopt_result;
+ return dexopt_result;
+ }
+
+ LOG(WARNING) << "Original dexopt failed, re-trying after boot image was regenerated.";
+ return dexopt(package_parameters_);
}
////////////////////////////////////
@@ -375,8 +593,8 @@
////////////////////////////////////
// Wrapper on fork/execv to run a command in a subprocess.
- bool Exec(const std::vector<std::string>& arg_vector, std::string* error_msg) {
- const std::string command_line(Join(arg_vector, ' '));
+ static bool Exec(const std::vector<std::string>& arg_vector, std::string* error_msg) {
+ const std::string command_line = Join(arg_vector, ' ');
CHECK_GE(arg_vector.size(), 1U) << command_line;
@@ -465,9 +683,8 @@
void AddCompilerOptionFromSystemProperty(const char* system_property,
const char* prefix,
bool runtime,
- std::vector<std::string>& out) {
- const std::string* value =
- system_properties_.GetProperty(system_property);
+ std::vector<std::string>& out) const {
+ const std::string* value = system_properties_.GetProperty(system_property);
if (value != nullptr) {
if (runtime) {
out.push_back("--runtime-arg");
@@ -480,11 +697,25 @@
}
}
+ static constexpr const char* kBootClassPathPropertyName = "BOOTCLASSPATH";
+ static constexpr const char* kAndroidRootPathPropertyName = "ANDROID_ROOT";
+ static constexpr const char* kAndroidDataPathPropertyName = "ANDROID_DATA";
+ // The index of the instruction-set string inside the package parameters. Needed for
+ // some special-casing that requires knowledge of the instruction-set.
+ static constexpr size_t kISAIndex = 3;
+
// Stores the system properties read out of the B partition. We need to use these properties
// to compile, instead of the A properties we could get from init/get_property.
SystemProperties system_properties_;
- const char* package_parameters_[10];
+ // Some select properties that are always needed.
+ std::string target_slot_;
+ std::string android_root_;
+ std::string android_data_;
+ std::string boot_classpath_;
+ std::string asec_mountpoint_;
+
+ const char* package_parameters_[DEXOPT_PARAM_COUNT];
// Store environment values we need to set.
std::vector<std::string> environ_;
@@ -497,7 +728,6 @@
////////////////////////
int get_property(const char *key, char *value, const char *default_value) {
- // TODO: Replace with system-properties map.
return gOps.GetProperty(key, value, default_value);
}
@@ -505,7 +735,6 @@
bool calculate_oat_file_path(char path[PKG_PATH_MAX], const char *oat_dir,
const char *apk_path,
const char *instruction_set) {
- // TODO: Insert B directory.
const char *file_name_start;
const char *file_name_end;
@@ -526,8 +755,13 @@
std::string file_name(file_name_start, file_name_len);
// <apk_parent_dir>/oat/<isa>/<file_name>.odex.b
- snprintf(path, PKG_PATH_MAX, "%s/%s/%s.odex.b", oat_dir, instruction_set,
- file_name.c_str());
+ snprintf(path,
+ PKG_PATH_MAX,
+ "%s/%s/%s.odex.%s",
+ oat_dir,
+ instruction_set,
+ file_name.c_str(),
+ gOps.GetTargetSlot().c_str());
return true;
}
@@ -539,11 +773,6 @@
*/
bool calculate_odex_file_path(char path[PKG_PATH_MAX], const char *apk_path,
const char *instruction_set) {
- if (StringPrintf("%soat/%s/odex.b", apk_path, instruction_set).length() + 1 > PKG_PATH_MAX) {
- ALOGE("apk_path '%s' may be too long to form odex file path.\n", apk_path);
- return false;
- }
-
const char *path_end = strrchr(apk_path, '/');
if (path_end == nullptr) {
ALOGE("apk_path '%s' has no '/'s in it?!\n", apk_path);
@@ -559,11 +788,15 @@
}
std::string name_component(name_begin, extension_start - name_begin);
- std::string new_path = StringPrintf("%s/oat/%s/%s.odex.b",
+ std::string new_path = StringPrintf("%s/oat/%s/%s.odex.%s",
path_component.c_str(),
instruction_set,
- name_component.c_str());
- CHECK_LT(new_path.length(), PKG_PATH_MAX);
+ name_component.c_str(),
+ gOps.GetTargetSlot().c_str());
+ if (new_path.length() >= PKG_PATH_MAX) {
+ LOG(ERROR) << "apk_path of " << apk_path << " is too long: " << new_path;
+ return false;
+ }
strcpy(path, new_path.c_str());
return true;
}
@@ -586,7 +819,7 @@
std::replace(from_src.begin(), from_src.end(), '/', '@');
std::string assembled_path = StringPrintf("%s/%s/%s/%s%s",
- OTAPreoptService::kOTADataDirectory,
+ gOps.GetOTADataDirectory().c_str(),
DALVIK_CACHE,
instruction_set,
from_src.c_str(),
@@ -600,27 +833,6 @@
return true;
}
-bool initialize_globals() {
- const char* data_path = getenv("ANDROID_DATA");
- if (data_path == nullptr) {
- ALOGE("Could not find ANDROID_DATA");
- return false;
- }
- return init_globals_from_data_and_root(data_path, kOTARootDirectory);
-}
-
-static bool initialize_directories() {
- // This is different from the normal installd. We only do the base
- // directory, the rest will be created on demand when each app is compiled.
- mode_t old_umask = umask(0);
- LOG(INFO) << "Old umask: " << old_umask;
- if (access(OTAPreoptService::kOTADataDirectory, R_OK) < 0) {
- ALOGE("Could not access %s\n", OTAPreoptService::kOTADataDirectory);
- return false;
- }
- return true;
-}
-
static int log_callback(int type, const char *fmt, ...) {
va_list ap;
int priority;
@@ -648,8 +860,6 @@
setenv("ANDROID_LOG_TAGS", "*:v", 1);
android::base::InitLogging(argv);
- ALOGI("otapreopt firing up\n");
-
if (argc < 2) {
ALOGE("Expecting parameters");
exit(1);
@@ -659,16 +869,6 @@
cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
- if (!initialize_globals()) {
- ALOGE("Could not initialize globals; exiting.\n");
- exit(1);
- }
-
- if (!initialize_directories()) {
- ALOGE("Could not create directories; exiting.\n");
- exit(1);
- }
-
if (selinux_enabled && selinux_status_open(true) < 0) {
ALOGE("Could not open selinux status; exiting.\n");
exit(1);
diff --git a/cmds/installd/otapreopt.rc b/cmds/installd/otapreopt.rc
new file mode 100644
index 0000000..059ae75
--- /dev/null
+++ b/cmds/installd/otapreopt.rc
@@ -0,0 +1,8 @@
+# When /data is available, look for A/B artifacts for the current slot and move them
+# into the dalvik-cache (relabeling them).
+on post-fs-data
+ exec - root -- /system/bin/otapreopt_slot
+ # The dalvik-cache was not moved itself, so as to restrict the rights of otapreopt_slot.
+ # But now the relabeling is annoying as there is no force option available here. So
+ # explicitly list all the ISAs we know.
+ restorecon_recursive /data/dalvik-cache/arm /data/dalvik-cache/arm64 /data/dalvik-cache/mips /data/dalvik-cache/mips64 /data/dalvik-cache/x86 /data/dalvik-cache/x86_64
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index f7f69a9..5ea89e6 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -14,14 +14,20 @@
** limitations under the License.
*/
+#include <fcntl.h>
#include <linux/unistd.h>
#include <sys/mount.h>
#include <sys/wait.h>
+#include <sstream>
+
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/stringprintf.h>
+#include <commands.h>
+#include <otapreopt_utils.h>
+
#ifndef LOG_TAG
#define LOG_TAG "otapreopt"
#endif
@@ -31,7 +37,37 @@
namespace android {
namespace installd {
+static void CloseDescriptor(int fd) {
+ if (fd >= 0) {
+ int result = close(fd);
+ UNUSED(result); // Ignore result. Printing to logcat will open a new descriptor
+ // that we do *not* want.
+ }
+}
+
+static void CloseDescriptor(const char* descriptor_string) {
+ int fd = -1;
+ std::istringstream stream(descriptor_string);
+ stream >> fd;
+ if (!stream.fail()) {
+ CloseDescriptor(fd);
+ }
+}
+
+// Entry for otapreopt_chroot. Expected parameters are:
+// [cmd] [status-fd] [target-slot] "dexopt" [dexopt-params]
+// The file descriptor denoted by status-fd will be closed. The rest of the parameters will
+// be passed on to otapreopt in the chroot.
static int otapreopt_chroot(const int argc, char **arg) {
+ // Close all file descriptors. They are coming from the caller, we do not want to pass them
+ // on across our fork/exec into a different domain.
+ // 1) Default descriptors.
+ CloseDescriptor(STDIN_FILENO);
+ CloseDescriptor(STDOUT_FILENO);
+ CloseDescriptor(STDERR_FILENO);
+ // 2) The status channel.
+ CloseDescriptor(arg[1]);
+
// We need to run the otapreopt tool from the postinstall partition. As such, set up a
// mount namespace and change root.
@@ -59,6 +95,28 @@
}
}
+ // Try to mount the vendor partition. update_engine doesn't do this for us, but we
+ // want it for vendor APKs.
+ // Notes:
+ // 1) We pretty much guess a name here and hope to find the partition by name.
+ // It is just as complicated and brittle to scan /proc/mounts. But this requires
+ // validating the target-slot so as not to try to mount some totally random path.
+ // 2) We're in a mount namespace here, so when we die, this will be cleaned up.
+ // 3) Ignore errors. Printing anything at this stage will open a file descriptor
+ // for logging.
+ if (!ValidateTargetSlotSuffix(arg[2])) {
+ LOG(ERROR) << "Target slot suffix not legal: " << arg[2];
+ exit(207);
+ }
+ std::string vendor_partition = StringPrintf("/dev/block/bootdevice/by-name/vendor%s",
+ arg[2]);
+ int vendor_result = mount(vendor_partition.c_str(),
+ "/postinstall/vendor",
+ "ext4",
+ MS_RDONLY,
+ /* data */ nullptr);
+ UNUSED(vendor_result);
+
// Chdir into /postinstall.
if (chdir("/postinstall") != 0) {
PLOG(ERROR) << "Unable to chdir into /postinstall.";
@@ -78,13 +136,42 @@
// Now go on and run otapreopt.
- const char* argv[1 + 9 + 1];
- CHECK_EQ(argc, 10);
- argv[0] = "/system/bin/otapreopt";
- for (size_t i = 1; i <= 9; ++i) {
- argv[i] = arg[i];
+ // Incoming: cmd + status-fd + target-slot + "dexopt" + dexopt-params + null
+ // Outgoing: cmd + target-slot + "dexopt" + dexopt-params + null
+ constexpr size_t kInArguments = 1 // Binary name.
+ + 1 // status file descriptor.
+ + 1 // target-slot.
+ + 1 // "dexopt."
+ + DEXOPT_PARAM_COUNT // dexopt parameters.
+ + 1; // null termination.
+ constexpr size_t kOutArguments = 1 // Binary name.
+ + 1 // target-slot.
+ + 1 // "dexopt."
+ + DEXOPT_PARAM_COUNT // dexopt parameters.
+ + 1; // null termination.
+ const char* argv[kOutArguments];
+ if (static_cast<size_t>(argc) != kInArguments - 1 /* null termination */) {
+ LOG(ERROR) << "Unexpected argument size "
+ << argc
+ << " vs "
+ << (kInArguments - 1);
+ for (size_t i = 0; i < static_cast<size_t>(argc); ++i) {
+ if (arg[i] == nullptr) {
+ LOG(ERROR) << "(null)";
+ } else {
+ LOG(ERROR) << "\"" << arg[i] << "\"";
+ }
+ }
+ exit(206);
}
- argv[10] = nullptr;
+ argv[0] = "/system/bin/otapreopt";
+
+ // The first parameter is the status file descriptor, skip.
+
+ for (size_t i = 1; i <= kOutArguments - 2 /* cmd + null */; ++i) {
+ argv[i] = arg[i + 1];
+ }
+ argv[kOutArguments - 1] = nullptr;
execv(argv[0], (char * const *)argv);
PLOG(ERROR) << "execv(OTAPREOPT) failed.";
diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh
index a31734a..f950276 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -18,24 +18,62 @@
# This script will run as a postinstall step to drive otapreopt.
+TARGET_SLOT="$1"
+STATUS_FD="$2"
+
# Maximum number of packages/steps.
MAXIMUM_PACKAGES=1000
-PREPARE=$(cmd otadexopt prepare)
-if [ "$PREPARE" != "Success" ] ; then
- echo "Failed to prepare."
+# First ensure the system is booted. This is to work around issues when cmd would
+# infinitely loop trying to get a service manager (which will never come up in that
+# mode). b/30797145
+BOOT_PROPERTY_NAME="dev.bootcomplete"
+
+BOOT_COMPLETE=$(getprop $BOOT_PROPERTY_NAME)
+if [ "$BOOT_COMPLETE" != "1" ] ; then
+ echo "Error: boot-complete not detected."
+ # We must return 0 to not block sideload.
+ exit 0
+fi
+
+
+# Compute target slot suffix.
+# TODO: Once bootctl is not restricted, we should query from there. Or get this from
+# update_engine as a parameter.
+if [ "$TARGET_SLOT" = "0" ] ; then
+ TARGET_SLOT_SUFFIX="_a"
+elif [ "$TARGET_SLOT" = "1" ] ; then
+ TARGET_SLOT_SUFFIX="_b"
+else
+ echo "Unknown target slot $TARGET_SLOT"
exit 1
fi
+
+PREPARE=$(cmd otadexopt prepare)
+# Note: Ignore preparation failures. Step and done will fail and exit this.
+# This is necessary to support suspends - the OTA service will keep
+# the state around for us.
+
+PROGRESS=$(cmd otadexopt progress)
+print -u${STATUS_FD} "global_progress $PROGRESS"
+
i=0
while ((i<MAXIMUM_PACKAGES)) ; do
- cmd otadexopt step
+ DEXOPT_PARAMS=$(cmd otadexopt next)
+
+ /system/bin/otapreopt_chroot $STATUS_FD $TARGET_SLOT_SUFFIX $DEXOPT_PARAMS >&- 2>&-
+
+ PROGRESS=$(cmd otadexopt progress)
+ print -u${STATUS_FD} "global_progress $PROGRESS"
+
DONE=$(cmd otadexopt done)
- if [ "$DONE" = "OTA complete." ] ; then
- break
+ if [ "$DONE" = "OTA incomplete." ] ; then
+ sleep 1
+ i=$((i+1))
+ continue
fi
- sleep 1
- i=$((i+1))
+ break
done
DONE=$(cmd otadexopt done)
@@ -45,6 +83,7 @@
echo "Complete or error."
fi
+print -u${STATUS_FD} "global_progress 1.0"
cmd otadexopt cleanup
exit 0
diff --git a/cmds/installd/otapreopt_slot.sh b/cmds/installd/otapreopt_slot.sh
new file mode 100644
index 0000000..b5786e9
--- /dev/null
+++ b/cmds/installd/otapreopt_slot.sh
@@ -0,0 +1,39 @@
+#!/system/bin/sh
+
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This script will move artifacts for the currently active slot.
+
+SLOT_SUFFIX=$(getprop ro.boot.slot_suffix)
+if test -n "$SLOT_SUFFIX" ; then
+ if test -d /data/ota/$SLOT_SUFFIX/dalvik-cache ; then
+ log -p i -t otapreopt_slot "Moving A/B artifacts for slot ${SLOT_SUFFIX}."
+ OLD_SIZE=$(du -h -s /data/dalvik-cache)
+ rm -rf /data/dalvik-cache/*
+ NEW_SIZE=$(du -h -s /data/ota/$SLOT_SUFFIX/dalvik-cache)
+ mv /data/ota/$SLOT_SUFFIX/dalvik-cache/* /data/dalvik-cache/
+ rmdir /data/ota/$SLOT_SUFFIX/dalvik-cache
+ rmdir /data/ota/$SLOT_SUFFIX
+ log -p i -t otapreopt_slot "Moved ${NEW_SIZE} over ${OLD_SIZE}"
+ else
+ log -p i -t otapreopt_slot "No A/B artifacts found for slot ${SLOT_SUFFIX}."
+ fi
+ exit 0
+else
+ log -p w -t otapreopt_slot "Slot property empty."
+ exit 1
+fi
diff --git a/cmds/installd/otapreopt_utils.h b/cmds/installd/otapreopt_utils.h
new file mode 100644
index 0000000..436e554
--- /dev/null
+++ b/cmds/installd/otapreopt_utils.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OTAPREOPT_UTILS_H_
+#define OTAPREOPT_UTILS_H_
+
+#include <regex>
+
+namespace android {
+namespace installd {
+
+static inline bool ValidateTargetSlotSuffix(const std::string& input) {
+ std::regex slot_suffix_regex("[a-zA-Z0-9_]+");
+ std::smatch slot_suffix_match;
+ return std::regex_match(input, slot_suffix_match, slot_suffix_regex);
+}
+
+} // namespace installd
+} // namespace android
+
+#endif // OTAPREOPT_UTILS_H_
diff --git a/cmds/installd/string_helpers.h b/cmds/installd/string_helpers.h
deleted file mode 100644
index e8fcdef..0000000
--- a/cmds/installd/string_helpers.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ART_OTAPREOPT_STRING_HELPERS_H_
-#define ART_OTAPREOPT_STRING_HELPERS_H_
-
-#include <sstream>
-#include <string>
-
-#include <android-base/macros.h>
-
-namespace android {
-namespace installd {
-
-static inline bool StringStartsWith(const std::string& target,
- const char* prefix) {
- return target.compare(0, strlen(prefix), prefix) == 0;
-}
-
-// Split the input according to the separator character. Doesn't honor quotation.
-static inline std::vector<std::string> Split(const std::string& in, const char separator) {
- if (in.empty()) {
- return std::vector<std::string>();
- }
-
- std::vector<std::string> ret;
- std::stringstream strstr(in);
- std::string token;
-
- while (std::getline(strstr, token, separator)) {
- ret.push_back(token);
- }
-
- return ret;
-}
-
-template <typename StringT>
-static inline std::string Join(const std::vector<StringT>& strings, char separator) {
- if (strings.empty()) {
- return "";
- }
-
- std::string result(strings[0]);
- for (size_t i = 1; i < strings.size(); ++i) {
- result += separator;
- result += strings[i];
- }
- return result;
-}
-
-} // namespace installd
-} // namespace android
-
-#endif // ART_OTAPREOPT_STRING_HELPERS_H_