blob: d2d382dfc14d2876e3d109f3dfdd5e9b0f5e46bf [file] [log] [blame]
/*
* Copyright (C) 2017 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 com.android.server.backup.utils;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_MANIFEST_PACKAGE_NAME;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_OLD_VERSION;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_POLICY_ALLOW_APKS;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_VERSION;
import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import android.app.backup.BackupAgent;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManagerMonitor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Slog;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.restore.RestorePolicy;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Utility methods to read backup tar file.
*/
public class TarBackupReader {
private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
private static final int TAR_HEADER_LENGTH_PATH = 100;
private static final int TAR_HEADER_OFFSET_PATH = 0;
private static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
private static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
private static final int TAR_HEADER_LENGTH_MODE = 8;
private static final int TAR_HEADER_OFFSET_MODE = 100;
private static final int TAR_HEADER_LENGTH_MODTIME = 12;
private static final int TAR_HEADER_OFFSET_MODTIME = 136;
private static final int TAR_HEADER_LENGTH_FILESIZE = 12;
private static final int TAR_HEADER_OFFSET_FILESIZE = 124;
private static final int TAR_HEADER_LONG_RADIX = 8;
private final InputStream mInputStream;
private final BytesReadListener mBytesReadListener;
private IBackupManagerMonitor mMonitor;
// Widget blob to be restored out-of-band.
private byte[] mWidgetData = null;
public TarBackupReader(InputStream inputStream, BytesReadListener bytesReadListener,
IBackupManagerMonitor monitor) {
mInputStream = inputStream;
mBytesReadListener = bytesReadListener;
mMonitor = monitor;
}
/**
* Consumes a tar file header block [sequence] and accumulates the relevant metadata.
*/
public FileMetadata readTarHeaders() throws IOException {
byte[] block = new byte[512];
FileMetadata info = null;
boolean gotHeader = readTarHeader(block);
if (gotHeader) {
try {
// okay, presume we're okay, and extract the various metadata
info = new FileMetadata();
info.size = extractRadix(block,
TAR_HEADER_OFFSET_FILESIZE,
TAR_HEADER_LENGTH_FILESIZE,
TAR_HEADER_LONG_RADIX);
info.mtime = extractRadix(block,
TAR_HEADER_OFFSET_MODTIME,
TAR_HEADER_LENGTH_MODTIME,
TAR_HEADER_LONG_RADIX);
info.mode = extractRadix(block,
TAR_HEADER_OFFSET_MODE,
TAR_HEADER_LENGTH_MODE,
TAR_HEADER_LONG_RADIX);
info.path = extractString(block,
TAR_HEADER_OFFSET_PATH_PREFIX,
TAR_HEADER_LENGTH_PATH_PREFIX);
String path = extractString(block,
TAR_HEADER_OFFSET_PATH,
TAR_HEADER_LENGTH_PATH);
if (path.length() > 0) {
if (info.path.length() > 0) {
info.path += '/';
}
info.path += path;
}
// tar link indicator field: 1 byte at offset 156 in the header.
int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
if (typeChar == 'x') {
// pax extended header, so we need to read that
gotHeader = readPaxExtendedHeader(info);
if (gotHeader) {
// and after a pax extended header comes another real header -- read
// that to find the real file type
gotHeader = readTarHeader(block);
}
if (!gotHeader) {
throw new IOException("Bad or missing pax header");
}
typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
}
switch (typeChar) {
case '0':
info.type = BackupAgent.TYPE_FILE;
break;
case '5': {
info.type = BackupAgent.TYPE_DIRECTORY;
if (info.size != 0) {
Slog.w(TAG, "Directory entry with nonzero size in header");
info.size = 0;
}
break;
}
case 0: {
// presume EOF
if (MORE_DEBUG) {
Slog.w(TAG, "Saw type=0 in tar header block, info=" + info);
}
return null;
}
default: {
Slog.e(TAG, "Unknown tar entity type: " + typeChar);
throw new IOException("Unknown entity type " + typeChar);
}
}
// Parse out the path
//
// first: apps/shared/unrecognized
if (FullBackup.SHARED_PREFIX.regionMatches(0,
info.path, 0, FullBackup.SHARED_PREFIX.length())) {
// File in shared storage. !!! TODO: implement this.
info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
info.packageName = SHARED_BACKUP_AGENT_PACKAGE;
info.domain = FullBackup.SHARED_STORAGE_TOKEN;
if (DEBUG) {
Slog.i(TAG, "File in shared storage: " + info.path);
}
} else if (FullBackup.APPS_PREFIX.regionMatches(0,
info.path, 0, FullBackup.APPS_PREFIX.length())) {
// App content! Parse out the package name and domain
// strip the apps/ prefix
info.path = info.path.substring(FullBackup.APPS_PREFIX.length());
// extract the package name
int slash = info.path.indexOf('/');
if (slash < 0) {
throw new IOException("Illegal semantic path in " + info.path);
}
info.packageName = info.path.substring(0, slash);
info.path = info.path.substring(slash + 1);
// if it's a manifest or metadata payload we're done, otherwise parse
// out the domain into which the file will be restored
if (!info.path.equals(BACKUP_MANIFEST_FILENAME) &&
!info.path.equals(BACKUP_METADATA_FILENAME)) {
slash = info.path.indexOf('/');
if (slash < 0) {
throw new IOException("Illegal semantic path in non-manifest "
+ info.path);
}
info.domain = info.path.substring(0, slash);
info.path = info.path.substring(slash + 1);
}
}
} catch (IOException e) {
if (DEBUG) {
Slog.e(TAG, "Parse error in header: " + e.getMessage());
if (MORE_DEBUG) {
hexLog(block);
}
}
throw e;
}
}
return info;
}
/**
* Tries to read exactly the given number of bytes into a buffer at the stated offset.
*
* @param in - input stream to read bytes from..
* @param buffer - where to write bytes to.
* @param offset - offset in buffer to write bytes to.
* @param size - number of bytes to read.
* @return number of bytes actually read.
* @throws IOException in case of an error.
*/
private static int readExactly(InputStream in, byte[] buffer, int offset, int size)
throws IOException {
if (size <= 0) {
throw new IllegalArgumentException("size must be > 0");
}
if (MORE_DEBUG) {
Slog.i(TAG, " ... readExactly(" + size + ") called");
}
int soFar = 0;
while (soFar < size) {
int nRead = in.read(buffer, offset + soFar, size - soFar);
if (nRead <= 0) {
if (MORE_DEBUG) {
Slog.w(TAG, "- wanted exactly " + size + " but got only " + soFar);
}
break;
}
soFar += nRead;
if (MORE_DEBUG) {
Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soFar));
}
}
return soFar;
}
/**
* Reads app manifest, filling version and hasApk fields in the metadata, and returns array of
* signatures.
*
* @param info - file metadata.
* @return array of signatures or null, in case of an error.
* @throws IOException in case of an error.
*/
public Signature[] readAppManifestAndReturnSignatures(FileMetadata info)
throws IOException {
// Fail on suspiciously large manifest files
if (info.size > 64 * 1024) {
throw new IOException("Restore manifest too big; corrupt? size=" + info.size);
}
byte[] buffer = new byte[(int) info.size];
if (MORE_DEBUG) {
Slog.i(TAG,
" readAppManifestAndReturnSignatures() looking for " + info.size + " bytes");
}
if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
mBytesReadListener.onBytesRead(info.size);
} else {
throw new IOException("Unexpected EOF in manifest");
}
String[] str = new String[1];
int offset = 0;
try {
offset = extractLine(buffer, offset, str);
int version = Integer.parseInt(str[0]);
if (version == BACKUP_MANIFEST_VERSION) {
offset = extractLine(buffer, offset, str);
String manifestPackage = str[0];
// TODO: handle <original-package>
if (manifestPackage.equals(info.packageName)) {
offset = extractLine(buffer, offset, str);
info.version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
// This is the platform version, which we don't use, but we parse it
// as a safety against corruption in the manifest.
Integer.parseInt(str[0]);
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
info.hasApk = str[0].equals("1");
offset = extractLine(buffer, offset, str);
int numSigs = Integer.parseInt(str[0]);
if (numSigs > 0) {
Signature[] sigs = new Signature[numSigs];
for (int i = 0; i < numSigs; i++) {
offset = extractLine(buffer, offset, str);
sigs[i] = new Signature(str[0]);
}
return sigs;
} else {
Slog.i(TAG, "Missing signature on backed-up package " + info.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_MISSING_SIGNATURE,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
}
} else {
Slog.i(TAG, "Expected package " + info.packageName
+ " but restore manifest claims " + manifestPackage);
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
monitoringExtras,
EXTRA_LOG_MANIFEST_PACKAGE_NAME, manifestPackage);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
}
} else {
Slog.i(TAG, "Unknown restore manifest version " + version
+ " for package " + info.packageName);
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
}
} catch (NumberFormatException e) {
Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
info.packageName));
} catch (IllegalArgumentException e) {
Slog.w(TAG, e.getMessage());
}
return null;
}
/**
* Chooses restore policy.
*
* @param packageManager - PackageManager instance.
* @param allowApks - allow restore set to include apks.
* @param info - file metadata.
* @param signatures - array of signatures parsed from backup file.
* @param userId - ID of the user for which restore is performed.
* @return a restore policy constant.
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
boolean allowApks, FileMetadata info, Signature[] signatures,
PackageManagerInternal pmi, int userId) {
if (signatures == null) {
return RestorePolicy.IGNORE;
}
RestorePolicy policy = RestorePolicy.IGNORE;
// Okay, got the manifest info we need...
try {
PackageInfo pkgInfo = packageManager.getPackageInfoAsUser(
info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
// Fall through to IGNORE if the app explicitly disallows backup
final int flags = pkgInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
// Restore system-uid-space packages only if they have
// defined a custom backup agent
if (!UserHandle.isCore(pkgInfo.applicationInfo.uid)
|| (pkgInfo.applicationInfo.backupAgentName != null)) {
// Verify signatures against any installed version; if they
// don't match, then we fall though and ignore the data. The
// signatureMatch() method explicitly ignores the signature
// check for packages installed on the system partition, because
// such packages are signed with the platform cert instead of
// the app developer's cert, so they're different on every
// device.
if (AppBackupUtils.signaturesMatch(signatures, pkgInfo, pmi)) {
if ((pkgInfo.applicationInfo.flags
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
Slog.i(TAG, "Package has restoreAnyVersion; taking data");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_RESTORE_ANY_VERSION,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
policy = RestorePolicy.ACCEPT;
} else if (pkgInfo.getLongVersionCode() >= info.version) {
Slog.i(TAG, "Sig + version match; taking data");
policy = RestorePolicy.ACCEPT;
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_VERSIONS_MATCH,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
} else {
// The data is from a newer version of the app than
// is presently installed. That means we can only
// use it if the matching apk is also supplied.
if (allowApks) {
Slog.i(TAG, "Data version " + info.version
+ " is newer than installed "
+ "version "
+ pkgInfo.getLongVersionCode()
+ " - requiring apk");
policy = RestorePolicy.ACCEPT_IF_APK;
} else {
Slog.i(TAG, "Data requires newer version "
+ info.version + "; ignoring");
mMonitor = BackupManagerMonitorUtils
.monitorEvent(mMonitor,
LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils
.putMonitoringExtra(
null,
EXTRA_LOG_OLD_VERSION,
info.version));
policy = RestorePolicy.IGNORE;
}
}
} else {
Slog.w(TAG, "Restore manifest signatures do not match "
+ "installed application for "
+ info.packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
}
} else {
Slog.w(TAG, "Package " + info.packageName
+ " is system level with no agent");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
pkgInfo,
LOG_EVENT_CATEGORY_AGENT,
null);
}
} else {
if (DEBUG) {
Slog.i(TAG,
"Restore manifest from " + info.packageName + " but allowBackup=false");
}
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
pkgInfo,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
null);
}
} catch (PackageManager.NameNotFoundException e) {
// Okay, the target app isn't installed. We can process
// the restore properly only if the dataset provides the
// apk file and we can successfully install it.
if (allowApks) {
if (DEBUG) {
Slog.i(TAG, "Package " + info.packageName
+ " not installed; requiring apk in dataset");
}
policy = RestorePolicy.ACCEPT_IF_APK;
} else {
policy = RestorePolicy.IGNORE;
}
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
monitoringExtras,
EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_APK_NOT_INSTALLED,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
}
if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) {
Slog.i(TAG, "Cannot restore package " + info.packageName
+ " without the matching .apk");
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
}
return policy;
}
// Given an actual file content size, consume the post-content padding mandated
// by the tar format.
public void skipTarPadding(long size) throws IOException {
long partial = (size + 512) % 512;
if (partial > 0) {
final int needed = 512 - (int) partial;
if (MORE_DEBUG) {
Slog.i(TAG, "Skipping tar padding: " + needed + " bytes");
}
byte[] buffer = new byte[needed];
if (readExactly(mInputStream, buffer, 0, needed) == needed) {
mBytesReadListener.onBytesRead(needed);
} else {
throw new IOException("Unexpected EOF in padding");
}
}
}
/**
* Read a widget metadata file, returning the restored blob.
*/
public void readMetadata(FileMetadata info) throws IOException {
// Fail on suspiciously large widget dump files
if (info.size > 64 * 1024) {
throw new IOException("Metadata too big; corrupt? size=" + info.size);
}
byte[] buffer = new byte[(int) info.size];
if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
mBytesReadListener.onBytesRead(info.size);
} else {
throw new IOException("Unexpected EOF in widget data");
}
String[] str = new String[1];
int offset = extractLine(buffer, 0, str);
int version = Integer.parseInt(str[0]);
if (version == BACKUP_MANIFEST_VERSION) {
offset = extractLine(buffer, offset, str);
final String pkg = str[0];
if (info.packageName.equals(pkg)) {
// Data checks out -- the rest of the buffer is a concatenation of
// binary blobs as described in the comment at writeAppWidgetData()
ByteArrayInputStream bin = new ByteArrayInputStream(buffer,
offset, buffer.length - offset);
DataInputStream in = new DataInputStream(bin);
while (bin.available() > 0) {
int token = in.readInt();
int size = in.readInt();
if (size > 64 * 1024) {
throw new IOException("Datum " + Integer.toHexString(token)
+ " too big; corrupt? size=" + info.size);
}
switch (token) {
case BACKUP_WIDGET_METADATA_TOKEN: {
if (MORE_DEBUG) {
Slog.i(TAG, "Got widget metadata for " + info.packageName);
}
mWidgetData = new byte[size];
in.read(mWidgetData);
break;
}
default: {
if (DEBUG) {
Slog.i(TAG, "Ignoring metadata blob " + Integer.toHexString(token)
+ " for " + info.packageName);
}
in.skipBytes(size);
break;
}
}
}
} else {
Slog.w(TAG,
"Metadata mismatch: package " + info.packageName + " but widget data for "
+ pkg);
Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
}
} else {
Slog.w(TAG, "Unsupported metadata version " + version);
Bundle monitoringExtras = BackupManagerMonitorUtils
.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
info.packageName);
monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
mMonitor = BackupManagerMonitorUtils.monitorEvent(
mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION,
null,
LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
monitoringExtras);
}
}
/**
* Builds a line from a byte buffer starting at 'offset'.
*
* @param buffer - where to read a line from.
* @param offset - offset in buffer to read a line from.
* @param outStr - an output parameter, the result will be put in outStr.
* @return the index of the next unconsumed data in the buffer.
* @throws IOException in case of an error.
*/
private static int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException {
final int end = buffer.length;
if (offset >= end) {
throw new IOException("Incomplete data");
}
int pos;
for (pos = offset; pos < end; pos++) {
byte c = buffer[pos];
// at LF we declare end of line, and return the next char as the
// starting point for the next time through
if (c == '\n') {
break;
}
}
outStr[0] = new String(buffer, offset, pos - offset);
pos++; // may be pointing an extra byte past the end but that's okay
return pos;
}
private boolean readTarHeader(byte[] block) throws IOException {
final int got = readExactly(mInputStream, block, 0, 512);
if (got == 0) {
return false; // Clean EOF
}
if (got < 512) {
throw new IOException("Unable to read full block header");
}
mBytesReadListener.onBytesRead(512);
return true;
}
// overwrites 'info' fields based on the pax extended header
private boolean readPaxExtendedHeader(FileMetadata info)
throws IOException {
// We should never see a pax extended header larger than this
if (info.size > 32 * 1024) {
Slog.w(TAG, "Suspiciously large pax header size " + info.size + " - aborting");
throw new IOException("Sanity failure: pax header size " + info.size);
}
// read whole blocks, not just the content size
int numBlocks = (int) ((info.size + 511) >> 9);
byte[] data = new byte[numBlocks * 512];
if (readExactly(mInputStream, data, 0, data.length) < data.length) {
throw new IOException("Unable to read full pax header");
}
mBytesReadListener.onBytesRead(data.length);
final int contentSize = (int) info.size;
int offset = 0;
do {
// extract the line at 'offset'
int eol = offset + 1;
while (eol < contentSize && data[eol] != ' ') {
eol++;
}
if (eol >= contentSize) {
// error: we just hit EOD looking for the end of the size field
throw new IOException("Invalid pax data");
}
// eol points to the space between the count and the key
int linelen = (int) extractRadix(data, offset, eol - offset, 10);
int key = eol + 1; // start of key=value
eol = offset + linelen - 1; // trailing LF
int value;
for (value = key + 1; data[value] != '=' && value <= eol; value++) {
;
}
if (value > eol) {
throw new IOException("Invalid pax declaration");
}
// pax requires that key/value strings be in UTF-8
String keyStr = new String(data, key, value - key, "UTF-8");
// -1 to strip the trailing LF
String valStr = new String(data, value + 1, eol - value - 1, "UTF-8");
if ("path".equals(keyStr)) {
info.path = valStr;
} else if ("size".equals(keyStr)) {
info.size = Long.parseLong(valStr);
} else {
if (DEBUG) {
Slog.i(TAG, "Unhandled pax key: " + key);
}
}
offset += linelen;
} while (offset < contentSize);
return true;
}
private static long extractRadix(byte[] data, int offset, int maxChars, int radix)
throws IOException {
long value = 0;
final int end = offset + maxChars;
for (int i = offset; i < end; i++) {
final byte b = data[i];
// Numeric fields in tar can terminate with either NUL or SPC
if (b == 0 || b == ' ') {
break;
}
if (b < '0' || b > ('0' + radix - 1)) {
throw new IOException("Invalid number in header: '" + (char) b
+ "' for radix " + radix);
}
value = radix * value + (b - '0');
}
return value;
}
private static String extractString(byte[] data, int offset, int maxChars) throws IOException {
final int end = offset + maxChars;
int eos = offset;
// tar string fields terminate early with a NUL
while (eos < end && data[eos] != 0) {
eos++;
}
return new String(data, offset, eos - offset, "US-ASCII");
}
private static void hexLog(byte[] block) {
int offset = 0;
int remaining = block.length;
StringBuilder buf = new StringBuilder(64);
while (remaining > 0) {
buf.append(String.format("%04x ", offset));
int numThisLine = (remaining > 16) ? 16 : remaining;
for (int i = 0; i < numThisLine; i++) {
buf.append(String.format("%02x ", block[offset + i]));
}
Slog.i("hexdump", buf.toString());
buf.setLength(0);
remaining -= numThisLine;
offset += numThisLine;
}
}
public IBackupManagerMonitor getMonitor() {
return mMonitor;
}
public byte[] getWidgetData() {
return mWidgetData;
}
}