Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a48c18e3 authored by Kelvin Zhang's avatar Kelvin Zhang
Browse files

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
parent 4e8328c9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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",
+1 −0
Original line number Diff line number Diff line
@@ -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();
+15 −0
Original line number Diff line number Diff line
@@ -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.
     */
+80 −0
Original line number Diff line number Diff line
@@ -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();