summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--artd/artd.cc69
-rw-r--r--artd/artd.h20
-rw-r--r--artd/artd_test.cc43
-rw-r--r--artd/binder/com/android/server/art/IArtd.aidl11
-rw-r--r--dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl10
-rw-r--r--dexopt_chroot_setup/dexopt_chroot_setup.cc42
-rw-r--r--dexopt_chroot_setup/dexopt_chroot_setup.h4
-rw-r--r--dexopt_chroot_setup/dexopt_chroot_setup_test.cc7
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java22
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();
+ }
}
}