Call apexd's API to allocate space before install non-AB package

When installing a non-AB package, if the OS comtains compressed apexes,
we need to allocate space for these apexes so that they can be properly
decompressed on the next reboot.

Test: adb shell cmd recovery install-package /data/ota_package.zip
Change-Id: Ia40d0614e0e724cfb17e91720ec88a15795bd8ee
This commit is contained in:
Kelvin Zhang 2021-04-12 17:23:51 -04:00
parent 4e8328c972
commit a48c18e3df
4 changed files with 97 additions and 0 deletions

View File

@ -210,6 +210,7 @@ java_library {
"apex_aidl_interface-java",
"framework-protos",
"updatable-driver-protos",
"ota_metadata_proto_java",
"android.hidl.base-V1.0-java",
"android.hardware.cas-V1.0-java",
"android.hardware.cas-V1.1-java",

View File

@ -23,6 +23,7 @@ import android.os.IRecoverySystemProgressListener;
/** @hide */
interface IRecoverySystem {
boolean allocateSpaceForUpdate(in String packageFilePath);
boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
boolean setupBcb(in String command);
boolean clearBcb();

View File

@ -672,6 +672,14 @@ public class RecoverySystem {
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
try {
if (!rs.allocateSpaceForUpdate(packageFile)) {
throw new IOException("Failed to allocate space for update "
+ packageFile.getAbsolutePath());
}
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
// Having set up the BCB (bootloader control block), go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@ -1391,6 +1399,13 @@ public class RecoverySystem {
return false;
}
/**
* Talks to RecoverySystemService via Binder to allocate space
*/
private boolean allocateSpaceForUpdate(File packageFile) throws RemoteException {
return mService.allocateSpaceForUpdate(packageFile.getAbsolutePath());
}
/**
* Talks to RecoverySystemService via Binder to clear up the BCB.
*/

View File

@ -24,10 +24,13 @@ import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMA
import static android.os.RecoverySystem.RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED;
import static android.os.RecoverySystem.ResumeOnRebootRebootErrorCode;
import static android.os.UserHandle.USER_SYSTEM;
import static android.ota.nano.OtaPackageMetadata.ApexMetadata;
import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_NONE;
import android.annotation.IntDef;
import android.apex.CompressedApexInfo;
import android.apex.CompressedApexInfoList;
import android.content.Context;
import android.content.IntentSender;
import android.content.SharedPreferences;
@ -47,9 +50,11 @@ import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.sysprop.ApexProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.FastImmutableArraySet;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@ -59,6 +64,7 @@ import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.RebootEscrowListener;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.ApexManager;
import libcore.io.IoUtils;
@ -68,9 +74,13 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* The recovery system service is responsible for coordinating recovery related
@ -871,6 +881,76 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
return rebootWithLskfImpl(packageName, reason, slotSwitch);
}
public static boolean isUpdatableApexSupported() {
return ApexProperties.updatable().orElse(false);
}
// Metadata should be no more than few MB, if it's larger than 100MB something is wrong.
private static final long APEX_INFO_SIZE_LIMIT = 24 * 1024 * 100;
private static CompressedApexInfoList getCompressedApexInfoList(String packageFile)
throws IOException {
try (ZipFile zipFile = new ZipFile(packageFile)) {
final ZipEntry entry = zipFile.getEntry("apex_info.pb");
if (entry == null) {
return null;
}
if (entry.getSize() >= APEX_INFO_SIZE_LIMIT) {
throw new IllegalArgumentException("apex_info.pb has size "
+ entry.getSize()
+ " which is larger than the permitted limit" + APEX_INFO_SIZE_LIMIT);
}
if (entry.getSize() == 0) {
CompressedApexInfoList infoList = new CompressedApexInfoList();
infoList.apexInfos = new CompressedApexInfo[0];
return infoList;
}
Log.i(TAG, "Allocating " + entry.getSize()
+ " bytes of memory to store OTA Metadata");
byte[] data = new byte[(int) entry.getSize()];
try (InputStream is = zipFile.getInputStream(entry)) {
int bytesRead = is.read(data);
String msg = "Read " + bytesRead + " when expecting " + data.length;
Log.e(TAG, msg);
if (bytesRead != data.length) {
throw new IOException(msg);
}
}
ApexMetadata metadata = ApexMetadata.parseFrom(data);
CompressedApexInfoList apexInfoList = new CompressedApexInfoList();
apexInfoList.apexInfos =
Arrays.stream(metadata.apexInfo).filter(apex -> apex.isCompressed).map(apex -> {
CompressedApexInfo info = new CompressedApexInfo();
info.moduleName = apex.packageName;
info.decompressedSize = apex.decompressedSize;
info.versionCode = apex.version;
return info;
}).toArray(CompressedApexInfo[]::new);
return apexInfoList;
}
}
@Override
public boolean allocateSpaceForUpdate(String packageFile) {
if (!isUpdatableApexSupported()) {
Log.i(TAG, "Updatable Apex not supported, "
+ "allocateSpaceForUpdate does nothing.");
return true;
}
try {
CompressedApexInfoList apexInfoList = getCompressedApexInfoList(packageFile);
ApexManager apexManager = ApexManager.getInstance();
apexManager.reserveSpaceForCompressedApex(apexInfoList);
return true;
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
} catch (IOException | UnsupportedOperationException e) {
Slog.e(TAG, "Failed to reserve space for compressed apex: ", e);
}
return false;
}
@Override // Binder call
public boolean isLskfCaptured(String packageName) {
enforcePermissionForResumeOnReboot();