diff options
author | 2024-05-03 13:07:59 +0100 | |
---|---|---|
committer | 2024-05-24 18:21:21 +0000 | |
commit | 31c355a3ecf77be13ad904722631a29e712fcd90 (patch) | |
tree | 79eade11a3aaefdfecfd872539bfdc7ee40852e6 | |
parent | 824b7e58b01e063a66dfcfd45cf55dc0804071f3 (diff) |
Add system requirement check.
It checks that the old system is at most one dessert release older than
the new one.
Bug: 311377497
Test: m test-art-host-gtest-art_artd_tests
Change-Id: Idd4e15d3ee03b3b1abceb707e592b88cb66fd757
-rw-r--r-- | artd/artd.cc | 69 | ||||
-rw-r--r-- | artd/artd.h | 20 | ||||
-rw-r--r-- | artd/artd_test.cc | 43 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/IArtd.aidl | 11 | ||||
-rw-r--r-- | dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl | 10 | ||||
-rw-r--r-- | dexopt_chroot_setup/dexopt_chroot_setup.cc | 42 | ||||
-rw-r--r-- | dexopt_chroot_setup/dexopt_chroot_setup.h | 4 | ||||
-rw-r--r-- | dexopt_chroot_setup/dexopt_chroot_setup_test.cc | 7 | ||||
-rw-r--r-- | libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java | 22 |
9 files changed, 224 insertions, 4 deletions
diff --git a/artd/artd.cc b/artd/artd.cc index 2619f65cfc..85c21b3bc6 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -51,6 +51,7 @@ #include "android-base/errors.h" #include "android-base/file.h" #include "android-base/logging.h" +#include "android-base/parseint.h" #include "android-base/result.h" #include "android-base/scopeguard.h" #include "android-base/strings.h" @@ -113,10 +114,13 @@ using ::android::base::ErrnoError; using ::android::base::Error; using ::android::base::Join; using ::android::base::make_scope_guard; +using ::android::base::ParseInt; using ::android::base::ReadFileToString; using ::android::base::Result; using ::android::base::Split; +using ::android::base::StartsWith; using ::android::base::Tokenize; +using ::android::base::Trim; using ::android::base::WriteStringToFd; using ::android::base::WriteStringToFile; using ::android::fs_mgr::FstabEntry; @@ -1428,6 +1432,39 @@ ScopedAStatus Artd::commitPreRebootStagedFiles(const std::vector<ArtifactsPath>& return ScopedAStatus::ok(); } +ScopedAStatus Artd::checkPreRebootSystemRequirements(const std::string& in_chrootDir, + bool* _aidl_return) { + RETURN_FATAL_IF_PRE_REBOOT(options_); + BuildSystemProperties new_props = + OR_RETURN_NON_FATAL(BuildSystemProperties::Create(in_chrootDir + "/system/build.prop")); + std::string old_release_str = props_->GetOrEmpty("ro.build.version.release"); + int old_release; + if (!ParseInt(old_release_str, &old_release)) { + return NonFatal( + ART_FORMAT("Failed to read or parse old release number, got '{}'", old_release_str)); + } + std::string new_release_str = new_props.GetOrEmpty("ro.build.version.release"); + int new_release; + if (!ParseInt(new_release_str, &new_release)) { + return NonFatal( + ART_FORMAT("Failed to read or parse new release number, got '{}'", new_release_str)); + } + if (new_release - old_release >= 2) { + // When the release version difference is large, there is no particular technical reason why we + // can't run Pre-reboot Dexopt, but we cannot test and support those cases. + LOG(WARNING) << ART_FORMAT( + "Pre-reboot Dexopt not supported due to large difference in release versions (old_release: " + "{}, new_release: {})", + old_release, + new_release); + *_aidl_return = false; + return ScopedAStatus::ok(); + } + + *_aidl_return = true; + return ScopedAStatus::ok(); +} + Result<void> Artd::Start() { OR_RETURN(SetLogVerbosity()); MemMap::Init(); @@ -1861,5 +1898,37 @@ ScopedAStatus Artd::validateClassLoaderContext(const std::string& in_dexFile, return ScopedAStatus::ok(); } +Result<BuildSystemProperties> BuildSystemProperties::Create(const std::string& filename) { + std::string content; + if (!ReadFileToString(filename, &content)) { + return ErrnoErrorf("Failed to read '{}'", filename); + } + std::unordered_map<std::string, std::string> system_properties; + for (const std::string& raw_line : Split(content, "\n")) { + std::string line = Trim(raw_line); + if (line.empty() || StartsWith(line, '#')) { + continue; + } + size_t pos = line.find('='); + if (pos == std::string::npos || pos == 0 || (pos == 1 && line[1] == '?')) { + return Errorf("Malformed system property line '{}' in file '{}'", line, filename); + } + if (line[pos - 1] == '?') { + std::string key = line.substr(/*pos=*/0, /*n=*/pos - 1); + if (system_properties.find(key) == system_properties.end()) { + system_properties[key] = line.substr(pos + 1); + } + } else { + system_properties[line.substr(/*pos=*/0, /*n=*/pos)] = line.substr(pos + 1); + } + } + return BuildSystemProperties(std::move(system_properties)); +} + +std::string BuildSystemProperties::GetProperty(const std::string& key) const { + auto it = system_properties_.find(key); + return it != system_properties_.end() ? it->second : ""; +} + } // namespace artd } // namespace art diff --git a/artd/artd.h b/artd/artd.h index d7c9effc6b..13086657c6 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -228,6 +228,9 @@ class Artd : public aidl::com::android::server::art::BnArtd { in_profiles, bool* _aidl_return) override; + ndk::ScopedAStatus checkPreRebootSystemRequirements(const std::string& in_chrootDir, + bool* _aidl_return) override; + ndk::ScopedAStatus preRebootInit() override; ndk::ScopedAStatus validateDexPath(const std::string& in_dexFile, @@ -329,6 +332,23 @@ class Artd : public aidl::com::android::server::art::BnArtd { const std::optional<std::string> init_environ_rc_path_; }; +// A class for getting system properties from a `build.prop` file. +class BuildSystemProperties : public tools::SystemProperties { + public: + // Creates an instance and loads system properties from the `build.prop` file specified at the + // given path. + static android::base::Result<BuildSystemProperties> Create(const std::string& filename); + + protected: + std::string GetProperty(const std::string& key) const override; + + private: + explicit BuildSystemProperties(std::unordered_map<std::string, std::string>&& system_properties) + : system_properties_(std::move(system_properties)) {} + + const std::unordered_map<std::string, std::string> system_properties_; +}; + } // namespace artd } // namespace art diff --git a/artd/artd_test.cc b/artd/artd_test.cc index 9c58dbd366..26b00dd831 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -2581,6 +2581,49 @@ TEST_F(ArtdTest, commitPreRebootStagedFilesNoNewFile) { EXPECT_FALSE(aidl_return); } +TEST_F(ArtdTest, checkPreRebootSystemRequirements) { + EXPECT_CALL(*mock_props_, GetProperty("ro.build.version.release")).WillRepeatedly(Return("15")); + std::string chroot_dir = scratch_path_ + "/chroot"; + bool aidl_return; + + constexpr const char* kTemplate = R"( + # Comment. + unrelated.system.property=abc + + ro.build.version.release={} + )"; + + CreateFile(chroot_dir + "/system/build.prop", ART_FORMAT(kTemplate, 15)); + ASSERT_STATUS_OK(artd_->checkPreRebootSystemRequirements(chroot_dir, &aidl_return)); + EXPECT_TRUE(aidl_return); + + CreateFile(chroot_dir + "/system/build.prop", ART_FORMAT(kTemplate, 16)); + ASSERT_STATUS_OK(artd_->checkPreRebootSystemRequirements(chroot_dir, &aidl_return)); + EXPECT_TRUE(aidl_return); + + CreateFile(chroot_dir + "/system/build.prop", ART_FORMAT(kTemplate, 17)); + ASSERT_STATUS_OK(artd_->checkPreRebootSystemRequirements(chroot_dir, &aidl_return)); + EXPECT_FALSE(aidl_return); +} + +TEST_F(ArtdTest, BuildSystemProperties) { + constexpr const char* kContent = R"( + # Comment. + property.foo=123 + property.foo?=456 + property.bar?=000 + property.bar=789 + property.baz?=111 + )"; + + CreateFile(scratch_path_ + "/build.prop", kContent); + BuildSystemProperties props = + OR_FAIL(BuildSystemProperties::Create(scratch_path_ + "/build.prop")); + EXPECT_EQ(props.GetOrEmpty("property.foo"), "123"); + EXPECT_EQ(props.GetOrEmpty("property.bar"), "789"); + EXPECT_EQ(props.GetOrEmpty("property.baz"), "111"); +} + class ArtdPreRebootTest : public ArtdTest { protected: void SetUp() override { diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index d49e1aaf59..da4425bc8f 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -270,6 +270,17 @@ interface IArtd { in List<com.android.server.art.ArtifactsPath> artifacts, in List<com.android.server.art.ProfilePath.WritableProfilePath> profiles); + /** + * Returns whether the old system and the new system meet the requirements to run Pre-reboot + * Dexopt. This method can only be called with a chroot dir set up by + * {@link IDexoptChrootSetup#setUp}. + * + * Not supported in Pre-reboot Dexopt mode. + * + * Throws fatal and non-fatal errors. + */ + boolean checkPreRebootSystemRequirements(@utf8InCpp String chrootDir); + // The methods below are only for Pre-reboot Dexopt and only supported in Pre-reboot Dexopt // mode. diff --git a/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl b/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl index 0b50d22778..b0a5e31488 100644 --- a/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl +++ b/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl @@ -22,13 +22,21 @@ interface IDexoptChrootSetup { const @utf8InCpp String CHROOT_DIR = PRE_REBOOT_DEXOPT_DIR + "/chroot"; /** - * Sets up the chroot environment. + * Sets up the chroot environment. All files in chroot, except apexes and the linker config, are + * accessible after this method is called. * * @param otaSlot The slot that contains the OTA update, "_a" or "_b", or null for a Mainline * update. */ void setUp(@nullable @utf8InCpp String otaSlot); + /** + * Initializes the chroot environment. Can only be called after {@link #setUp}. Apexes and + * the linker config in chroot are accessible, and binaries are executable in chroot, after + * this method is called. + */ + void init(); + /** Tears down the chroot environment. */ void tearDown(); } diff --git a/dexopt_chroot_setup/dexopt_chroot_setup.cc b/dexopt_chroot_setup/dexopt_chroot_setup.cc index 35343c5b3c..6f01851e3d 100644 --- a/dexopt_chroot_setup/dexopt_chroot_setup.cc +++ b/dexopt_chroot_setup/dexopt_chroot_setup.cc @@ -68,6 +68,7 @@ using ::android::base::SetProperty; using ::android::base::Split; using ::android::base::Tokenize; using ::android::base::WaitForProperty; +using ::android::base::WriteStringToFile; using ::android::fs_mgr::FstabEntry; using ::art::tools::CmdlineBuilder; using ::art::tools::Fatal; @@ -79,6 +80,8 @@ using ::ndk::ScopedAStatus; constexpr const char* kServiceName = "dexopt_chroot_setup"; const NoDestructor<std::string> kBindMountTmpDir( std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + "/mount_tmp"); +const NoDestructor<std::string> kOtaSlotFile(std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + + "/ota_slot"); constexpr mode_t kChrootDefaultMode = 0755; constexpr std::chrono::milliseconds kSnapshotCtlTimeout = std::chrono::seconds(60); @@ -303,6 +306,20 @@ Result<void> MountTmpfs(const std::string& target, std::string_view se_context) return {}; } +Result<std::optional<std::string>> LoadOtaSlotFile() { + std::string content; + if (!ReadFileToString(*kOtaSlotFile, &content)) { + return ErrnoErrorf("Failed to read '{}'", *kOtaSlotFile); + } + if (content == "_a" || content == "_b") { + return content; + } + if (content.empty()) { + return std::nullopt; + } + return Errorf("Invalid content of '{}': '{}'", *kOtaSlotFile, content); +} + } // namespace ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaSlot) { @@ -318,6 +335,16 @@ ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaS return ScopedAStatus::ok(); } +ScopedAStatus DexoptChrootSetup::init() { + if (!mu_.try_lock()) { + return Fatal("Unexpected concurrent calls"); + } + std::lock_guard<std::mutex> lock(mu_, std::adopt_lock); + + OR_RETURN_NON_FATAL(InitChroot()); + return ScopedAStatus::ok(); +} + ScopedAStatus DexoptChrootSetup::tearDown() { if (!mu_.try_lock()) { return Fatal("Unexpected concurrent calls"); @@ -406,6 +433,16 @@ Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ot OR_RETURN(BindMountRecursive(src, PathInChroot(src))); } + if (!WriteStringToFile(ota_slot.value_or(""), *kOtaSlotFile)) { + return ErrnoErrorf("Failed to write '{}'", *kOtaSlotFile); + } + + return {}; +} + +Result<void> DexoptChrootSetup::InitChroot() const { + std::optional<std::string> ota_slot = OR_RETURN(LoadOtaSlotFile()); + // Generate empty linker config to suppress warnings. if (!android::base::WriteStringToFile("", PathInChroot("/linkerconfig/ld.config.txt"))) { PLOG(WARNING) << "Failed to generate empty linker config to suppress warnings"; @@ -482,6 +519,11 @@ Result<void> DexoptChrootSetup::TearDownChroot() const { return Errorf("Failed to remove dir '{}': {}", *kBindMountTmpDir, ec.message()); } + std::filesystem::remove(*kOtaSlotFile, ec); + if (ec) { + return Errorf("Failed to remove file '{}': {}", *kOtaSlotFile, ec.message()); + } + if (!SetProperty("sys.snapshotctl.unmap", "requested")) { return Errorf("Failed to request snapshotctl unmap"); } diff --git a/dexopt_chroot_setup/dexopt_chroot_setup.h b/dexopt_chroot_setup/dexopt_chroot_setup.h index baceb1a480..466471b5ed 100644 --- a/dexopt_chroot_setup/dexopt_chroot_setup.h +++ b/dexopt_chroot_setup/dexopt_chroot_setup.h @@ -32,6 +32,8 @@ class DexoptChrootSetup : public aidl::com::android::server::art::BnDexoptChroot public: ndk::ScopedAStatus setUp(const std::optional<std::string>& in_otaSlot) override; + ndk::ScopedAStatus init() override; + ndk::ScopedAStatus tearDown() override; android::base::Result<void> Start(); @@ -40,6 +42,8 @@ class DexoptChrootSetup : public aidl::com::android::server::art::BnDexoptChroot android::base::Result<void> SetUpChroot(const std::optional<std::string>& ota_slot) const REQUIRES(mu_); + android::base::Result<void> InitChroot() const REQUIRES(mu_); + android::base::Result<void> TearDownChroot() const REQUIRES(mu_); std::mutex mu_; diff --git a/dexopt_chroot_setup/dexopt_chroot_setup_test.cc b/dexopt_chroot_setup/dexopt_chroot_setup_test.cc index bfa38e0aa0..2b90bb941f 100644 --- a/dexopt_chroot_setup/dexopt_chroot_setup_test.cc +++ b/dexopt_chroot_setup/dexopt_chroot_setup_test.cc @@ -103,6 +103,7 @@ TEST_F(DexoptChrootSetupTest, Run) { // We only test the Mainline update case here. There isn't an easy way to test the OTA update case // in such a unit test. The OTA update case is assumed to be covered by the E2E test. ASSERT_STATUS_OK(dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt)); + ASSERT_STATUS_OK(dexopt_chroot_setup_->init()); // Some important dirs that should be the same as outside. std::vector<const char*> same_dirs = { @@ -173,6 +174,7 @@ TEST_F(DexoptChrootSetupTest, Run) { // caller (typically system_server) called `setUp` and crashed later, and a new instance called // `setUp` again. ASSERT_STATUS_OK(dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt)); + ASSERT_STATUS_OK(dexopt_chroot_setup_->init()); ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown()); @@ -180,6 +182,11 @@ TEST_F(DexoptChrootSetupTest, Run) { // Check that `tearDown` can be repetitively called too. ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown()); + + // Check that `setUp` can be followed directly by a `tearDown`. + ASSERT_STATUS_OK(dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt)); + ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown()); + EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR)); } } // namespace diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java index c20a1ce8a8..d762732e5e 100644 --- a/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java +++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java @@ -35,8 +35,10 @@ import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; import com.android.server.art.ArtManagerLocal; import com.android.server.art.ArtModuleServiceInitializer; +import com.android.server.art.ArtdRefCache; import com.android.server.art.AsLog; import com.android.server.art.GlobalInjector; +import com.android.server.art.IArtd; import com.android.server.art.IDexoptChrootSetup; import com.android.server.art.PreRebootDexoptJob; import com.android.server.art.Utils; @@ -75,7 +77,8 @@ public class PreRebootDriver { } /** - * Runs Pre-reboot Dexopt and returns whether it is successful. + * Runs Pre-reboot Dexopt and returns whether it is successful. Returns false if Pre-reboot + * dexopt failed, the system requirement check failed, or system requirements are not met. * * @param otaSlot The slot that contains the OTA update, "_a" or "_b", or null for a Mainline * update. @@ -84,7 +87,10 @@ public class PreRebootDriver { @NonNull PreRebootStatsReporter statsReporter) { try { statsReporter.recordJobStarted(); - setUp(otaSlot); + if (!setUp(otaSlot)) { + statsReporter.recordJobEnded(Status.STATUS_FAILED); + return false; + } runFromChroot(cancellationSignal); return true; } catch (RemoteException e) { @@ -102,8 +108,13 @@ public class PreRebootDriver { return false; } - private void setUp(@Nullable String otaSlot) throws RemoteException { + private boolean setUp(@Nullable String otaSlot) throws RemoteException { mInjector.getDexoptChrootSetup().setUp(otaSlot); + if (!mInjector.getArtd().checkPreRebootSystemRequirements(CHROOT_DIR)) { + return false; + } + mInjector.getDexoptChrootSetup().init(); + return true; } private void tearDown() { @@ -189,5 +200,10 @@ public class PreRebootDriver { public IDexoptChrootSetup getDexoptChrootSetup() { return GlobalInjector.getInstance().getDexoptChrootSetup(); } + + @NonNull + public IArtd getArtd() { + return ArtdRefCache.getInstance().getArtd(); + } } } |