summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-02 00:08:25 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-02 00:08:25 +0000
commitedaa34263b850e15c4b844ebea33b65528b7f62f (patch)
tree35483a3e16fa3dc5171d328d853191ecb59b0e1d
parent67449b327db1b2609d70c1c8688217cc5d433ab4 (diff)
parentdcf4a961732046efca31b5480ece3af86fde1978 (diff)
downloadplatform_testing-edaa34263b850e15c4b844ebea33b65528b7f62f.tar.gz
Snap for 8237733 from dcf4a961732046efca31b5480ece3af86fde1978 to sc-qpr3-release
Change-Id: I244cc5d6008c8e0af32a6990888b3f478286f8a5
-rw-r--r--libraries/sts-common-util/host-side/Android.bp1
-rw-r--r--libraries/sts-common-util/host-side/rootcanal/Android.bp11
-rw-r--r--libraries/sts-common-util/host-side/rootcanal/android.hardware.bluetooth@1.1-service.sim.rc4
-rw-r--r--libraries/sts-common-util/host-side/rootcanal/empty.sh0
-rw-r--r--libraries/sts-common-util/host-side/src/com/android/sts/common/OverlayFsUtils.java75
-rw-r--r--libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java268
6 files changed, 347 insertions, 12 deletions
diff --git a/libraries/sts-common-util/host-side/Android.bp b/libraries/sts-common-util/host-side/Android.bp
index 49f51f4d7..f151cd374 100644
--- a/libraries/sts-common-util/host-side/Android.bp
+++ b/libraries/sts-common-util/host-side/Android.bp
@@ -28,6 +28,7 @@ java_library_host {
libs: [
"compatibility-tradefed",
+ "guava",
"tradefed",
],
}
diff --git a/libraries/sts-common-util/host-side/rootcanal/Android.bp b/libraries/sts-common-util/host-side/rootcanal/Android.bp
new file mode 100644
index 000000000..a5b7495d0
--- /dev/null
+++ b/libraries/sts-common-util/host-side/rootcanal/Android.bp
@@ -0,0 +1,11 @@
+sh_test {
+ name: "sts-rootcanal-sidebins",
+ src: "empty.sh",
+ test_suites: ["sts"],
+ data_bins: [
+ "android.hardware.bluetooth@1.1-service.sim",
+ "android.hardware.bluetooth@1.1-impl-sim"
+ ],
+ data: ["android.hardware.bluetooth@1.1-service.sim.rc"],
+}
+
diff --git a/libraries/sts-common-util/host-side/rootcanal/android.hardware.bluetooth@1.1-service.sim.rc b/libraries/sts-common-util/host-side/rootcanal/android.hardware.bluetooth@1.1-service.sim.rc
new file mode 100644
index 000000000..262684136
--- /dev/null
+++ b/libraries/sts-common-util/host-side/rootcanal/android.hardware.bluetooth@1.1-service.sim.rc
@@ -0,0 +1,4 @@
+service vendor.bluetooth-1-1 /vendor/bin/hw/android.hardware.bluetooth@1.1-service.sim
+ class hal
+ user bluetooth
+ group bluetooth
diff --git a/libraries/sts-common-util/host-side/rootcanal/empty.sh b/libraries/sts-common-util/host-side/rootcanal/empty.sh
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/libraries/sts-common-util/host-side/rootcanal/empty.sh
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/OverlayFsUtils.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/OverlayFsUtils.java
index 816420f49..a21ce577b 100644
--- a/libraries/sts-common-util/host-side/src/com/android/sts/common/OverlayFsUtils.java
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/OverlayFsUtils.java
@@ -17,38 +17,68 @@
package com.android.sts.common;
import static org.junit.Assert.assertTrue;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
+import static org.junit.Assert.assertNotNull;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.google.common.hash.Hashing;
+
+/** TestWatcher that enables writing to read-only partitions and reboots device when done. */
+public class OverlayFsUtils extends TestWatcher {
+ private static final String OVERLAYFS_PREFIX = "overlay_sts_";
+
+ private final BaseHostJUnit4Test test;
-public class OverlayFsUtils {
// output of `stat`, e.g. "root shell 755 u:object_r:vendor_file:s0"
static final Pattern PERM_PATTERN =
Pattern.compile(
"^(?<user>[a-zA-Z0-9_-]+) (?<group>[a-zA-Z0-9_-]+) (?<perm>[0-7]+)"
+ " (?<secontext>.*)$");
+ public OverlayFsUtils(BaseHostJUnit4Test test) {
+ assertNotNull("Need to pass in a valid testcase object.", test);
+ this.test = test;
+ }
+
/**
* Mounts an OverlayFS dir over the top most common dir in the list.
*
* <p>The directory should be writable after this returns successfully. To cleanup, reboot the
* device as unfortunately unmounting overlayfs is complicated.
*
- * @param device The test device to setup overlayfs for.
* @param dir The directory to make writable. Directories with single quotes are not supported.
*/
- public static void makeWritable(ITestDevice device, String dir)
- throws DeviceNotAvailableException, IOException {
- // TODO(duytruong): This should ideally be made into a TestRule that also handles cleanups
- // However, test devices initiation is done in one of the @Before, after a rule's setup.
+ public void makeWritable(final String dir)
+ throws DeviceNotAvailableException, IOException, IllegalStateException {
+ ITestDevice device = test.getDevice();
+ assertNotNull("device not set.", device);
+ assertTrue("dir needs to be an absolute path.", dir.startsWith("/"));
+
+ // Check and make sure we have not already mounted over this dir. We do that by hashing
+ // the lower dir path and put that as part of the device ID for `mount`.
+ String dirHash = Hashing.md5().hashString(dir, StandardCharsets.UTF_8).toString();
+ String id = OVERLAYFS_PREFIX + dirHash;
+ CommandResult res = device.executeShellV2Command("mount | grep -q " + id);
+ if (res.getStatus() == CommandStatus.SUCCESS) {
+ // a mount with the same ID already exists
+ throw new IllegalStateException(dir + " has already been made writable.");
+ }
+
assertTrue("Can't acquire root for " + device.getSerialNumber(), device.enableAdbRoot());
+ // Match permissions of upper dir to lower dir
String statOut =
CommandUtil.runAndCheck(device, "stat -c '%U %G %a %C' '" + dir + "'").getStdout();
Matcher m = PERM_PATTERN.matcher(statOut);
@@ -58,7 +88,7 @@ public class OverlayFsUtils {
String unixPerm = m.group("perm");
String seContext = m.group("secontext");
- Path tempdir = Paths.get("/mnt", "stsoverlayfs", dir);
+ Path tempdir = Paths.get("/mnt", "stsoverlayfs", id);
String upperdir = tempdir.resolve("upper").toString();
String workdir = tempdir.resolve("workdir").toString();
@@ -69,8 +99,29 @@ public class OverlayFsUtils {
String mountCmd =
String.format(
- "mount -t overlay overlay -o lowerdir='%s',upperdir='%s',workdir='%s' '%s'",
- dir, upperdir, workdir, dir);
+ "mount -t overlay '%s' -o lowerdir='%s',upperdir='%s',workdir='%s' '%s'",
+ id, dir, upperdir, workdir, dir);
CommandUtil.runAndCheck(device, mountCmd);
}
+
+ public boolean anyOverlayFsMounted() throws DeviceNotAvailableException {
+ ITestDevice device = test.getDevice();
+ assertNotNull("Device not set", device);
+ CommandResult res = device.executeShellV2Command("mount | grep -q " + OVERLAYFS_PREFIX);
+ return res.getStatus() == CommandStatus.SUCCESS;
+ }
+
+ @Override
+ public void finished(Description d) {
+ ITestDevice device = test.getDevice();
+ assertNotNull("Device not set", device);
+ try {
+ if (anyOverlayFsMounted()) {
+ device.rebootUntilOnline();
+ device.waitForDeviceAvailable();
+ }
+ } catch (DeviceNotAvailableException e) {
+ throw new AssertionError("Device unavailable when cleaning up", e);
+ }
+ }
}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java
new file mode 100644
index 000000000..d3d23c30c
--- /dev/null
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/RootcanalUtils.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2022 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.sts.common;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathExpressionException;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.rules.TestWatcher;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.RunUtil;
+
+/** TestWatcher that sets up a virtual bluetooth HAL and reboots the device once done. */
+public class RootcanalUtils extends TestWatcher {
+ private static final String LOCK_FILENAME = "/mnt/sts_rootcanal.lck";
+
+ private BaseHostJUnit4Test test;
+
+ public RootcanalUtils(BaseHostJUnit4Test test) {
+ assertNotNull(test);
+ this.test = test;
+ }
+
+ @Override
+ public void finished(Description d) {
+ ITestDevice device = test.getDevice();
+ assertNotNull("Device not set", device);
+ try {
+ // No need to call OverlayFsUtils' finished() because we're restarting anyway
+ device.rebootUntilOnline();
+ device.waitForDeviceAvailable();
+ } catch (DeviceNotAvailableException e) {
+ throw new AssertionError("Device unavailable when cleaning up", e);
+ }
+ }
+
+ /** Replace existing HAL with RootCanal HAL on current device. */
+ public void enableRootcanal()
+ throws DeviceNotAvailableException, IOException, InterruptedException,
+ TimeoutException {
+ enableRootcanal(6111);
+ }
+
+ /**
+ * Replace existing HAL with RootCanal HAL on current device.
+ *
+ * @param port host TCP port to adb-forward to rootcanal control port.
+ */
+ public void enableRootcanal(int port)
+ throws DeviceNotAvailableException, IOException, InterruptedException,
+ TimeoutException {
+ ITestDevice device = test.getDevice();
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(test.getBuild());
+ assertNotNull("Device not set", device);
+ assertNotNull("Build not set", buildHelper);
+
+ // Check and made sure we're not calling this more than once for a device
+ assertFalse("rootcanal set up called more than once", device.doesFileExist(LOCK_FILENAME));
+ device.pushString("", LOCK_FILENAME);
+
+ // Make sure that /vendor is writable
+ try {
+ new OverlayFsUtils(test).makeWritable("/vendor");
+ } catch (IllegalStateException e) {
+ CLog.w(e);
+ }
+
+ // Remove existing HAL files and push new virtual HAL files.
+ CommandUtil.runAndCheck(device, "svc bluetooth disable");
+ CommandUtil.runAndCheck(
+ device,
+ "rm /vendor/lib64/hw/android.hardware.bluetooth@* "
+ + "/vendor/lib/hw/android.hardware.bluetooth@* "
+ + "/vendor/bin/hw/android.hardware.bluetooth@* "
+ + "/vendor/etc/init/android.hardware.bluetooth@*");
+
+ device.pushFile(
+ buildHelper.getTestFile("android.hardware.bluetooth@1.1-service.sim"),
+ "/vendor/bin/hw/android.hardware.bluetooth@1.1-service.sim");
+
+ // Pushing the same lib to both 32 and 64bit lib dirs because (a) it works and
+ // (b) FileUtil does not yet support "arm/lib" and "arm64/lib64" layout.
+ device.pushFile(
+ buildHelper.getTestFile("android.hardware.bluetooth@1.1-impl-sim.so"),
+ "/vendor/lib/hw/android.hardware.bluetooth@1.1-impl-sim.so");
+ device.pushFile(
+ buildHelper.getTestFile("android.hardware.bluetooth@1.1-impl-sim.so"),
+ "/vendor/lib64/hw/android.hardware.bluetooth@1.1-impl-sim.so");
+ device.pushFile(
+ buildHelper.getTestFile("android.hardware.bluetooth@1.1-service.sim.rc"),
+ "/vendor/etc/init/android.hardware.bluetooth@1.1-service.sim.rc");
+
+ // Download and patch the VINTF manifest if needed.
+ tryUpdateVintfManifest(device);
+
+ // Fix up permissions and SELinux contexts of files pushed over
+ CommandUtil.runAndCheck(device, "cp /system/lib64/libchrome.so /vendor/lib64/libchrome.so");
+ CommandUtil.runAndCheck(
+ device, "chmod 755 /vendor/bin/hw/android.hardware.bluetooth@1.1-service.sim");
+ CommandUtil.runAndCheck(
+ device,
+ "chcon u:object_r:hal_bluetooth_default_exec:s0 "
+ + "/vendor/bin/hw/android.hardware.bluetooth@1.1-service.sim");
+ CommandUtil.runAndCheck(
+ device,
+ "chmod 644 "
+ + "/vendor/etc/vintf/manifest.xml "
+ + "/vendor/lib/hw/android.hardware.bluetooth@1.1-impl-sim.so "
+ + "/vendor/lib64/hw/android.hardware.bluetooth@1.1-impl-sim.so");
+ CommandUtil.runAndCheck(
+ device, "chcon u:object_r:vendor_configs_file:s0 /vendor/etc/vintf/manifest.xml");
+ CommandUtil.runAndCheck(
+ device,
+ "chcon u:object_r:vendor_file:s0 "
+ + "/vendor/lib/hw/android.hardware.bluetooth@1.1-impl-sim.so "
+ + "/vendor/lib64/hw/android.hardware.bluetooth@1.1-impl-sim.so");
+
+ // Kill currently running BT HAL.
+ if (ProcessUtil.killAll(device, "android\\.hardware\\.bluetooth@.*", 10_000, false)) {
+ CLog.d("Killed existing BT HAL");
+ } else {
+ CLog.w("No existing BT HAL was found running");
+ }
+
+ // Kill hwservicemanager, wait for it to come back up on its own, and wait for it
+ // to finish initializing. This is needed to reload the VINTF and HAL rc information.
+ // Note that a userspace reboot would not work here because hwservicemanager starts
+ // before userdata is mounted.
+ device.setProperty("hwservicemanager.ready", "false");
+ ProcessUtil.killAll(device, "hwservicemanager$", 10_000);
+ waitPropertyValue(device, "hwservicemanager.ready", "true", 10_000);
+ TimeUnit.SECONDS.sleep(30);
+
+ // Launch the new HAL
+ List<String> cmd =
+ List.of(
+ "adb",
+ "-s",
+ device.getSerialNumber(),
+ "shell",
+ "/vendor/bin/hw/android.hardware.bluetooth@1.1-service.sim");
+ RunUtil.getDefault().runCmdInBackground(cmd);
+ ProcessUtil.waitProcessRunning(
+ device, "android\\.hardware\\.bluetooth@1\\.1-service\\.sim", 10_000);
+
+ // Reenable Bluetooth and enable RootCanal control channel
+ String checkCmd = "netstat -l -t -n -W | grep '0\\.0\\.0\\.0:6111'";
+ while (true) {
+ CommandUtil.runAndCheck(device, "svc bluetooth enable");
+ CommandUtil.runAndCheck(device, "setprop vendor.bt.rootcanal_test_console true");
+ CommandResult res = device.executeShellV2Command(checkCmd);
+ if (res.getStatus() == CommandStatus.SUCCESS) {
+ break;
+ }
+ }
+
+ device.executeAdbCommand("forward", "tcp:6111", String.format("tcp:%d", port));
+ }
+
+ private void tryUpdateVintfManifest(ITestDevice device)
+ throws DeviceNotAvailableException, IOException {
+ try {
+ String vintfManifest = device.pullFileContents("/vendor/etc/vintf/manifest.xml");
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(new InputSource(new StringReader(vintfManifest)));
+ String XPATH = "/manifest/hal[name=\"android.hardware.bluetooth\"][version!=\"1.1\"]";
+ Node node =
+ (Node)
+ XPathFactory.newInstance()
+ .newXPath()
+ .evaluate(XPATH, doc, XPathConstants.NODE);
+ if (node != null) {
+ Node versionNode =
+ (Node)
+ XPathFactory.newInstance()
+ .newXPath()
+ .evaluate("version", node, XPathConstants.NODE);
+ versionNode.setTextContent("1.1");
+
+ Node fqnameNode =
+ (Node)
+ XPathFactory.newInstance()
+ .newXPath()
+ .evaluate("fqname", node, XPathConstants.NODE);
+ String newFqname =
+ fqnameNode.getTextContent().replaceAll("@[0-9]+\\.[0-9]+(::.*)", "@1.1$1");
+ fqnameNode.setTextContent(newFqname);
+
+ File outFile = File.createTempFile("stsrootcanal", null);
+ outFile.deleteOnExit();
+
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ DOMSource source = new DOMSource(doc);
+ StreamResult result = new StreamResult(new FileWriter(outFile));
+ transformer.transform(source, result);
+ device.pushFile(outFile, "/vendor/etc/vintf/manifest.xml");
+ CLog.d("Updated VINTF manifest");
+ } else {
+ CLog.d("Not updating VINTF manifest");
+ }
+ } catch (ParserConfigurationException
+ | SAXException
+ | XPathExpressionException
+ | TransformerException e) {
+ CLog.e("Could not parse vintf manifest: %s", e);
+ }
+ }
+
+ /** Spin wait until given property has given value. */
+ private void waitPropertyValue(ITestDevice device, String name, String value, long timeoutMs)
+ throws TimeoutException, DeviceNotAvailableException, InterruptedException {
+ long endTime = System.currentTimeMillis() + timeoutMs;
+ while (true) {
+ if (value.equals(device.getProperty(name))) {
+ return;
+ }
+ if (System.currentTimeMillis() > endTime) {
+ throw new TimeoutException();
+ }
+ TimeUnit.MILLISECONDS.sleep(250);
+ }
+ }
+}