Merge "Provided a solution to sort partitions from order in /product/overlay/partition_order.xml, added a debugging command to dump the partition order and how it get established." into main

This commit is contained in:
Brandon Liu
2023-07-17 17:56:20 +00:00
committed by Gerrit Code Review
7 changed files with 316 additions and 3 deletions

View File

@@ -190,4 +190,15 @@ interface IOverlayManager {
* @throws SecurityException if the transaction failed * @throws SecurityException if the transaction failed
*/ */
void commit(in OverlayManagerTransaction transaction); void commit(in OverlayManagerTransaction transaction);
/**
* Returns a String of a list of partitions from low priority to high.
*/
String getPartitionOrder();
/**
* Returns a boolean which represent whether the partition list is sorted by default.
* If not then it should be sorted by /product/overlay/partition_order.xml.
*/
boolean isDefaultPartitionOrder();
} }

View File

@@ -34,8 +34,15 @@ import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import com.android.internal.util.function.TriConsumer; import com.android.internal.util.function.TriConsumer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -46,6 +53,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/** /**
* Responsible for reading overlay configuration files and handling queries of overlay mutability, * Responsible for reading overlay configuration files and handling queries of overlay mutability,
* default-enabled state, and priority. * default-enabled state, and priority.
@@ -61,6 +72,8 @@ public class OverlayConfig {
@VisibleForTesting @VisibleForTesting
public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE; public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
public static final String PARTITION_ORDER_FILE_PATH = "/product/overlay/partition_order.xml";
@VisibleForTesting @VisibleForTesting
public static final class Configuration { public static final class Configuration {
@Nullable @Nullable
@@ -119,6 +132,10 @@ public class OverlayConfig {
// Singleton instance only assigned in system server // Singleton instance only assigned in system server
private static OverlayConfig sInstance; private static OverlayConfig sInstance;
private final String mPartitionOrder;
private final boolean mIsDefaultPartitionOrder;
@VisibleForTesting @VisibleForTesting
public OverlayConfig(@Nullable File rootDirectory, public OverlayConfig(@Nullable File rootDirectory,
@Nullable Supplier<OverlayScanner> scannerFactory, @Nullable Supplier<OverlayScanner> scannerFactory,
@@ -137,6 +154,8 @@ public class OverlayConfig {
new File(rootDirectory, p.getNonConicalFolder().getPath()), new File(rootDirectory, p.getNonConicalFolder().getPath()),
p))); p)));
} }
mIsDefaultPartitionOrder = !sortPartitions(PARTITION_ORDER_FILE_PATH, partitions);
mPartitionOrder = generatePartitionOrderString(partitions);
ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions); ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions);
@@ -198,6 +217,96 @@ public class OverlayConfig {
} }
} }
private static String generatePartitionOrderString(List<OverlayPartition> partitions) {
if (partitions == null || partitions.size() == 0) {
return "";
}
StringBuilder partitionOrder = new StringBuilder();
partitionOrder.append(partitions.get(0).getName());
for (int i = 1; i < partitions.size(); i++) {
partitionOrder.append(", ").append(partitions.get(i).getName());
}
return partitionOrder.toString();
}
private static boolean parseAndValidatePartitionsOrderXml(String partitionOrderFilePath,
Map<String, Integer> orderMap, List<OverlayPartition> partitions) {
try {
File file = new File(partitionOrderFilePath);
if (!file.exists()) {
Log.w(TAG, "partition_order.xml does not exist.");
return false;
}
var dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(file);
doc.getDocumentElement().normalize();
Element root = doc.getDocumentElement();
if (!root.getNodeName().equals("partition-order")) {
Log.w(TAG, "Invalid partition_order.xml, "
+ "xml root element is not partition-order");
return false;
}
NodeList partitionList = doc.getElementsByTagName("partition");
for (int order = 0; order < partitionList.getLength(); order++) {
Node partitionNode = partitionList.item(order);
if (partitionNode.getNodeType() == Node.ELEMENT_NODE) {
Element partitionElement = (Element) partitionNode;
String partitionName = partitionElement.getAttribute("name");
if (orderMap.containsKey(partitionName)) {
Log.w(TAG, "Invalid partition_order.xml, "
+ "it has duplicate partition: " + partitionName);
return false;
}
orderMap.put(partitionName, order);
}
}
if (orderMap.keySet().size() != partitions.size()) {
Log.w(TAG, "Invalid partition_order.xml, partition_order.xml has "
+ orderMap.keySet().size() + " partitions, "
+ "which is different from SYSTEM_PARTITIONS");
return false;
}
for (int i = 0; i < partitions.size(); i++) {
if (!orderMap.keySet().contains(partitions.get(i).getName())) {
Log.w(TAG, "Invalid Parsing partition_order.xml, "
+ "partition_order.xml does not have partition: "
+ partitions.get(i).getName());
return false;
}
}
} catch (ParserConfigurationException | SAXException | IOException e) {
Log.w(TAG, "Parsing or validating partition_order.xml failed, "
+ "exception thrown: " + e.getMessage());
return false;
}
Log.i(TAG, "Sorting partitions in the specified order from partitions_order.xml");
return true;
}
/**
* Sort partitions by order in partition_order.xml if the file exists.
*
* @hide
*/
@VisibleForTesting
public static boolean sortPartitions(String partitionOrderFilePath,
List<OverlayPartition> partitions) {
Map<String, Integer> orderMap = new HashMap<>();
if (!parseAndValidatePartitionsOrderXml(partitionOrderFilePath, orderMap, partitions)) {
return false;
}
Comparator<OverlayPartition> partitionComparator = Comparator.comparingInt(
o -> orderMap.get(o.getName()));
Collections.sort(partitions, partitionComparator);
return true;
}
/** /**
* Creates an instance of OverlayConfig for use in the zygote process. * Creates an instance of OverlayConfig for use in the zygote process.
* This instance will not include information of static overlays existing outside of a partition * This instance will not include information of static overlays existing outside of a partition
@@ -476,4 +585,19 @@ public class OverlayConfig {
*/ */
private static native String[] createIdmap(@NonNull String targetPath, private static native String[] createIdmap(@NonNull String targetPath,
@NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable); @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
/**
* @hide
*/
public boolean isDefaultPartitionOrder() {
return mIsDefaultPartitionOrder;
}
/**
* @hide
*/
public String getPartitionOrder() {
return mPartitionOrder;
}
} }

View File

@@ -27,6 +27,7 @@ import android.util.ArraySet;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo; import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils; import com.android.internal.util.XmlUtils;
@@ -53,8 +54,11 @@ import java.util.Map;
* *
* @see #parseOverlay(File, XmlPullParser, OverlayScanner, ParsingContext) * @see #parseOverlay(File, XmlPullParser, OverlayScanner, ParsingContext)
* @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext) * @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
*
* @hide
**/ **/
final class OverlayConfigParser { @VisibleForTesting
public final class OverlayConfigParser {
// Default values for overlay configurations. // Default values for overlay configurations.
static final boolean DEFAULT_ENABLED_STATE = false; static final boolean DEFAULT_ENABLED_STATE = false;
@@ -115,7 +119,11 @@ final class OverlayConfigParser {
} }
} }
static class OverlayPartition extends SystemPartition { /**
* @hide
**/
@VisibleForTesting
public static class OverlayPartition extends SystemPartition {
// Policies passed to idmap2 during idmap creation. // Policies passed to idmap2 during idmap creation.
// Keep partition policy constants in sync with f/b/cmds/idmap2/include/idmap2/Policies.h. // Keep partition policy constants in sync with f/b/cmds/idmap2/include/idmap2/Policies.h.
static final String POLICY_ODM = "odm"; static final String POLICY_ODM = "odm";
@@ -128,7 +136,11 @@ final class OverlayConfigParser {
@NonNull @NonNull
public final String policy; public final String policy;
OverlayPartition(@NonNull SystemPartition partition) { /**
* @hide
**/
@VisibleForTesting
public OverlayPartition(@NonNull SystemPartition partition) {
super(partition); super(partition);
this.policy = policyForPartition(partition); this.policy = policyForPartition(partition);
} }

View File

@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.content.pm.PackagePartitions;
import android.os.FileUtils; import android.os.FileUtils;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
@@ -32,6 +33,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R; import com.android.frameworks.coretests.R;
import com.android.internal.content.om.OverlayConfig; import com.android.internal.content.om.OverlayConfig;
import com.android.internal.content.om.OverlayConfig.IdmapInvocation; import com.android.internal.content.om.OverlayConfig.IdmapInvocation;
import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
import com.android.internal.content.om.OverlayScanner; import com.android.internal.content.om.OverlayScanner;
import org.junit.Rule; import org.junit.Rule;
@@ -46,6 +48,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
@Presubmit @Presubmit
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@@ -88,6 +91,17 @@ public class OverlayConfigTest {
assertEquals(configIndex, config.configIndex); assertEquals(configIndex, config.configIndex);
} }
private String generatePartitionOrderString(List<OverlayPartition> partitions) {
StringBuilder partitionOrder = new StringBuilder();
for (int i = 0; i < partitions.size(); i++) {
partitionOrder.append(partitions.get(i).getName());
if (i < partitions.size() - 1) {
partitionOrder.append(", ");
}
}
return partitionOrder.toString();
}
@Test @Test
public void testImmutableAfterNonImmutableFails() throws IOException { public void testImmutableAfterNonImmutableFails() throws IOException {
mExpectedException.expect(IllegalStateException.class); mExpectedException.expect(IllegalStateException.class);
@@ -685,4 +699,122 @@ public class OverlayConfigTest {
OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three"); OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
assertNotNull(o3); assertNotNull(o3);
} }
@Test
public void testSortPartitionsWithoutXml() throws IOException {
ArrayList<OverlayPartition> partitions = new ArrayList<>(
PackagePartitions.getOrderedPartitions(OverlayPartition::new));
final OverlayConfig overlayConfig = createConfigImpl();
String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
"/product/overlay/partition_order.xml");
assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
assertEquals("system, vendor, odm, oem, product, system_ext",
generatePartitionOrderString(partitions));
}
@Test
public void testSortPartitionsWithInvalidXmlRootElement() throws IOException {
ArrayList<OverlayPartition> partitions = new ArrayList<>(
PackagePartitions.getOrderedPartitions(OverlayPartition::new));
createFile("/product/overlay/partition_order.xml",
"<partition-list>\n"
+ " <partition name=\"system_ext\"/>\n"
+ " <partition name=\"vendor\"/>\n"
+ " <partition name=\"oem\"/>\n"
+ " <partition name=\"odm\"/>\n"
+ " <partition name=\"product\"/>\n"
+ " <partition name=\"system\"/>\n"
+ "</partition-list>\n");
final OverlayConfig overlayConfig = createConfigImpl();
String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
"/product/overlay/partition_order.xml");
assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
assertEquals("system, vendor, odm, oem, product, system_ext",
generatePartitionOrderString(partitions));
}
@Test
public void testSortPartitionsWithInvalidPartition() throws IOException {
ArrayList<OverlayPartition> partitions = new ArrayList<>(
PackagePartitions.getOrderedPartitions(OverlayPartition::new));
createFile("/product/overlay/partition_order.xml",
"<partition-order>\n"
+ " <partition name=\"INVALID\"/>\n"
+ " <partition name=\"vendor\"/>\n"
+ " <partition name=\"oem\"/>\n"
+ " <partition name=\"odm\"/>\n"
+ " <partition name=\"product\"/>\n"
+ " <partition name=\"system\"/>\n"
+ "</partition-order>\n");
final OverlayConfig overlayConfig = createConfigImpl();
String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
"/product/overlay/partition_order.xml");
assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
assertEquals("system, vendor, odm, oem, product, system_ext",
generatePartitionOrderString(partitions));
}
@Test
public void testSortPartitionsWithDuplicatePartition() throws IOException {
ArrayList<OverlayPartition> partitions = new ArrayList<>(
PackagePartitions.getOrderedPartitions(OverlayPartition::new));
createFile("/product/overlay/partition_order.xml",
"<partition-order>\n"
+ " <partition name=\"system_ext\"/>\n"
+ " <partition name=\"system\"/>\n"
+ " <partition name=\"vendor\"/>\n"
+ " <partition name=\"oem\"/>\n"
+ " <partition name=\"odm\"/>\n"
+ " <partition name=\"product\"/>\n"
+ " <partition name=\"system\"/>\n"
+ "</partition-order>\n");
final OverlayConfig overlayConfig = createConfigImpl();
String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
"/product/overlay/partition_order.xml");
assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
assertEquals("system, vendor, odm, oem, product, system_ext",
generatePartitionOrderString(partitions));
}
@Test
public void testSortPartitionsWithMissingPartition() throws IOException {
ArrayList<OverlayPartition> partitions = new ArrayList<>(
PackagePartitions.getOrderedPartitions(OverlayPartition::new));
createFile("/product/overlay/partition_order.xml",
"<partition-order>\n"
+ " <partition name=\"vendor\"/>\n"
+ " <partition name=\"oem\"/>\n"
+ " <partition name=\"odm\"/>\n"
+ " <partition name=\"product\"/>\n"
+ " <partition name=\"system\"/>\n"
+ "</partition-order>\n");
final OverlayConfig overlayConfig = createConfigImpl();
String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
"/product/overlay/partition_order.xml");
assertEquals(false, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
assertEquals("system, vendor, odm, oem, product, system_ext",
generatePartitionOrderString(partitions));
}
@Test
public void testSortPartitionsWithCorrectPartitionOrderXml() throws IOException {
ArrayList<OverlayPartition> partitions = new ArrayList<>(
PackagePartitions.getOrderedPartitions(OverlayPartition::new));
createFile("/product/overlay/partition_order.xml",
"<partition-order>\n"
+ " <partition name=\"system_ext\"/>\n"
+ " <partition name=\"vendor\"/>\n"
+ " <partition name=\"oem\"/>\n"
+ " <partition name=\"odm\"/>\n"
+ " <partition name=\"product\"/>\n"
+ " <partition name=\"system\"/>\n"
+ "</partition-order>\n");
final OverlayConfig overlayConfig = createConfigImpl();
String partitionOrderFilePath = String.format("%s/%s", mTestFolder.getRoot(),
"/product/overlay/partition_order.xml");
assertEquals(true, overlayConfig.sortPartitions(partitionOrderFilePath, partitions));
assertEquals("system_ext, vendor, oem, odm, product, system",
generatePartitionOrderString(partitions));
}
} }

View File

@@ -1109,6 +1109,21 @@ public final class OverlayManagerService extends SystemService {
int callingUid = Binder.getCallingUid(); int callingUid = Binder.getCallingUid();
mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, realUserId); mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, realUserId);
} }
/**
* @hide
*/
public String getPartitionOrder() {
return mImpl.getOverlayConfig().getPartitionOrder();
}
/**
* @hide
*/
public boolean isDefaultPartitionOrder() {
return mImpl.getOverlayConfig().isDefaultPartitionOrder();
}
}; };
private static final class PackageManagerHelperImpl implements PackageManagerHelper { private static final class PackageManagerHelperImpl implements PackageManagerHelper {

View File

@@ -876,4 +876,8 @@ final class OverlayManagerServiceImpl {
super(message, cause); super(message, cause);
} }
} }
OverlayConfig getOverlayConfig() {
return mOverlayConfig;
}
} }

View File

@@ -16,6 +16,8 @@
package com.android.server.om; package com.android.server.om;
import static com.android.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
@@ -79,6 +81,8 @@ final class OverlayManagerShellCommand extends ShellCommand {
return runLookup(); return runLookup();
case "fabricate": case "fabricate":
return runFabricate(); return runFabricate();
case "partition-order":
return runPartitionOrder();
default: default:
return handleDefaultCommands(cmd); return handleDefaultCommands(cmd);
} }
@@ -130,6 +134,9 @@ final class OverlayManagerShellCommand extends ShellCommand {
out.println(" Create an overlay from a single resource. Caller must be root. Example:"); out.println(" Create an overlay from a single resource. Caller must be root. Example:");
out.println(" fabricate --target android --name LighterGray \\"); out.println(" fabricate --target android --name LighterGray \\");
out.println(" android:color/lighter_gray 0x1c 0xffeeeeee"); out.println(" android:color/lighter_gray 0x1c 0xffeeeeee");
out.println(" partition-order");
out.println(" Print the partition order from overlay config and how this order");
out.println(" got established, by default or by " + PARTITION_ORDER_FILE_PATH);
} }
private int runList() throws RemoteException { private int runList() throws RemoteException {
@@ -230,6 +237,14 @@ final class OverlayManagerShellCommand extends ShellCommand {
return 0; return 0;
} }
private int runPartitionOrder() throws RemoteException {
final PrintWriter out = getOutPrintWriter();
out.println("Partition order (low to high priority): " + mInterface.getPartitionOrder());
out.println("Established by " + (mInterface.isDefaultPartitionOrder() ? "default"
: PARTITION_ORDER_FILE_PATH));
return 0;
}
private int runFabricate() throws RemoteException { private int runFabricate() throws RemoteException {
final PrintWriter err = getErrPrintWriter(); final PrintWriter err = getErrPrintWriter();
if (Binder.getCallingUid() != Process.ROOT_UID) { if (Binder.getCallingUid() != Process.ROOT_UID) {