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:
Alex Buynytskyy
2020-02-08 17:50:50 -08:00
parent 6e406c8ae7
commit cd4d3875e2
14 changed files with 251 additions and 170 deletions

View File

@@ -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 =

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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;
}
/**

View 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);
}
}

View File

@@ -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;
}