Incremental install via adb.
- passing V4 signature to IncFS, - cleanup and use InstallationFile everywhere, - pass params to DataLoader creation, - minor refactor for PackageManagerShellCommandDataLoader to prepare for Incremental data loading. Test: atest PackageManagerShellCommandTest Bug: b/136132412 b/133435829 Change-Id: Iacc3e4c51c0fa3410b076147ce153a1303246189
This commit is contained in:
@@ -16,82 +16,59 @@
|
||||
|
||||
package android.content.pm;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Defines the properties of a file in an installation session.
|
||||
* TODO(b/136132412): update with new APIs.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class InstallationFile implements Parcelable {
|
||||
public static final int FILE_TYPE_UNKNOWN = -1;
|
||||
public static final int FILE_TYPE_APK = 0;
|
||||
public static final int FILE_TYPE_LIB = 1;
|
||||
public static final int FILE_TYPE_OBB = 2;
|
||||
private final @PackageInstaller.FileLocation int mLocation;
|
||||
private final @NonNull String mName;
|
||||
private final long mLengthBytes;
|
||||
private final @Nullable byte[] mMetadata;
|
||||
private final @Nullable byte[] mSignature;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(prefix = {"FILE_TYPE_"}, value = {
|
||||
FILE_TYPE_APK,
|
||||
FILE_TYPE_LIB,
|
||||
FILE_TYPE_OBB,
|
||||
})
|
||||
public @interface FileType {
|
||||
}
|
||||
|
||||
private String mFileName;
|
||||
private @FileType int mFileType;
|
||||
private long mFileSize;
|
||||
private byte[] mMetadata;
|
||||
|
||||
public InstallationFile(@NonNull String fileName, long fileSize,
|
||||
@Nullable byte[] metadata) {
|
||||
mFileName = fileName;
|
||||
mFileSize = fileSize;
|
||||
public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name,
|
||||
long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) {
|
||||
mLocation = location;
|
||||
mName = name;
|
||||
mLengthBytes = lengthBytes;
|
||||
mMetadata = metadata;
|
||||
if (fileName.toLowerCase().endsWith(".apk")) {
|
||||
mFileType = FILE_TYPE_APK;
|
||||
} else if (fileName.toLowerCase().endsWith(".obb")) {
|
||||
mFileType = FILE_TYPE_OBB;
|
||||
} else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith(
|
||||
"lib/")) {
|
||||
mFileType = FILE_TYPE_LIB;
|
||||
} else {
|
||||
mFileType = FILE_TYPE_UNKNOWN;
|
||||
}
|
||||
mSignature = signature;
|
||||
}
|
||||
|
||||
public @FileType int getFileType() {
|
||||
return mFileType;
|
||||
public @PackageInstaller.FileLocation int getLocation() {
|
||||
return mLocation;
|
||||
}
|
||||
|
||||
public @NonNull String getName() {
|
||||
return mFileName;
|
||||
return mName;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return mFileSize;
|
||||
public long getLengthBytes() {
|
||||
return mLengthBytes;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getMetadata() {
|
||||
return mMetadata;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getSignature() {
|
||||
return mSignature;
|
||||
}
|
||||
|
||||
private InstallationFile(Parcel source) {
|
||||
mFileName = source.readString();
|
||||
mFileType = source.readInt();
|
||||
mFileSize = source.readLong();
|
||||
mLocation = source.readInt();
|
||||
mName = source.readString();
|
||||
mLengthBytes = source.readLong();
|
||||
mMetadata = source.createByteArray();
|
||||
mSignature = source.createByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,10 +78,11 @@ public final class InstallationFile implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(mFileName);
|
||||
dest.writeInt(mFileType);
|
||||
dest.writeLong(mFileSize);
|
||||
dest.writeInt(mLocation);
|
||||
dest.writeString(mName);
|
||||
dest.writeLong(mLengthBytes);
|
||||
dest.writeByteArray(mMetadata);
|
||||
dest.writeByteArray(mSignature);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<InstallationFile> CREATOR =
|
||||
|
||||
@@ -29,6 +29,8 @@ package android.os.incremental;
|
||||
* @throws IllegalStateException the session is not an Incremental installation session.
|
||||
*/
|
||||
|
||||
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
@@ -85,7 +87,7 @@ public final class IncrementalFileStorages {
|
||||
try {
|
||||
result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams);
|
||||
for (InstallationFile file : addedFiles) {
|
||||
if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
|
||||
if (file.getLocation() == LOCATION_DATA_APP) {
|
||||
try {
|
||||
result.addApkFile(file);
|
||||
} catch (IOException e) {
|
||||
@@ -95,7 +97,7 @@ public final class IncrementalFileStorages {
|
||||
e);
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Unknown file type: " + file.getFileType());
|
||||
throw new IOException("Unknown file location: " + file.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +149,8 @@ public final class IncrementalFileStorages {
|
||||
String apkName = apk.getName();
|
||||
File targetFile = Paths.get(stageDirPath, apkName).toFile();
|
||||
if (!targetFile.exists()) {
|
||||
mDefaultStorage.makeFile(apkName, apk.getSize(), null,
|
||||
apk.getMetadata(), 0, null, null, null);
|
||||
mDefaultStorage.makeFile(apkName, apk.getLengthBytes(), null, apk.getMetadata(),
|
||||
apk.getSignature());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -299,7 +299,16 @@ public final class IncrementalManager {
|
||||
return nativeIsIncrementalPath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns raw signature for file if it's on Incremental File System.
|
||||
* Unsafe, use only if you are sure what you are doing.
|
||||
*/
|
||||
public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
|
||||
return nativeUnsafeGetFileSignature(path);
|
||||
}
|
||||
|
||||
/* Native methods */
|
||||
private static native boolean nativeIsEnabled();
|
||||
private static native boolean nativeIsIncrementalPath(@NonNull String path);
|
||||
private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -169,10 +171,11 @@ public final class IncrementalStorage {
|
||||
* @param path Relative path of the new file.
|
||||
* @param size Size of the new file in bytes.
|
||||
* @param metadata Metadata bytes.
|
||||
* @param v4signatureBytes Serialized V4SignatureProto.
|
||||
*/
|
||||
public void makeFile(@NonNull String path, long size, @Nullable UUID id,
|
||||
@Nullable byte[] metadata, int hashAlgorithm, @Nullable byte[] rootHash,
|
||||
@Nullable byte[] additionalData, @Nullable byte[] signature) throws IOException {
|
||||
@Nullable byte[] metadata, @Nullable byte[] v4signatureBytes)
|
||||
throws IOException {
|
||||
try {
|
||||
if (id == null && metadata == null) {
|
||||
throw new IOException("File ID and metadata cannot both be null");
|
||||
@@ -181,13 +184,7 @@ public final class IncrementalStorage {
|
||||
params.size = size;
|
||||
params.metadata = (metadata == null ? new byte[0] : metadata);
|
||||
params.fileId = idToBytes(id);
|
||||
if (hashAlgorithm != 0 || signature != null) {
|
||||
params.signature = new IncrementalSignature();
|
||||
params.signature.hashAlgorithm = hashAlgorithm;
|
||||
params.signature.rootHash = rootHash;
|
||||
params.signature.additionalData = additionalData;
|
||||
params.signature.signature = signature;
|
||||
}
|
||||
params.signature = parseV4Signature(v4signatureBytes);
|
||||
int res = mService.makeFile(mId, path, params);
|
||||
if (res != 0) {
|
||||
throw new IOException("makeFile() failed with errno " + -res);
|
||||
@@ -197,6 +194,7 @@ public final class IncrementalStorage {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a file in Incremental storage. The content of the file is mapped from a range inside
|
||||
* a source file in the same storage.
|
||||
@@ -349,6 +347,37 @@ public final class IncrementalStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata object of an IncFs File.
|
||||
*
|
||||
* @param id The file id.
|
||||
* @return Byte array that contains metadata bytes.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getFileMetadata(@NonNull UUID id) {
|
||||
try {
|
||||
final byte[] rawId = idToBytes(id);
|
||||
return mService.getMetadataById(mId, rawId);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the data loader service associated with the current storage to start data loader
|
||||
*
|
||||
* @return True if data loader is successfully started.
|
||||
*/
|
||||
public boolean startLoading() {
|
||||
try {
|
||||
return mService.startLoading(mId);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int UUID_BYTE_SIZE = 16;
|
||||
|
||||
/**
|
||||
@@ -386,35 +415,44 @@ public final class IncrementalStorage {
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata object of an IncFs File.
|
||||
*
|
||||
* @param id The file id.
|
||||
* @return Byte array that contains metadata bytes.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getFileMetadata(@NonNull UUID id) {
|
||||
try {
|
||||
final byte[] rawId = idToBytes(id);
|
||||
return mService.getMetadataById(mId, rawId);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private static final int INCFS_HASH_SHA256 = 1;
|
||||
private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
|
||||
private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
|
||||
|
||||
/**
|
||||
* Informs the data loader service associated with the current storage to start data loader
|
||||
*
|
||||
* @return True if data loader is successfully started.
|
||||
* Deserialize and validate v4 signature bytes.
|
||||
*/
|
||||
public boolean startLoading() {
|
||||
try {
|
||||
return mService.startLoading(mId);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
return false;
|
||||
private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
|
||||
throws IOException {
|
||||
if (v4signatureBytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final V4Signature signature;
|
||||
try (DataInputStream input = new DataInputStream(
|
||||
new ByteArrayInputStream(v4signatureBytes))) {
|
||||
signature = V4Signature.readFrom(input);
|
||||
}
|
||||
|
||||
final byte[] rootHash = signature.verityRootHash;
|
||||
final byte[] additionalData = signature.v3Digest;
|
||||
final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;
|
||||
|
||||
if (rootHash.length != INCFS_MAX_HASH_SIZE) {
|
||||
throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
|
||||
}
|
||||
if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
|
||||
throw new IOException(
|
||||
"additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
|
||||
}
|
||||
|
||||
IncrementalSignature result = new IncrementalSignature();
|
||||
result.hashAlgorithm = INCFS_HASH_SHA256;
|
||||
result.rootHash = rootHash;
|
||||
result.additionalData = additionalData;
|
||||
result.signature = pkcs7Signature;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
62
core/java/android/os/incremental/V4Signature.java
Normal file
62
core/java/android/os/incremental/V4Signature.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.os.incremental;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* V4 signature fields.
|
||||
* Keep in sync with APKSig master copy.
|
||||
* @hide
|
||||
*/
|
||||
public class V4Signature {
|
||||
public final byte[] verityRootHash;
|
||||
public final byte[] v3Digest;
|
||||
public final byte[] pkcs7SignatureBlock;
|
||||
|
||||
V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) {
|
||||
this.verityRootHash = verityRootHash;
|
||||
this.v3Digest = v3Digest;
|
||||
this.pkcs7SignatureBlock = pkcs7SignatureBlock;
|
||||
}
|
||||
|
||||
static byte[] readBytes(DataInputStream stream) throws IOException {
|
||||
byte[] result = new byte[stream.readInt()];
|
||||
stream.read(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static V4Signature readFrom(DataInputStream stream) throws IOException {
|
||||
byte[] verityRootHash = readBytes(stream);
|
||||
byte[] v3Digest = readBytes(stream);
|
||||
byte[] pkcs7SignatureBlock = readBytes(stream);
|
||||
return new V4Signature(verityRootHash, v3Digest, pkcs7SignatureBlock);
|
||||
}
|
||||
|
||||
static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException {
|
||||
stream.writeInt(bytes.length);
|
||||
stream.write(bytes);
|
||||
}
|
||||
|
||||
void writeTo(DataOutputStream stream) throws IOException {
|
||||
writeBytes(stream, this.verityRootHash);
|
||||
writeBytes(stream, this.v3Digest);
|
||||
writeBytes(stream, this.pkcs7SignatureBlock);
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ public abstract class DataLoaderService extends Service {
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @Nullable DataLoader onCreateDataLoader() {
|
||||
public @Nullable DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user