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