diff options
-rw-r--r-- | cmds/installd/Android.mk | 6 | ||||
-rw-r--r-- | cmds/installd/commands.cpp | 454 | ||||
-rw-r--r-- | cmds/installd/commands.h | 20 | ||||
-rw-r--r-- | cmds/installd/globals.cpp | 20 | ||||
-rw-r--r-- | cmds/installd/globals.h | 5 | ||||
-rw-r--r-- | cmds/installd/installd.cpp | 45 | ||||
-rw-r--r-- | cmds/installd/installd_constants.h | 37 | ||||
-rw-r--r-- | cmds/installd/installd_deps.h | 3 | ||||
-rw-r--r-- | cmds/installd/otapreopt.cpp | 434 | ||||
-rw-r--r-- | cmds/installd/otapreopt.rc | 8 | ||||
-rw-r--r-- | cmds/installd/otapreopt_chroot.cpp | 97 | ||||
-rw-r--r-- | cmds/installd/otapreopt_script.sh | 55 | ||||
-rw-r--r-- | cmds/installd/otapreopt_slot.sh | 39 | ||||
-rw-r--r-- | cmds/installd/otapreopt_utils.h | 34 | ||||
-rw-r--r-- | cmds/installd/string_helpers.h | 67 |
15 files changed, 936 insertions, 388 deletions
diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk index 6cd0689593..d29e5bac32 100644 --- a/cmds/installd/Android.mk +++ b/cmds/installd/Android.mk @@ -45,8 +45,8 @@ LOCAL_MODULE_TAGS := optional 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 e6680eda49..95451bf274 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 @@ namespace installd { 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 @@ static void run_dex2oat(int zip_fd, int oat_fd, int image_fd, const char* input_ 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 @@ static void run_dex2oat(int zip_fd, int oat_fd, int image_fd, const char* input_ + (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 @@ static void run_dex2oat(int zip_fd, int oat_fd, int image_fd, const char* input_ 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 @@ bool dump_profile(uid_t uid, const char* pkgname, const char* code_path_string) 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 @@ static bool add_extension_to_file_name(char* file_name, const char* extension) { 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 @@ bool merge_profiles(uid_t uid, const char *pkgname) { 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 @@ int dexopt(const char* apk_path, uid_t uid, const char* pkgname, const char* ins 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 @@ int dexopt(const char* apk_path, uid_t uid, const char* pkgname, const char* ins 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 @@ int dexopt(const char* apk_path, uid_t uid, const char* pkgname, const char* ins 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 @@ int dexopt(const char* apk_path, uid_t uid, const char* pkgname, const char* ins } // 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 @@ static bool unlink_and_rename(const char* from, const char* to) { 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 @@ int move_ab(const char* apk_path, const char* instruction_set, const char* oat_d 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; - } - 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; - } - } + bool oat_success = move_ab_path(b_path, a_path); + bool success; - // 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; + 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); } - return -1; + success = art_success || kIgnoreAppImageFailure; + } else { + // Cleanup: delete B image, ignore errors. + unlink(b_image_path.c_str()); + + success = false; } - return 0; + return success ? 0 : -1; } } // namespace installd diff --git a/cmds/installd/commands.h b/cmds/installd/commands.h index 7a42c5caf7..e990f1b695 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 merge_profiles(uid_t uid, const char *pkgname); 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 6a67e29396..93e1ce54af 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 @@ bool init_globals_from_data_and_root(const char* data, const char* root) { // 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 @@ bool init_globals_from_data_and_root(const char* data, const char* root) { } // 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 3e523460ad..c90beec49c 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 d5c3ccda4e..531c6df200 100644 --- a/cmds/installd/installd.cpp +++ b/cmds/installd/installd.cpp @@ -219,7 +219,8 @@ static int do_destroy_app_data(char **arg, char reply[REPLY_MAX] ATTRIBUTE_UNUSE // 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 @@ static int do_ota_dexopt(char **arg, char reply[REPLY_MAX] ATTRIBUTE_UNUSED) { 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_ota_dexopt(char **arg, char reply[REPLY_MAX] ATTRIBUTE_UNUSED) { } } +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]) { + const char* args[DEXOPT_PARAM_COUNT]; + for (size_t i = 0; i < DEXOPT_PARAM_COUNT; ++i) { + CHECK(arg[i] != nullptr); + args[i] = arg[i]; + } + int dexopt_flags = atoi(arg[6]); + DexoptFn dexopt_fn; if ((dexopt_flags & DEXOPT_OTA) != 0) { - return do_ota_dexopt(arg, reply); + dexopt_fn = do_ota_dexopt; + } else { + dexopt_fn = do_regular_dexopt; } - 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 + return dexopt_fn(args, reply); } static int do_merge_profiles(char **arg, char reply[REPLY_MAX]) @@ -546,7 +557,7 @@ done: 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 dfde7274bc..b0bcce911a 100644 --- a/cmds/installd/installd_constants.h +++ b/cmds/installd/installd_constants.h @@ -26,50 +26,13 @@ namespace installd { 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 5ff46e694f..509317803e 100644 --- a/cmds/installd/installd_deps.h +++ b/cmds/installd/installd_deps.h @@ -57,9 +57,6 @@ extern bool create_cache_path(char path[PKG_PATH_MAX], 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 8b9c931ea2..7e02b6bcfc 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 @@ static constexpr T RoundUp(T x, typename std::remove_reference<T>::type n) { 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 @@ class OTAPreoptService { // // 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 @@ class OTAPreoptService { 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 @@ class OTAPreoptService { 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 @@ private: 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; } - bool ReadPackage(int argc ATTRIBUTE_UNUSED, char** argv) { + 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 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 @@ private: } 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 @@ private: // 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: //////////////////////////////////// // 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 @@ private: 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 @@ private: } } + 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 @@ OTAPreoptService gOps; //////////////////////// 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 @@ int get_property(const char *key, char *value, const char *default_value) { 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 @@ bool calculate_oat_file_path(char path[PKG_PATH_MAX], const char *oat_dir, 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_oat_file_path(char path[PKG_PATH_MAX], const char *oat_dir, */ 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 @@ bool calculate_odex_file_path(char path[PKG_PATH_MAX], const char *apk_path, } 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 @@ bool create_cache_path(char path[PKG_PATH_MAX], 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 @@ bool create_cache_path(char path[PKG_PATH_MAX], 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 @@ static int otapreopt_main(const int argc, char *argv[]) { 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 @@ static int otapreopt_main(const int argc, char *argv[]) { 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 0000000000..059ae752e7 --- /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 f7f69a90f4..5ea89e6842 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 @@ using android::base::StringPrintf; 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 @@ static int otapreopt_chroot(const int argc, char **arg) { } } + // 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 @@ static int otapreopt_chroot(const int argc, char **arg) { // Now go on and run otapreopt. - const char* argv[1 + 9 + 1]; - CHECK_EQ(argc, 10); + // 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[0] = "/system/bin/otapreopt"; - for (size_t i = 1; i <= 9; ++i) { - argv[i] = arg[i]; + + // 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[10] = nullptr; + 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 a31734a651..f950276090 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 @@ else 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 0000000000..b5786e9a28 --- /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 0000000000..436e554944 --- /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 e8fcdef9cf..0000000000 --- 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_ |