summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp51
-rw-r--r--OWNERS2
-rw-r--r--fuzzers/GsiServiceFuzzer.cpp29
-rw-r--r--gsi_service.cpp41
-rw-r--r--gsi_service.h2
-rw-r--r--gsi_tool.cpp78
-rw-r--r--include/libgsi/libgsi.h2
-rw-r--r--libgsid.cpp2
-rw-r--r--partition_installer.cpp3
-rw-r--r--tests/Android.bp17
-rw-r--r--tests/DSUEndtoEndTest.java122
-rw-r--r--tests/DsuGsiIntegrationTest.java9
-rw-r--r--tests/DsuGsiToolTest.java107
-rw-r--r--tests/dsu_gsi_tool_test.xml21
14 files changed, 401 insertions, 85 deletions
diff --git a/Android.bp b/Android.bp
index 50618f9..1b78971 100644
--- a/Android.bp
+++ b/Android.bp
@@ -74,21 +74,19 @@ cc_library_headers {
recovery_available: true,
vendor_available: true,
export_include_dirs: ["include"],
+ apex_available: [
+ "//apex_available:anyapex",
+ "//apex_available:platform",
+ ],
+ min_sdk_version: "31",
}
-cc_binary {
- name: "gsid",
+cc_defaults {
+ name: "gsid_defaults",
srcs: [
- "daemon.cpp",
"gsi_service.cpp",
"partition_installer.cpp",
],
- required: [
- "mke2fs",
- ],
- init_rc: [
- "gsid.rc",
- ],
shared_libs: [
"libbase",
"libbinder",
@@ -124,6 +122,22 @@ cc_binary {
local_include_dirs: ["include"],
}
+cc_binary {
+ name: "gsid",
+ defaults: [
+ "gsid_defaults",
+ ],
+ srcs: [
+ "daemon.cpp",
+ ],
+ required: [
+ "mke2fs",
+ ],
+ init_rc: [
+ "gsid.rc",
+ ],
+}
+
aidl_interface {
name: "gsi_aidl_interface",
unstable: true,
@@ -149,3 +163,22 @@ filegroup {
],
path: "aidl",
}
+
+cc_fuzz {
+ name: "gsi_service_fuzzer",
+ defaults: [
+ "gsid_defaults",
+ "service_fuzzer_defaults",
+ "fuzzer_disable_leaks",
+ ],
+ srcs: [
+ "fuzzers/GsiServiceFuzzer.cpp",
+ ],
+ fuzz_config: {
+ triage_assignee: "waghpawan@google.com",
+ cc: [
+ "elsk@google.com",
+ "yochiang@google.com",
+ ]
+ },
+}
diff --git a/OWNERS b/OWNERS
index a9d0479..e8bcfb2 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 30545
+# Bug component: 322150
dvander@google.com
sspatil@google.com
elsk@google.com
diff --git a/fuzzers/GsiServiceFuzzer.cpp b/fuzzers/GsiServiceFuzzer.cpp
new file mode 100644
index 0000000..1d100d8
--- /dev/null
+++ b/fuzzers/GsiServiceFuzzer.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include <fuzzbinder/libbinder_driver.h>
+
+#include "gsi_service.h"
+
+using android::fuzzService;
+using android::sp;
+using android::gsi::GsiService;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ auto binder = sp<GsiService>::make();
+ fuzzService(binder, FuzzedDataProvider(data, size));
+ return 0;
+} \ No newline at end of file
diff --git a/gsi_service.cpp b/gsi_service.cpp
index 2caa445..224f520 100644
--- a/gsi_service.cpp
+++ b/gsi_service.cpp
@@ -109,6 +109,15 @@ void GsiService::Register(const std::string& name) {
if (!status.isOk()) return status; \
} while (0)
+#define ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK \
+ do { \
+ if (!android::base::EndsWith(GetActiveDsuSlot(), ".lock")) { \
+ ENFORCE_SYSTEM_OR_SHELL; \
+ } else { \
+ ENFORCE_SYSTEM; \
+ } \
+ } while (0)
+
int GsiService::SaveInstallation(const std::string& installation) {
auto dsu_slot = GetDsuSlot(installation);
auto install_dir_file = DsuInstallDirFile(dsu_slot);
@@ -314,7 +323,7 @@ binder::Status GsiService::setGsiAshmem(const ::android::os::ParcelFileDescripto
binder::Status GsiService::enableGsiAsync(bool one_shot, const std::string& dsuSlot,
const sp<IGsiServiceCallback>& resultCallback) {
- ENFORCE_SYSTEM_OR_SHELL;
+ ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK;
std::lock_guard<std::mutex> guard(lock_);
const auto result = EnableGsi(one_shot, dsuSlot);
@@ -323,7 +332,7 @@ binder::Status GsiService::enableGsiAsync(bool one_shot, const std::string& dsuS
}
binder::Status GsiService::enableGsi(bool one_shot, const std::string& dsuSlot, int* _aidl_return) {
- ENFORCE_SYSTEM_OR_SHELL;
+ ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK;
std::lock_guard<std::mutex> guard(lock_);
*_aidl_return = EnableGsi(one_shot, dsuSlot);
@@ -343,10 +352,11 @@ binder::Status GsiService::isGsiEnabled(bool* _aidl_return) {
}
binder::Status GsiService::removeGsiAsync(const sp<IGsiServiceCallback>& resultCallback) {
- bool result = false;
- auto status = removeGsi(&result);
- if (!status.isOk()) {
- LOG(ERROR) << "Could not removeGsi: " << status.exceptionMessage().string();
+ int result = IGsiService::INSTALL_OK;
+ bool success = true;
+ auto status = removeGsi(&success);
+ if (!status.isOk() || !success) {
+ LOG(ERROR) << "Could not removeGsi: " << status.exceptionMessage().c_str();
result = IGsiService::INSTALL_ERROR_GENERIC;
}
resultCallback->onResult(result);
@@ -354,7 +364,7 @@ binder::Status GsiService::removeGsiAsync(const sp<IGsiServiceCallback>& resultC
}
binder::Status GsiService::removeGsi(bool* _aidl_return) {
- ENFORCE_SYSTEM_OR_SHELL;
+ ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK;
std::lock_guard<std::mutex> guard(lock_);
std::string install_dir = GetActiveInstalledImageDir();
@@ -369,7 +379,7 @@ binder::Status GsiService::removeGsi(bool* _aidl_return) {
}
binder::Status GsiService::disableGsi(bool* _aidl_return) {
- ENFORCE_SYSTEM_OR_SHELL;
+ ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK;
std::lock_guard<std::mutex> guard(lock_);
*_aidl_return = DisableGsiInstall();
@@ -436,7 +446,7 @@ binder::Status GsiService::getInstalledDsuSlots(std::vector<std::string>* _aidl_
}
binder::Status GsiService::zeroPartition(const std::string& name, int* _aidl_return) {
- ENFORCE_SYSTEM_OR_SHELL;
+ ENFORCE_SYSTEM_OR_SHELL_IF_UNLOCK;
std::lock_guard<std::mutex> guard(lock_);
if (IsGsiRunning() || !IsGsiInstalled()) {
@@ -496,6 +506,12 @@ binder::Status GsiService::getAvbPublicKey(AvbPublicKey* dst, int32_t* _aidl_ret
return binder::Status::ok();
}
int fd = installer_->GetPartitionFd();
+ if (fd == -1) {
+ LOG(ERROR) << "Failed to get partition fd";
+ *_aidl_return = INSTALL_ERROR_GENERIC;
+ return binder::Status::ok();
+ }
+
if (!GetAvbPublicKeyFromFd(fd, dst)) {
LOG(ERROR) << "Failed to extract AVB public key";
*_aidl_return = INSTALL_ERROR_GENERIC;
@@ -610,7 +626,7 @@ binder::Status ImageService::createBackingImage(const std::string& name, int64_t
auto status = on_progress->onProgress(static_cast<int64_t>(current),
static_cast<int64_t>(total));
if (!status.isOk()) {
- LOG(ERROR) << "progress callback returned: " << status.toString8().string();
+ LOG(ERROR) << "progress callback returned: " << status.toString8().c_str();
return false;
}
return true;
@@ -871,7 +887,8 @@ int GsiService::ValidateInstallParams(std::string& install_dir) {
}
if (access(install_dir.c_str(), F_OK) != 0 && (errno == ENOENT)) {
- if (android::base::StartsWith(install_dir, kDsuSDPrefix)) {
+ if (android::base::StartsWith(install_dir, kDsuSDPrefix) ||
+ android::base::StartsWith(install_dir, kDefaultDsuImageFolder)) {
if (mkdir(install_dir.c_str(), 0755) != 0) {
PLOG(ERROR) << "Failed to create " << install_dir;
return INSTALL_ERROR_GENERIC;
@@ -905,7 +922,7 @@ int GsiService::ValidateInstallParams(std::string& install_dir) {
LOG(ERROR) << "cannot install GSIs to external media if verity uses check_at_most_once";
return INSTALL_ERROR_GENERIC;
}
- } else if (install_dir != kDefaultDsuImageFolder) {
+ } else if (!android::base::StartsWith(install_dir, kDefaultDsuImageFolder)) {
LOG(ERROR) << "cannot install DSU to " << install_dir;
return INSTALL_ERROR_GENERIC;
}
diff --git a/gsi_service.h b/gsi_service.h
index 3c4c278..0dce269 100644
--- a/gsi_service.h
+++ b/gsi_service.h
@@ -86,11 +86,11 @@ class GsiService : public BinderService<GsiService>, public BnGsiService {
std::string GetActiveInstalledImageDir();
static std::vector<std::string> GetInstalledDsuSlots();
+ GsiService();
private:
friend class ImageService;
- GsiService();
static int ValidateInstallParams(std::string& install_dir);
int EnableGsi(bool one_shot, const std::string& dsu_slot);
bool DisableGsiInstall();
diff --git a/gsi_tool.cpp b/gsi_tool.cpp
index 181452b..6aa0fb5 100644
--- a/gsi_tool.cpp
+++ b/gsi_tool.cpp
@@ -19,6 +19,7 @@
#include <sysexits.h>
#include <unistd.h>
+#include <algorithm>
#include <chrono>
#include <condition_variable>
#include <functional>
@@ -70,14 +71,50 @@ static const std::map<std::string, CommandCallback> kCommandMap = {
// clang-format on
};
+// Commands not allowed for locked DSU
+static const std::vector<std::string> kEnforceNonLockedDsu = {
+ // clang-format off
+ "disable",
+ "enable",
+ "wipe",
+ // clang-format on
+};
+
static std::string ErrorMessage(const android::binder::Status& status,
int error_code = IGsiService::INSTALL_ERROR_GENERIC) {
if (!status.isOk()) {
- return status.exceptionMessage().string();
+ return status.exceptionMessage().c_str();
}
return "error code " + std::to_string(error_code);
}
+static inline bool IsRoot() {
+ return getuid() == 0;
+}
+
+static int EnforceNonLockedDsu(sp<IGsiService> gsid) {
+ bool running;
+ auto status = gsid->isGsiRunning(&running);
+ if (!status.isOk()) {
+ std::cerr << "Could not get DSU running status: " << ErrorMessage(status) << std::endl;
+ return EX_SOFTWARE;
+ }
+ if (!running) {
+ return 0;
+ }
+ std::string dsuSlot = {};
+ status = gsid->getActiveDsuSlot(&dsuSlot);
+ if (!status.isOk()) {
+ std::cerr << "Could not get the active DSU slot: " << ErrorMessage(status) << std::endl;
+ return EX_SOFTWARE;
+ }
+ if (android::base::EndsWith(dsuSlot, ".lock") && !IsRoot()) {
+ std::cerr << "Must be root to access a locked DSU" << std::endl;
+ return EX_NOPERM;
+ }
+ return 0;
+}
+
class ProgressBar {
public:
explicit ProgressBar(sp<IGsiService> gsid) : gsid_(gsid) {}
@@ -210,7 +247,7 @@ static int Install(sp<IGsiService> gsid, int argc, char** argv) {
bool reboot = true;
std::string installDir = "";
std::string partition = kDefaultPartition;
- if (getuid() != 0) {
+ if (!IsRoot()) {
std::cerr << "must be root to install a GSI" << std::endl;
return EX_NOPERM;
}
@@ -376,7 +413,7 @@ static int CreatePartition(sp<IGsiService> gsid, int argc, char** argv) {
}
}
- if (getuid() != 0) {
+ if (!IsRoot()) {
std::cerr << "must be root to install a DSU" << std::endl;
return EX_NOPERM;
}
@@ -496,7 +533,7 @@ static int WipeData(sp<IGsiService> gsid, int argc, char** /* argv */) {
bool running;
auto status = gsid->isGsiRunning(&running);
if (!status.isOk()) {
- std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
+ std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
}
if (running) {
@@ -507,7 +544,7 @@ static int WipeData(sp<IGsiService> gsid, int argc, char** /* argv */) {
bool installed;
status = gsid->isGsiInstalled(&installed);
if (!status.isOk()) {
- std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
+ std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
}
if (!installed) {
@@ -532,7 +569,7 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) {
bool running;
auto status = gsid->isGsiRunning(&running);
if (!status.isOk()) {
- std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
+ std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
} else if (running) {
std::cout << "running" << std::endl;
@@ -540,7 +577,7 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) {
bool installed;
status = gsid->isGsiInstalled(&installed);
if (!status.isOk()) {
- std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
+ std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
} else if (installed) {
std::cout << "installed" << std::endl;
@@ -548,21 +585,21 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) {
bool enabled;
status = gsid->isGsiEnabled(&enabled);
if (!status.isOk()) {
- std::cerr << status.exceptionMessage().string() << std::endl;
+ std::cerr << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
} else if (running || installed) {
std::cout << (enabled ? "enabled" : "disabled") << std::endl;
} else {
std::cout << "normal" << std::endl;
}
- if (getuid() != 0) {
+ if (!IsRoot()) {
return 0;
}
std::vector<std::string> dsu_slots;
status = gsid->getInstalledDsuSlots(&dsu_slots);
if (!status.isOk()) {
- std::cerr << status.exceptionMessage().string() << std::endl;
+ std::cerr << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
}
int n = 0;
@@ -571,13 +608,18 @@ static int Status(sp<IGsiService> gsid, int argc, char** /* argv */) {
sp<IImageService> image_service = nullptr;
status = gsid->openImageService("dsu/" + dsu_slot + "/", &image_service);
if (!status.isOk()) {
- std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
+ if (running) {
+ // openImageService through binder (gsid) could fail if running,
+ // because we can't stat the "outside" userdata.
+ continue;
+ }
+ std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
}
std::vector<std::string> images;
status = image_service->getAllBackingImages(&images);
if (!status.isOk()) {
- std::cerr << "error: " << status.exceptionMessage().string() << std::endl;
+ std::cerr << "error: " << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
}
for (auto&& image : images) {
@@ -603,7 +645,7 @@ static int Cancel(sp<IGsiService> gsid, int /* argc */, char** /* argv */) {
bool cancelled = false;
auto status = gsid->cancelGsiInstall(&cancelled);
if (!status.isOk()) {
- std::cerr << status.exceptionMessage().string() << std::endl;
+ std::cerr << status.exceptionMessage().c_str() << std::endl;
return EX_SOFTWARE;
}
if (!cancelled) {
@@ -671,7 +713,6 @@ static int Disable(sp<IGsiService> gsid, int argc, char** /* argv */) {
std::cerr << "Unrecognized arguments to disable." << std::endl;
return EX_USAGE;
}
-
bool installing = false;
gsid->isGsiInstallInProgress(&installing);
if (installing) {
@@ -729,12 +770,19 @@ int main(int argc, char** argv) {
std::string command = argv[1];
+ int rc;
+ const auto& vec = kEnforceNonLockedDsu;
+ if (std::find(vec.begin(), vec.end(), command) != vec.end() &&
+ (rc = EnforceNonLockedDsu(service)) != 0) {
+ return rc;
+ }
+
auto iter = kCommandMap.find(command);
if (iter == kCommandMap.end()) {
std::cerr << "Unrecognized command: " << command << std::endl;
return usage(argc, argv);
}
- int rc = iter->second(service, argc - 1, argv + 1);
+ rc = iter->second(service, argc - 1, argv + 1);
return rc;
}
diff --git a/include/libgsi/libgsi.h b/include/libgsi/libgsi.h
index 5318341..969ea28 100644
--- a/include/libgsi/libgsi.h
+++ b/include/libgsi/libgsi.h
@@ -76,6 +76,8 @@ static inline std::string GetDsuMetadataKeyDir(const std::string& dsu_slot) {
// install_dir "/data/gsi/dsu/dsu2" has a slot name "dsu2"
std::string GetDsuSlot(const std::string& install_dir);
+static constexpr char kDsuSlotProp[] = "ro.gsid.dsu_slot";
+
static constexpr char kGsiBootedProp[] = "ro.gsid.image_running";
static constexpr char kGsiInstalledProp[] = "gsid.image_installed";
diff --git a/libgsid.cpp b/libgsid.cpp
index 4689dcd..fc75c90 100644
--- a/libgsid.cpp
+++ b/libgsid.cpp
@@ -27,7 +27,7 @@ using android::sp;
sp<IGsiService> GetGsiService() {
auto sm = android::defaultServiceManager();
auto name = android::String16(kGsiServiceName);
- android::sp<android::IBinder> res = sm->waitForService(name);
+ static android::sp<android::IBinder> res = sm->waitForService(name);
if (res) {
return android::interface_cast<IGsiService>(res);
}
diff --git a/partition_installer.cpp b/partition_installer.cpp
index ebd3b64..ebfb329 100644
--- a/partition_installer.cpp
+++ b/partition_installer.cpp
@@ -256,6 +256,9 @@ bool PartitionInstaller::CommitGsiChunk(const void* data, size_t bytes) {
}
int PartitionInstaller::GetPartitionFd() {
+ if (!system_device_) {
+ return -1;
+ }
return system_device_->fd();
}
diff --git a/tests/Android.bp b/tests/Android.bp
index 3035c10..e7e662c 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -97,3 +97,20 @@ java_test_host {
"general-tests",
],
}
+
+java_test_host {
+ name: "DsuGsiToolTest",
+ srcs: [
+ "DsuGsiToolTest.java",
+ ],
+ static_libs: [
+ "DsuTestBase",
+ ],
+ libs: [
+ "tradefed",
+ ],
+ test_config: "dsu_gsi_tool_test.xml",
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/tests/DSUEndtoEndTest.java b/tests/DSUEndtoEndTest.java
index 79799bf..8179d59 100644
--- a/tests/DSUEndtoEndTest.java
+++ b/tests/DSUEndtoEndTest.java
@@ -21,16 +21,20 @@ import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.SparseImageUtil;
+import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.ZipUtil2;
import org.apache.commons.compress.archivers.zip.ZipFile;
+import org.junit.Before;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
@@ -40,7 +44,6 @@ import java.util.concurrent.TimeUnit;
public class DSUEndtoEndTest extends DsuTestBase {
private static final long kDefaultUserdataSize = 4L * 1024 * 1024 * 1024;
private static final String LPUNPACK_PATH = "bin/lpunpack";
- private static final String SIMG2IMG_PATH = "bin/simg2img";
// Example: atest -v DSUEndtoEndTest -- --test-arg \
// com.android.tradefed.testtype.HostTest:set-option:system_image_path:/full/path/to/system.img
@@ -51,56 +54,83 @@ public class DSUEndtoEndTest extends DsuTestBase {
importance=Importance.ALWAYS)
private String mSystemImagePath;
- private File mUnsparseSystemImage;
+ private File mTempDir;
- @After
- public void teardown() throws Exception {
- if (mUnsparseSystemImage != null) {
- mUnsparseSystemImage.delete();
+ private File getTempPath(String relativePath) throws IOException {
+ if (mTempDir == null) {
+ mTempDir = FileUtil.createTempDir("DSUEndtoEndTest");
}
+ return new File(mTempDir, relativePath);
}
- @Test
- public void testDSU() throws Exception {
- String simg2imgPath = "simg2img";
- if (mSystemImagePath == null) {
- IBuildInfo buildInfo = getBuild();
- File imgs = ((IDeviceBuildInfo) buildInfo).getDeviceImageFile();
- Assert.assertNotEquals("Failed to fetch system image. See system_image_path parameter", null, imgs);
- File otaTools = buildInfo.getFile("otatools.zip");
- File tempdir = ZipUtil2.extractZipToTemp(otaTools, "otatools");
- File system = ZipUtil2.extractFileFromZip(new ZipFile(imgs), "system.img");
- if (system == null) {
- File superImg = ZipUtil2.extractFileFromZip(new ZipFile(imgs), "super.img");
- String lpunpackPath = new File(tempdir, LPUNPACK_PATH).getAbsolutePath();
- String outputDir = superImg.getParentFile().getAbsolutePath();
- String[] cmd = {lpunpackPath, "-p", "system_a", superImg.getAbsolutePath(), outputDir};
- Process p = Runtime.getRuntime().exec(cmd);
- p.waitFor();
- if (p.exitValue() == 0) {
- mSystemImagePath = new File(outputDir, "system_a.img").getAbsolutePath();
- } else {
- ByteArrayOutputStream stderr = new ByteArrayOutputStream();
- int len;
- byte[] buf = new byte[1024];
- while ((len = p.getErrorStream().read(buf)) != -1) {
- stderr.write(buf, 0, len);
- }
- Assert.assertEquals("non-zero exit value (" + stderr.toString("UTF-8") + ")", 0, p.exitValue());
- }
- } else {
- mSystemImagePath = system.getAbsolutePath();
+ /** Extract system.img from build info to a temproary file. */
+ private File extractSystemImageFromBuildInfo(IBuildInfo buildInfo)
+ throws IOException, InterruptedException {
+ File imgZip = ((IDeviceBuildInfo) buildInfo).getDeviceImageFile();
+ Assert.assertNotNull(
+ "Failed to fetch system image. See system_image_path parameter", imgZip);
+
+ File superImg = getTempPath("super.img");
+ try (ZipFile zip = new ZipFile(imgZip)) {
+ File systemImg = getTempPath("system.img");
+ if (ZipUtil2.extractFileFromZip(zip, "system.img", systemImg)) {
+ return systemImg;
}
- simg2imgPath = new File(tempdir, SIMG2IMG_PATH).getAbsolutePath();
+ Assert.assertTrue(
+ "No system.img or super.img in img zip.",
+ ZipUtil2.extractFileFromZip(zip, "super.img", superImg));
+ }
+
+ if (SparseImageUtil.isSparse(superImg)) {
+ File unsparseSuperImage = getTempPath("super.raw");
+ SparseImageUtil.unsparse(superImg, unsparseSuperImage);
+ superImg = unsparseSuperImage;
}
- File gsi = new File(mSystemImagePath);
- Assert.assertTrue("not a valid file", gsi.isFile());
- String[] cmd = {simg2imgPath, mSystemImagePath, mSystemImagePath + ".raw"};
+
+ File otaTools = buildInfo.getFile("otatools.zip");
+ Assert.assertNotNull("No otatools.zip in BuildInfo.", otaTools);
+ File otaToolsDir = getTempPath("otatools");
+ ZipUtil2.extractZip(otaTools, otaToolsDir);
+
+ String lpunpackPath = new File(otaToolsDir, LPUNPACK_PATH).getAbsolutePath();
+ File outputDir = getTempPath("lpunpack_output");
+ outputDir.mkdirs();
+ String[] cmd = {
+ lpunpackPath, "-p", "system_a", superImg.getAbsolutePath(), outputDir.getAbsolutePath()
+ };
Process p = Runtime.getRuntime().exec(cmd);
p.waitFor();
- if (p.exitValue() == 0) {
- mUnsparseSystemImage = new File(mSystemImagePath + ".raw");
- gsi = mUnsparseSystemImage;
+ if (p.exitValue() != 0) {
+ String stderr = StreamUtil.getStringFromStream(p.getErrorStream());
+ Assert.fail(String.format("lpunpack returned %d. (%s)", p.exitValue(), stderr));
+ }
+ return new File(outputDir, "system_a.img");
+ }
+
+ @Before
+ public void setUp() {
+ mTempDir = null;
+ }
+
+ @After
+ public void tearDown() {
+ FileUtil.recursiveDelete(mTempDir);
+ }
+
+ @Test
+ public void testDSU() throws Exception {
+ File systemImage;
+ if (mSystemImagePath != null) {
+ systemImage = new File(mSystemImagePath);
+ } else {
+ systemImage = extractSystemImageFromBuildInfo(getBuild());
+ }
+ Assert.assertTrue("not a valid file", systemImage.isFile());
+
+ if (SparseImageUtil.isSparse(systemImage)) {
+ File unsparseSystemImage = getTempPath("system.raw");
+ SparseImageUtil.unsparse(systemImage, unsparseSystemImage);
+ systemImage = unsparseSystemImage;
}
boolean wasRoot = getDevice().isAdbRoot();
@@ -117,8 +147,8 @@ public class DSUEndtoEndTest extends DsuTestBase {
"gsi_tool install --userdata-size %d"
+ " --gsi-size %d"
+ " && sleep 10000000",
- getDsuUserdataSize(kDefaultUserdataSize), gsi.length()),
- gsi,
+ getDsuUserdataSize(kDefaultUserdataSize), systemImage.length()),
+ systemImage,
null,
10,
TimeUnit.MINUTES,
diff --git a/tests/DsuGsiIntegrationTest.java b/tests/DsuGsiIntegrationTest.java
index 78d001c..bb0014a 100644
--- a/tests/DsuGsiIntegrationTest.java
+++ b/tests/DsuGsiIntegrationTest.java
@@ -122,6 +122,7 @@ public class DsuGsiIntegrationTest extends DsuTestBase {
getDevice().executeShellV2Command("gsi_tool wipe");
if (isDsuRunning()) {
getDevice().reboot();
+ getDevice().disableAdbRoot();
}
} catch (DeviceNotAvailableException e) {
CLog.w("Failed to clean up DSU installation on device: %s", e);
@@ -140,6 +141,7 @@ public class DsuGsiIntegrationTest extends DsuTestBase {
CLog.i("Wipe existing DSU installation");
assertShellCommand("gsi_tool wipe");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuNotRunning();
}
@@ -160,6 +162,7 @@ public class DsuGsiIntegrationTest extends DsuTestBase {
CLog.i("Test 'gsi_tool enable -s' and 'gsi_tool enable'");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuNotRunning();
final long freeSpaceAfterInstall = getDevice().getPartitionFreeSpace("/data") << 10;
@@ -173,12 +176,14 @@ public class DsuGsiIntegrationTest extends DsuTestBase {
assertShellCommand("gsi_tool enable");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuRunning();
CLog.i("Set up 'adb remount' for testing (requires reboot)");
assertAdbRoot();
assertShellCommand("remount");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuRunning();
assertAdbRoot();
assertShellCommand("remount");
@@ -190,12 +195,14 @@ public class DsuGsiIntegrationTest extends DsuTestBase {
CLog.i("DSU and original system have separate remount overlays");
assertShellCommand("gsi_tool disable");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuNotRunning();
assertDevicePathNotExist(REMOUNT_TEST_PATH);
CLog.i("Test that 'adb remount' is consistent after reboot");
assertShellCommand("gsi_tool enable");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuRunning();
assertDevicePathExist(REMOUNT_TEST_FILE);
assertEquals(
@@ -208,12 +215,14 @@ public class DsuGsiIntegrationTest extends DsuTestBase {
assertAdbRoot();
assertShellCommand("enable-verity");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuRunning();
assertDevicePathNotExist(REMOUNT_TEST_PATH);
CLog.i("Test 'gsi_tool wipe'");
assertShellCommand("gsi_tool wipe");
getDevice().reboot();
+ getDevice().disableAdbRoot();
assertDsuNotRunning();
final double dampeningCoefficient = 0.9;
diff --git a/tests/DsuGsiToolTest.java b/tests/DsuGsiToolTest.java
new file mode 100644
index 0000000..c6e621a
--- /dev/null
+++ b/tests/DsuGsiToolTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 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.dsu;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class DsuGsiToolTest extends DsuTestBase {
+ private static final long DSU_MAX_WAIT_SEC = 10 * 60;
+
+ private String getDsuInstallCommand(String slotName) {
+ return String.format("am start-activity"
+ + " -n com.android.dynsystem/com.android.dynsystem.VerificationActivity"
+ + " -a android.os.image.action.START_INSTALL"
+ + " --el KEY_USERDATA_SIZE 2147483648"
+ + " --ez KEY_ENABLE_WHEN_COMPLETED true"
+ + " --es KEY_DSU_SLOT %s", slotName);
+ }
+
+ @Before
+ public void setUp() throws DeviceNotAvailableException {
+ if (isDsuRunning()) {
+ assertAdbRoot();
+ CLog.i("Wipe existing DSU installation");
+ assertShellCommand("gsi_tool wipe");
+ getDevice().reboot();
+ assertDsuNotRunning();
+ }
+ getDevice().disableAdbRoot();
+ }
+
+ @After
+ public void tearDown() throws DeviceNotAvailableException {
+ if (isDsuRunning()) {
+ getDevice().reboot();
+ }
+ getDevice().executeShellCommand("gsi_tool wipe");
+ }
+
+ @Test
+ public void testNonLockedDsu() throws DeviceNotAvailableException {
+ final String slotName = "foo";
+ assertShellCommand(getDsuInstallCommand(slotName));
+ CLog.i("Wait for DSU installation complete and reboot");
+ assertTrue(
+ "Timed out waiting for DSU installation complete",
+ getDevice().waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000));
+ CLog.i("DSU installation is complete and device is disconnected");
+
+ getDevice().waitForDeviceAvailable();
+ assertDsuRunning();
+ CLog.i("Successfully booted with DSU");
+
+ // These commands should run without any error
+ assertShellCommand("gsi_tool enable");
+ assertShellCommand("gsi_tool disable");
+ assertShellCommand("gsi_tool wipe");
+ }
+
+ @Test
+ public void testLockedDsu() throws DeviceNotAvailableException {
+ final String slotName = "foo.lock";
+ assertShellCommand(getDsuInstallCommand(slotName));
+ CLog.i("Wait for DSU installation complete and reboot");
+ assertTrue(
+ "Timed out waiting for DSU installation complete",
+ getDevice().waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000));
+ CLog.i("DSU installation is complete and device is disconnected");
+
+ getDevice().waitForDeviceAvailable();
+ assertDsuRunning();
+ CLog.i("Successfully booted with DSU");
+
+ // These commands should fail on a locked DSU
+ var result = getDevice().executeShellV2Command("gsi_tool enable");
+ assertFalse(result.getStatus() == CommandStatus.SUCCESS);
+ result = getDevice().executeShellV2Command("gsi_tool disable");
+ assertFalse(result.getStatus() == CommandStatus.SUCCESS);
+ result = getDevice().executeShellV2Command("gsi_tool wipe");
+ assertFalse(result.getStatus() == CommandStatus.SUCCESS);
+ }
+}
diff --git a/tests/dsu_gsi_tool_test.xml b/tests/dsu_gsi_tool_test.xml
new file mode 100644
index 0000000..d893902
--- /dev/null
+++ b/tests/dsu_gsi_tool_test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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 DsuGsiToolTest">
+ <option name="test-suite-tag" value="dsu-gsi-tool-test" />
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.tests.dsu.DsuGsiToolTest" />
+ </test>
+</configuration>