Merge "Change the hashtree hash algorithm to sha256"
diff --git a/apexd/Android.bp b/apexd/Android.bp
index a6afb4a..fb60191 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -219,11 +219,6 @@
export_header_lib_headers: [
"libutils_headers",
],
- product_variables: {
- debuggable: {
- cflags: ["-DDEBUG_ALLOW_BUNDLED_KEY"],
- },
- },
}
genrule {
@@ -310,6 +305,7 @@
],
shared_libs: [
"libbinder",
+ "libfs_mgr",
"libutils",
],
test_suites: ["device-tests"],
diff --git a/apexd/AndroidTest.xml b/apexd/AndroidTest.xml
index aea1d24..6f65f4e 100644
--- a/apexd/AndroidTest.xml
+++ b/apexd/AndroidTest.xml
@@ -28,6 +28,24 @@
on higher levels (e.g., PackageInstaller). -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="remount-system" value="true" />
+ <option name="push" value="apex.apexd_test.apex->/system_ext/apex/apex.apexd_test.apex" />
+ <option name="push" value="apex.apexd_test_different_app.apex->/system_ext/apex/apex.apexd_test_different_app.apex" />
+ <option name="push" value="apex.apexd_test_postinstall.apex->/system_ext/apex/apex.apexd_test_postinstall.apex" />
+ <option name="push" value="apex.apexd_test_preinstall.apex->/system_ext/apex/apex.apexd_test_preinstall.apex" />
+ </target_preparer>
+
+ <!-- system_server might still hold a reference to apexservice. This means that apexd is still
+ running, and test apexes pushed in the PushFilePreparer above are not yet scanned.
+ One way to solve this is to reboot a device, but that would significantly increase
+ execution of this test module. Instead, force a GC in system_server by sending kill -10. -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="shell kill -10 $(pidof system_server)" />
+ <option name="teardown-command" value="shell kill -10 $(pidof system_server)" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.GTest" >
<!-- Note: despite how these lines read, the test will run nicely separated out
of a subfolder. -->
diff --git a/apexd/OWNERS b/apexd/OWNERS
deleted file mode 100644
index ed9813e..0000000
--- a/apexd/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-agampe@google.com
diff --git a/apexd/aidl/android/apex/IApexService.aidl b/apexd/aidl/android/apex/IApexService.aidl
index 57784a8..c4405b8 100644
--- a/apexd/aidl/android/apex/IApexService.aidl
+++ b/apexd/aidl/android/apex/IApexService.aidl
@@ -95,4 +95,24 @@
* functional on user builds.
*/
void resumeRevertIfNeeded();
+ /**
+ * Forces apexd to remount all active packages.
+ *
+ * This call is mostly useful for speeding up development of APEXes.
+ * Instead of going through a full APEX installation that requires a reboot,
+ * developers can incorporate this method in much faster `adb sync` based
+ * workflow:
+ *
+ * 1. adb shell stop
+ * 2. adb sync
+ * 3. adb shell cmd -w apexservice remountPackages
+ * 4. adb shell start
+ *
+ * Note, that for an APEX package will be successfully remounted only if
+ * there are no alive processes holding a reference to it.
+ *
+ * Not meant for use outside of testing. This call will not be functional
+ * on user builds. Only root is allowed to call this method.
+ */
+ void remountPackages();
}
diff --git a/apexd/apex_database.cpp b/apexd/apex_database.cpp
index f8549a1..d027f95 100644
--- a/apexd/apex_database.cpp
+++ b/apexd/apex_database.cpp
@@ -34,6 +34,7 @@
#include <unordered_map>
#include <utility>
+using android::base::ConsumeSuffix;
using android::base::ParseInt;
using android::base::ReadFileToString;
using android::base::Result;
@@ -168,6 +169,25 @@
return {};
}
+// This is not the right place to do this normalization, but proper solution
+// will require some refactoring first. :(
+// TODO(ioffe): introduce MountedApexDataBuilder and delegate all
+// building/normalization logic to it.
+void NormalizeIfDeleted(MountedApexData* apex_data) {
+ std::string_view full_path = apex_data->full_path;
+ if (ConsumeSuffix(&full_path, "(deleted)")) {
+ apex_data->deleted = true;
+ auto it = full_path.rbegin();
+ while (it != full_path.rend() && isspace(*it)) {
+ it++;
+ }
+ full_path.remove_suffix(it - full_path.rbegin());
+ } else {
+ apex_data->deleted = false;
+ }
+ apex_data->full_path = full_path;
+}
+
Result<MountedApexData> resolveMountInfo(const BlockDevice& block,
const std::string& mountPoint) {
// Now, see if it is dm-verity or loop mounted
@@ -177,9 +197,11 @@
if (!backingFile.ok()) {
return backingFile.error();
}
- return MountedApexData(block.DevPath(), *backingFile, mountPoint,
- /* device_name= */ "",
- /* hashtree_loop_name= */ "");
+ auto result = MountedApexData(block.DevPath(), *backingFile, mountPoint,
+ /* device_name= */ "",
+ /* hashtree_loop_name= */ "");
+ NormalizeIfDeleted(&result);
+ return result;
}
case DeviceMapperDevice: {
auto name = block.GetProperty("dm/name");
@@ -192,6 +214,7 @@
if (auto status = PopulateLoopInfo(block, &result); !status.ok()) {
return status.error();
}
+ NormalizeIfDeleted(&result);
return result;
}
case UnknownDevice: {
@@ -254,7 +277,9 @@
activeVersions[package] = version;
SetLatest(package, mountData->full_path);
}
- LOG(INFO) << "Found " << mountPoint;
+ LOG(INFO) << "Found " << mountPoint << " backed by"
+ << (mountData->deleted ? " deleted " : " ") << "file "
+ << mountData->full_path;
}
LOG(INFO) << mounted_apexes_.size() << " packages restored.";
diff --git a/apexd/apex_database.h b/apexd/apex_database.h
index 5b738b7..1fb5bd3 100644
--- a/apexd/apex_database.h
+++ b/apexd/apex_database.h
@@ -38,6 +38,8 @@
// Name of the loop device backing up hashtree or empty string in case
// hashtree is embedded inside an APEX.
std::string hashtree_loop_name;
+ // Whenever apex file specified in full_path was deleted.
+ bool deleted;
MountedApexData() {}
MountedApexData(const std::string& loop_name, const std::string& full_path,
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index ba1647e..9664221 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -51,11 +51,6 @@
constexpr const char* kImageFilename = "apex_payload.img";
constexpr const char* kBundledPublicKeyFilename = "apex_pubkey";
-#ifdef DEBUG_ALLOW_BUNDLED_KEY
-constexpr const bool kDebugAllowBundledKey = true;
-#else
-constexpr const bool kDebugAllowBundledKey = false;
-#endif
} // namespace
@@ -257,15 +252,6 @@
return Error() << "Error verifying " << apex.GetPath() << ": "
<< "public key doesn't match the pre-installed one";
}
- } else if (kDebugAllowBundledKey) {
- // Failing to find the matching public key in the built-in partitions
- // is a hard error for non-debuggable build. For debuggable builds,
- // the public key bundled in the APEX itself is used as a fallback.
- LOG(WARNING) << "Verifying " << apex.GetPath() << " with the bundled key";
- if (!CompareKeys(pk, pk_len, apex.GetBundledPublicKey())) {
- return Error() << "Error verifying " << apex.GetPath() << ": "
- << "public key doesn't match the one bundled in the APEX";
- }
} else {
return public_key.error();
}
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index 8b82b41..cd4b175 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -80,6 +80,7 @@
}
TEST(ApexFileTest, VerifyApexVerity) {
+ ASSERT_RESULT_OK(collectPreinstalledData({"/system_ext/apex"}));
const std::string filePath = testDataDir + "apex.apexd_test.apex";
Result<ApexFile> apexFile = ApexFile::Open(filePath);
ASSERT_RESULT_OK(apexFile);
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 0b81e42..bf673ce 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -63,6 +63,7 @@
#include <algorithm>
#include <array>
#include <chrono>
+#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
@@ -1607,7 +1608,7 @@
// Make sure that kActiveApexPackagesDataDir exists.
auto create_dir_status =
- createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0750);
+ createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0755);
if (!create_dir_status.ok()) {
return create_dir_status.error();
}
@@ -1687,6 +1688,9 @@
// temporary folder and restore state from it in case unstagePackages fails.
for (const std::string& path : paths) {
+ if (isPathForBuiltinApexes(path)) {
+ return Error() << "Can't uninstall pre-installed apex " << path;
+ }
if (access(path.c_str(), F_OK) != 0) {
return ErrnoError() << "Can't access " << path;
}
@@ -1820,63 +1824,10 @@
}
Result<void> remountApexFile(const std::string& path) {
- auto ret = deactivatePackage(path);
- if (!ret.ok()) return ret.error();
-
- ret = activatePackage(path);
- if (!ret.ok()) return ret.error();
-
- return {};
-}
-
-Result<void> monitorBuiltinDirs() {
- int fd = inotify_init1(IN_CLOEXEC);
- if (fd == -1) {
- return ErrnoErrorf("inotify_init failed");
+ if (auto ret = deactivatePackage(path); !ret.ok()) {
+ return ret;
}
- std::map<int, std::string> desc_to_dir;
- for (const auto& dir : kApexPackageBuiltinDirs) {
- int desc = inotify_add_watch(fd, dir.c_str(), IN_CREATE | IN_MODIFY);
- if (desc == -1 && errno != ENOENT) {
- // don't complain about missing directories like /product/apex
- return ErrnoErrorf("failed to add watch on {}", dir);
- }
- desc_to_dir.emplace(desc, dir);
- }
- static std::thread th([fd, desc_to_dir]() -> void {
- constexpr int num_events = 100;
- constexpr size_t average_path_length = 50;
- char buffer[num_events *
- (sizeof(struct inotify_event) + average_path_length)];
- while (true) {
- ssize_t length = read(fd, buffer, sizeof(buffer));
- if (length < 0) {
- PLOG(ERROR) << "failed to read inotify event: " << strerror(errno);
- continue;
- }
- int i = 0;
- while (i < length) {
- struct inotify_event* e = (struct inotify_event*)&buffer[i];
- if (e->len > 0 && (e->mask & (IN_CREATE | IN_MODIFY)) != 0) {
- if (desc_to_dir.find(e->wd) == desc_to_dir.end()) {
- LOG(ERROR) << "unexpected watch descriptor " << e->wd
- << " for name: " << e->name;
- } else {
- std::string path = desc_to_dir.at(e->wd) + "/" + e->name;
- auto ret = remountApexFile(path);
- if (!ret.ok()) {
- LOG(ERROR) << ret.error().message();
- } else {
- LOG(INFO) << path << " remounted because it was changed";
- }
- }
- }
- i += sizeof(struct inotify_event) + e->len;
- }
- }
- });
-
- return {};
+ return activatePackage(path);
}
void initialize(CheckpointInterface* checkpoint_service) {
@@ -1993,13 +1944,6 @@
// Now that APEXes are mounted, snapshot or restore DE_sys data.
snapshotOrRestoreDeSysData();
-
- if (android::base::GetBoolProperty("ro.debuggable", false)) {
- status = monitorBuiltinDirs();
- if (!status.ok()) {
- LOG(ERROR) << "cannot monitor built-in dirs: " << status.error();
- }
- }
}
void onAllPackagesActivated() {
@@ -2226,5 +2170,36 @@
return status != kApexStatusReady && status != kApexStatusActivated;
}
+Result<void> remountPackages() {
+ std::vector<std::string> apexes;
+ gMountedApexes.ForallMountedApexes([&apexes](const std::string& /*package*/,
+ const MountedApexData& data,
+ bool latest) {
+ if (latest) {
+ LOG(DEBUG) << "Found active APEX " << data.full_path;
+ apexes.push_back(data.full_path);
+ }
+ });
+ std::vector<std::string> failed;
+ for (const std::string& apex : apexes) {
+ // Since this is only used during development workflow, we are trying to
+ // remount as many apexes as possible instead of failing fast.
+ if (auto ret = remountApexFile(apex); !ret) {
+ LOG(WARNING) << "Failed to remount " << apex << " : " << ret.error();
+ failed.emplace_back(apex);
+ }
+ }
+ static constexpr const char* kErrorMessage =
+ "Failed to remount following APEX packages, hence previous versions of "
+ "them are still active. If APEX you are developing is in this list, it "
+ "means that there still are alive processes holding a reference to the "
+ "previous version of your APEX.\n";
+ if (!failed.empty()) {
+ return Error() << kErrorMessage << "Failed (" << failed.size() << ") "
+ << "APEX packages: [" << Join(failed, ',') << "]";
+ }
+ return {};
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd.h b/apexd/apexd.h
index e757ae6..cde6989 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -114,6 +114,10 @@
int unmountAll();
+// Optimistically tries to remount as many APEX packages as possible.
+// For more documentation see corresponding binder call in IApexService.aidl.
+android::base::Result<void> remountPackages();
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd.rc b/apexd/apexd.rc
index 9d77ef3..e306025 100644
--- a/apexd/apexd.rc
+++ b/apexd/apexd.rc
@@ -1,8 +1,9 @@
service apexd /system/bin/apexd
+ interface aidl apexservice
class core
user root
group system
- shutdown critical
+ oneshot
disabled # does not start with the core class
service apexd-bootstrap /system/bin/apexd --bootstrap
diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp
index 24a2b26..2a4dfd5 100644
--- a/apexd/apexd_main.cpp
+++ b/apexd/apexd_main.cpp
@@ -126,6 +126,8 @@
android::apex::bootCompletedCleanup);
}
+ android::apex::binder::AllowServiceShutdown();
+
android::apex::binder::JoinThreadPool();
return 1;
}
diff --git a/apexd/apexd_verity_test.cpp b/apexd/apexd_verity_test.cpp
index ce86dab..cf2317c 100644
--- a/apexd/apexd_verity_test.cpp
+++ b/apexd/apexd_verity_test.cpp
@@ -25,6 +25,7 @@
#include <gtest/gtest.h>
#include "apex_file.h"
+#include "apex_preinstalled_data.h"
#include "apexd_test_utils.h"
#include "apexd_verity.h"
@@ -44,6 +45,7 @@
}
TEST(ApexdVerityTest, ReusesHashtree) {
+ ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
TemporaryDir td;
auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
@@ -76,6 +78,7 @@
}
TEST(ApexdVerityTest, RegenerateHashree) {
+ ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
TemporaryDir td;
auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index 1dfff0a..0a8ef6a 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -29,8 +29,10 @@
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/IServiceManager.h>
+#include <binder/LazyServiceRegistrar.h>
#include <binder/ProcessState.h>
#include <binder/Status.h>
+#include <private/android_filesystem_config.h>
#include <utils/String16.h>
#include "apex_file.h"
@@ -50,6 +52,16 @@
using BinderStatus = ::android::binder::Status;
+BinderStatus CheckCallerIsRoot(const std::string& name) {
+ uid_t uid = IPCThreadState::self()->getCallingUid();
+ if (uid != AID_ROOT) {
+ std::string msg = "Only root is allowed to call " + name;
+ return BinderStatus::fromExceptionCode(BinderStatus::EX_SECURITY,
+ String8(name.c_str()));
+ }
+ return BinderStatus::ok();
+}
+
class ApexService : public BnApexService {
public:
using BinderStatus = ::android::binder::Status;
@@ -87,6 +99,7 @@
BinderStatus destroyDeSnapshots(int rollback_id) override;
BinderStatus destroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids) override;
+ BinderStatus remountPackages() override;
status_t dump(int fd, const Vector<String16>& args) override;
@@ -524,6 +537,22 @@
return BinderStatus::ok();
}
+BinderStatus ApexService::remountPackages() {
+ LOG(DEBUG) << "remountPackages() received by ApexService";
+ if (auto debug = CheckDebuggable("remountPackages"); !debug.isOk()) {
+ return debug;
+ }
+ if (auto root = CheckCallerIsRoot("remountPackages"); !root.isOk()) {
+ return root;
+ }
+ if (auto res = ::android::apex::remountPackages(); !res.ok()) {
+ return BinderStatus::fromExceptionCode(
+ BinderStatus::EX_SERVICE_SPECIFIC,
+ String8(res.error().message().c_str()));
+ }
+ return BinderStatus::ok();
+}
+
status_t ApexService::onTransact(uint32_t _aidl_code, const Parcel& _aidl_data,
Parcel* _aidl_reply, uint32_t _aidl_flags) {
switch (_aidl_code) {
@@ -631,8 +660,20 @@
<< std::endl
<< " getStagedSessionInfo [sessionId] - displays information about a "
"given session previously submitted"
+ << std::endl
<< " submitStagedSession [sessionId] - attempts to submit the "
"installer session with given id"
+ << std::endl
+ << " remountPackages - Force apexd to remount active packages. This "
+ "call can be used to speed up development workflow of an APEX "
+ "package. Example of usage:\n"
+ " 1. adb shell stop\n"
+ " 2. adb sync\n"
+ " 3. adb shell cmd -w apexservice remountPackages\n"
+ " 4. adb shell start\n"
+ "\n"
+ "Note: APEX package will be successfully remounted only if there "
+ "are no alive processes holding a reference to it"
<< std::endl;
dprintf(fd, "%s", log.operator std::string().c_str());
};
@@ -851,6 +892,17 @@
return BAD_VALUE;
}
+ if (cmd == String16("remountPackages")) {
+ BinderStatus status = remountPackages();
+ if (status.isOk()) {
+ return OK;
+ }
+ std::string msg = StringLog() << "remountPackages failed: "
+ << status.toString8().string() << std::endl;
+ dprintf(err, "%s", msg.c_str());
+ return BAD_VALUE;
+ }
+
if (cmd == String16("help")) {
if (args.size() != 1) {
print_help(err, "Help has no options");
@@ -868,18 +920,24 @@
static constexpr const char* kApexServiceName = "apexservice";
-using android::defaultServiceManager;
using android::IPCThreadState;
using android::ProcessState;
using android::sp;
-using android::String16;
+using android::binder::LazyServiceRegistrar;
void CreateAndRegisterService() {
sp<ProcessState> ps(ProcessState::self());
- // Create binder service and register with servicemanager
+ // Create binder service and register with LazyServiceRegistrar
sp<ApexService> apexService = new ApexService();
- defaultServiceManager()->addService(String16(kApexServiceName), apexService);
+ auto lazyRegistrar = LazyServiceRegistrar::getInstance();
+ lazyRegistrar.forcePersist(true);
+ lazyRegistrar.registerService(apexService, kApexServiceName);
+}
+
+void AllowServiceShutdown() {
+ auto lazyRegistrar = LazyServiceRegistrar::getInstance();
+ lazyRegistrar.forcePersist(false);
}
void StartThreadPool() {
diff --git a/apexd/apexservice.h b/apexd/apexservice.h
index b246a10..73c0f1f 100644
--- a/apexd/apexservice.h
+++ b/apexd/apexservice.h
@@ -22,6 +22,7 @@
namespace binder {
void CreateAndRegisterService();
+void AllowServiceShutdown();
void StartThreadPool();
void JoinThreadPool();
diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp
index b435626..9751397 100644
--- a/apexd/apexservice_test.cpp
+++ b/apexd/apexservice_test.cpp
@@ -40,6 +40,7 @@
#include <android-base/strings.h>
#include <android/os/IVold.h>
#include <binder/IServiceManager.h>
+#include <fs_mgr_overlayfs.h>
#include <fstab/fstab.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -99,7 +100,7 @@
using android::IServiceManager;
sp<IServiceManager> sm = android::defaultServiceManager();
- sp<IBinder> binder = sm->getService(String16("apexservice"));
+ sp<IBinder> binder = sm->waitForService(String16("apexservice"));
if (binder != nullptr) {
service_ = android::interface_cast<IApexService>(binder);
}
@@ -136,12 +137,29 @@
static bool IsSelinuxEnforced() { return 0 != security_getenforce(); }
- Result<bool> IsActive(const std::string& name, int64_t version) {
+ Result<bool> IsActive(const std::string& name) {
+ std::vector<ApexInfo> list;
+ android::binder::Status status = service_->getActivePackages(&list);
+ if (!status.isOk()) {
+ return Error() << "Failed to check if " << name
+ << " is active : " << status.exceptionMessage().c_str();
+ }
+ for (const ApexInfo& apex : list) {
+ if (apex.moduleName == name) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Result<bool> IsActive(const std::string& name, int64_t version,
+ const std::string& path) {
std::vector<ApexInfo> list;
android::binder::Status status = service_->getActivePackages(&list);
if (status.isOk()) {
for (const ApexInfo& p : list) {
- if (p.moduleName == name && p.versionCode == version) {
+ if (p.moduleName == name && p.versionCode == version &&
+ p.modulePath == path) {
return true;
}
}
@@ -550,6 +568,16 @@
return slaves;
}
+Result<void> CopyFile(const std::string& from, const std::string& to,
+ const fs::copy_options& options) {
+ std::error_code ec;
+ if (!fs::copy_file(from, to, options)) {
+ return Error() << "Failed to copy file " << from << " to " << to << " : "
+ << ec.message();
+ }
+ return {};
+}
+
} // namespace
TEST_F(ApexServiceTest, HaveSelinux) {
@@ -957,7 +985,10 @@
{
// Check package is not active.
- Result<bool> active = IsActive(installer_->package, installer_->version);
+ std::string path = stage_package ? installer_->test_installed_file
+ : installer_->test_file;
+ Result<bool> active =
+ IsActive(installer_->package, installer_->version, path);
ASSERT_TRUE(IsOk(active));
ASSERT_FALSE(*active);
}
@@ -1034,7 +1065,8 @@
{
// Check package is active.
- Result<bool> active = IsActive(installer_->package, installer_->version);
+ Result<bool> active = IsActive(installer_->package, installer_->version,
+ installer_->test_installed_file);
ASSERT_TRUE(IsOk(active));
ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ',');
}
@@ -1164,7 +1196,8 @@
<< GetDebugStr(installer_.get());
{
// Check package is active.
- Result<bool> active = IsActive(installer_->package, installer_->version);
+ Result<bool> active = IsActive(installer_->package, installer_->version,
+ installer_->test_installed_file);
ASSERT_TRUE(IsOk(active));
ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ',');
}
@@ -1192,7 +1225,8 @@
<< GetDebugStr(installer_.get());
{
// Check package is active.
- Result<bool> active = IsActive(installer_->package, installer_->version);
+ Result<bool> active = IsActive(installer_->package, installer_->version,
+ installer_->test_installed_file);
ASSERT_TRUE(IsOk(active));
ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ',');
}
@@ -1594,7 +1628,8 @@
// Ensure that the package is neither active nor mounted.
for (const InstallerUPtr& installer : installers) {
- Result<bool> active = IsActive(installer->package, installer->version);
+ Result<bool> active = IsActive(installer->package, installer->version,
+ installer->test_file);
ASSERT_TRUE(IsOk(active));
EXPECT_FALSE(*active);
}
@@ -2063,7 +2098,7 @@
// Make sure that /data/apex/active exists and is empty
ASSERT_TRUE(
- IsOk(createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0750)));
+ IsOk(createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0755)));
auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir);
ASSERT_TRUE(IsOk(active_pkgs));
ASSERT_EQ(0u, active_pkgs->size());
@@ -2147,6 +2182,18 @@
UnorderedElementsAre(installer1.test_installed_file));
}
+TEST_F(ApexServiceTest, UnstagePackagesFailPreInstalledApex) {
+ auto status = service_->unstagePackages(
+ {"/system/apex/com.android.apex.cts.shim.apex"});
+ ASSERT_FALSE(IsOk(status));
+ const std::string& error_message =
+ std::string(status.exceptionMessage().c_str());
+ ASSERT_THAT(error_message,
+ HasSubstr("Can't uninstall pre-installed apex "
+ "/system/apex/com.android.apex.cts.shim.apex"));
+ ASSERT_TRUE(RegularFileExists("/system/apex/com.android.apex.cts.shim.apex"));
+}
+
class ApexServiceRevertTest : public ApexServiceTest {
protected:
void SetUp() override { ApexServiceTest::SetUp(); }
@@ -2171,7 +2218,7 @@
// First check that /data/apex/active exists and has correct permissions.
struct stat sd;
ASSERT_EQ(0, stat(kActiveApexPackagesDataDir, &sd));
- ASSERT_EQ(0750u, sd.st_mode & ALLPERMS);
+ ASSERT_EQ(0755u, sd.st_mode & ALLPERMS);
// Now read content and check it contains expected values.
auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir);
@@ -2668,6 +2715,80 @@
ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list)));
}
+TEST_F(ApexServiceTest, RemountPackagesPackageOnSystemChanged) {
+ static constexpr const char* kSystemPath =
+ "/system_ext/apex/apex.apexd_test.apex";
+ static constexpr const char* kPackageName = "com.android.apex.test_package";
+ if (!fs_mgr_overlayfs_is_setup()) {
+ GTEST_SKIP() << "/system_ext is not overlayed into read-write";
+ }
+ if (auto res = IsActive(kPackageName); !res.ok()) {
+ FAIL() << res.error();
+ } else {
+ ASSERT_FALSE(*res) << kPackageName << " is active";
+ }
+ ASSERT_EQ(0, access(kSystemPath, F_OK))
+ << "Failed to stat " << kSystemPath << " : " << strerror(errno);
+ ASSERT_TRUE(IsOk(service_->activatePackage(kSystemPath)));
+ std::string backup_path = GetTestFile("apex.apexd_test.apexd.bak");
+ // Copy original /system_ext apex file. We will need to restore it after test
+ // runs.
+ ASSERT_RESULT_OK(CopyFile(kSystemPath, backup_path, fs::copy_options::none));
+
+ // Make sure we cleanup after ourselves.
+ auto deleter = android::base::make_scope_guard([&]() {
+ if (auto ret = service_->deactivatePackage(kSystemPath); !ret.isOk()) {
+ LOG(ERROR) << ret.exceptionMessage();
+ }
+ auto ret = CopyFile(backup_path, kSystemPath,
+ fs::copy_options::overwrite_existing);
+ if (!ret.ok()) {
+ LOG(ERROR) << ret.error();
+ }
+ });
+
+ // Copy v2 version to /system_ext/apex/ and then call remountPackages.
+ PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex"));
+ if (!installer.Prepare()) {
+ FAIL() << GetDebugStr(&installer);
+ }
+ ASSERT_RESULT_OK(CopyFile(installer.test_file, kSystemPath,
+ fs::copy_options::overwrite_existing));
+ // Don't check that remountPackages succeeded. Most likely it will fail, but
+ // it should still remount our test apex.
+ service_->remountPackages();
+
+ // Check that v2 is now active.
+ auto active_apex = GetActivePackage("com.android.apex.test_package");
+ ASSERT_RESULT_OK(active_apex);
+ ASSERT_EQ(2u, active_apex->versionCode);
+ // Sanity check that module path didn't change.
+ ASSERT_EQ(kSystemPath, active_apex->modulePath);
+}
+
+TEST_F(ApexServiceActivationSuccessTest, RemountPackagesPackageOnDataChanged) {
+ ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file)))
+ << GetDebugStr(installer_.get());
+ // Copy v2 version to /data/apex/active and then call remountPackages.
+ PrepareTestApexForInstall installer2(GetTestFile("apex.apexd_test_v2.apex"));
+ if (!installer2.Prepare()) {
+ FAIL() << GetDebugStr(&installer2);
+ }
+ ASSERT_RESULT_OK(CopyFile(installer2.test_file,
+ installer_->test_installed_file,
+ fs::copy_options::overwrite_existing));
+ // Don't check that remountPackages succeeded. Most likely it will fail, but
+ // it should still remount our test apex.
+ service_->remountPackages();
+
+ // Check that v2 is now active.
+ auto active_apex = GetActivePackage("com.android.apex.test_package");
+ ASSERT_RESULT_OK(active_apex);
+ ASSERT_EQ(2u, active_apex->versionCode);
+ // Sanity check that module path didn't change.
+ ASSERT_EQ(installer_->test_installed_file, active_apex->modulePath);
+}
+
class LogTestToLogcat : public ::testing::EmptyTestEventListener {
void OnTestStart(const ::testing::TestInfo& test_info) override {
#ifdef __ANDROID__
diff --git a/tests/Android.bp b/tests/Android.bp
index 3cb6d7c..95475c7 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -210,20 +210,6 @@
}
java_test_host {
- name: "apex_remount_tests",
- srcs: ["src/**/ApexRemountTest.java"],
- libs: ["tradefed"],
- static_libs: [
- "module_test_util",
- "truth-prebuilt",
- "apex_manifest_proto_java",
- ],
- test_config: "apex-remount-tests.xml",
-
- data: [":com.android.apex.cts.shim.v2_prebuilt"],
-}
-
-java_test_host {
name: "module_test_utils_tests",
srcs: ["src/**/ModuleTestUtilsTest.java"],
libs: ["tradefed", "truth-prebuilt"],
@@ -246,6 +232,7 @@
test_suites: ["general-tests"],
data: [
":apex.apexd_test_v2",
+ ":com.android.apex.cts.shim.v2_prebuilt",
":com.android.apex.cts.shim.v2_no_pb",
],
}
diff --git a/tests/apex-remount-tests.xml b/tests/apex-remount-tests.xml
deleted file mode 100644
index 50bf95f..0000000
--- a/tests/apex-remount-tests.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Runs the apex remount test cases">
- <option name="test-suite-tag" value="apex_remount_tests" />
- <option name="test-suite-tag" value="apct" />
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <test class="com.android.tradefed.testtype.HostTest" >
- <option name="jar" value="apex_remount_tests.jar" />
- </test>
-</configuration>
diff --git a/tests/src/com/android/tests/apex/ApexRemountTest.java b/tests/src/com/android/tests/apex/ApexRemountTest.java
deleted file mode 100644
index bd73182..0000000
--- a/tests/src/com/android/tests/apex/ApexRemountTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.apex;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeTrue;
-
-import com.android.apex.Protos.ApexManifest;
-import com.android.tests.util.ModuleTestUtils;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.util.JsonFormat;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for automatic remount of APEXes when they are updated via `adb sync`
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class ApexRemountTest extends BaseHostJUnit4Test {
- private File mSavedShimFile;
- private final ModuleTestUtils mUtils = new ModuleTestUtils(this);
-
- private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex";
-
- @Before
- public void setUp() throws Exception {
- mUtils.abandonActiveStagedSession();
- mUtils.uninstallShimApexIfNecessary();
- mSavedShimFile = getDevice().pullFile(SHIM_APEX_PATH);
- }
-
- @After
- public void tearDown() throws Exception {
- if (mSavedShimFile != null) {
- getDevice().remountSystemWritable();
- getDevice().pushFile(mSavedShimFile, SHIM_APEX_PATH);
- getDevice().reboot();
- assertThat(getShimApexManifest().getVersion()).isEqualTo(1L);
- }
- }
-
- @Test
- public void testApexIsRemountedUponUpdate() throws Exception {
- assumeTrue("APEXes on the device are flattened", hasNonFlattenedApex());
-
- ModuleTestUtils utils = new ModuleTestUtils(this);
- File updatedFile = utils.getTestFile("com.android.apex.cts.shim.v2.apex");
-
- getDevice().remountSystemWritable();
- getDevice().pushFile(updatedFile, SHIM_APEX_PATH);
-
- // Wait some time until the update is detected by apexd and remount is done
- TimeUnit.SECONDS.sleep(5);
-
- assertThat(getShimApexManifest().getVersion()).isEqualTo(2L);
- }
-
- private ApexManifest getShimApexManifest() throws DeviceNotAvailableException,
- InvalidProtocolBufferException {
- String json = getDevice().executeShellCommand(
- "cat /apex/com.android.apex.cts.shim/apex_manifest.pb");
- ApexManifest.Builder builder = ApexManifest.newBuilder();
- JsonFormat.parser().merge(json, builder);
- return builder.build();
- }
-
- private boolean hasNonFlattenedApex() throws Exception {
- return "true".equals(getDevice().getProperty("ro.apex.updatable"));
- }
-}
diff --git a/tests/src/com/android/tests/apex/ApexRollbackTests.java b/tests/src/com/android/tests/apex/ApexRollbackTests.java
index dfc05fc..c64182c 100644
--- a/tests/src/com/android/tests/apex/ApexRollbackTests.java
+++ b/tests/src/com/android/tests/apex/ApexRollbackTests.java
@@ -82,7 +82,13 @@
@Test
public void testAutomaticBootLoopRecovery() throws Exception {
assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
-
+ ITestDevice device = getDevice();
+ // Skip this test if there is already crashing process on device
+ boolean hasCrashingProcess =
+ device.getBooleanProperty("sys.init.updatable_crashing", false);
+ String crashingProcess = device.getProperty("sys.init.updatable_crashing_process_name");
+ assumeFalse(
+ "Device already has a crashing process: " + crashingProcess, hasCrashingProcess);
File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
// To simulate an apex update that causes a boot loop, we install a
@@ -92,7 +98,6 @@
// persist.debug.trigger_watchdog.apex is installed. If so,
// trigger_watchdog.sh repeatedly kills the system server causing a
// boot loop.
- ITestDevice device = getDevice();
assertThat(device.setProperty("persist.debug.trigger_watchdog.apex",
"com.android.apex.cts.shim@2")).isTrue();
String error = device.installPackage(apexFile, false, "--wait");
@@ -118,7 +123,7 @@
assertThat(activatedApexes).doesNotContain(ctsShimV2);
// Assert that a session has failed with the expected reason
- String sessionInfo = device.executeShellCommand("cmd apexservice getStagedSessionInfo "
+ String sessionInfo = device.executeShellCommand("cmd -w apexservice getStagedSessionInfo "
+ sessionIdToCheck);
assertThat(sessionInfo).contains("revertReason: zygote");
}
diff --git a/tests/src/com/android/tests/apex/ApexdHostTest.java b/tests/src/com/android/tests/apex/ApexdHostTest.java
index b7b6a95..2e3a832 100644
--- a/tests/src/com/android/tests/apex/ApexdHostTest.java
+++ b/tests/src/com/android/tests/apex/ApexdHostTest.java
@@ -29,6 +29,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.time.Duration;
import java.util.Set;
@@ -38,10 +39,13 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class ApexdHostTest extends BaseHostJUnit4Test {
+ private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex";
+
private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this);
@Test
public void testOrphanedApexIsNotActivated() throws Exception {
+ assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
try {
assertThat(getDevice().pushFile(mTestUtils.getTestFile("apex.apexd_test_v2.apex"),
@@ -61,8 +65,9 @@
}
@Test
public void testApexWithoutPbIsNotActivated() throws Exception {
- final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex";
+ assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
assumeTrue("Device requires root", getDevice().isAdbRoot());
+ final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex";
try {
assertThat(getDevice().pushFile(mTestUtils.getTestFile(testApexFile),
"/data/apex/active/" + testApexFile)).isTrue();
@@ -79,4 +84,34 @@
getDevice().executeShellV2Command("rm /data/apex/active/" + testApexFile);
}
}
+
+ @Test
+ public void testRemountApex() throws Exception {
+ assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
+ assumeTrue("Device requires root", getDevice().isAdbRoot());
+ final File oldFile = getDevice().pullFile(SHIM_APEX_PATH);
+ try {
+ getDevice().remountSystemWritable();
+ // In case remount requires a reboot, wait for boot to complete.
+ getDevice().waitForBootComplete(Duration.ofMinutes(3).toMillis());
+ final File newFile = mTestUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+ // Stop framework
+ getDevice().executeShellV2Command("stop");
+ // Push new shim APEX. This simulates adb sync.
+ getDevice().pushFile(newFile, SHIM_APEX_PATH);
+ // Ask apexd to remount packages
+ getDevice().executeShellV2Command("cmd -w apexservice remountPackages");
+ // Start framework
+ getDevice().executeShellV2Command("start");
+ // Give enough time for system_server to boot.
+ Thread.sleep(Duration.ofSeconds(15).toMillis());
+ final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
+ ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
+ "com.android.apex.cts.shim", 2L);
+ assertThat(activeApexes).contains(testApex);
+ } finally {
+ getDevice().pushFile(oldFile, SHIM_APEX_PATH);
+ getDevice().reboot();
+ }
+ }
}
diff --git a/tests/util/com/android/tests/util/ModuleTestUtils.java b/tests/util/com/android/tests/util/ModuleTestUtils.java
index 8f62bc3..dcf2efd 100644
--- a/tests/util/com/android/tests/util/ModuleTestUtils.java
+++ b/tests/util/com/android/tests/util/ModuleTestUtils.java
@@ -196,14 +196,16 @@
return;
}
+ final ITestDevice.ApexInfo shim = getShimApex();
+ if (shim.sourceDir.startsWith("/system")) {
+ CLog.i("Skipping uninstall of " + shim.sourceDir + ". Reason: pre-installed version");
+ return;
+ }
+ CLog.i("Uninstalling shim apex");
final String errorMessage = mTest.getDevice().uninstallPackage(SHIM);
if (errorMessage == null) {
- CLog.i("Uninstalling shim apex");
mTest.getDevice().reboot();
} else {
- // Most likely we tried to uninstall system version and failed. It should be fine to
- // continue tests.
- // TODO(b/140813980): use ApexInfo.sourceDir to decide whenever to issue an uninstall.
CLog.w("Failed to uninstall shim APEX: " + errorMessage);
}
assertThat(getShimApex().versionCode).isEqualTo(1L);