Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.backup.utils;
     18 
     19 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
     20 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION;
     21 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_MANIFEST_PACKAGE_NAME;
     22 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_OLD_VERSION;
     23 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_POLICY_ALLOW_APKS;
     24 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT;
     25 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
     26 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED;
     27 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK;
     28 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE;
     29 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE;
     30 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH;
     31 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE;
     32 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
     33 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
     34 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
     35 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
     36 
     37 import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_FILENAME;
     38 import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_VERSION;
     39 import static com.android.server.backup.BackupManagerService.BACKUP_METADATA_FILENAME;
     40 import static com.android.server.backup.BackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
     41 import static com.android.server.backup.BackupManagerService.DEBUG;
     42 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
     43 import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
     44 import static com.android.server.backup.BackupManagerService.TAG;
     45 
     46 import android.app.backup.BackupAgent;
     47 import android.app.backup.BackupManagerMonitor;
     48 import android.app.backup.FullBackup;
     49 import android.app.backup.IBackupManagerMonitor;
     50 import android.content.pm.ApplicationInfo;
     51 import android.content.pm.PackageInfo;
     52 import android.content.pm.PackageManager;
     53 import android.content.pm.PackageManagerInternal;
     54 import android.content.pm.Signature;
     55 import android.os.Bundle;
     56 import android.os.Process;
     57 import android.util.Slog;
     58 
     59 import com.android.server.backup.FileMetadata;
     60 import com.android.server.backup.restore.RestorePolicy;
     61 
     62 import java.io.ByteArrayInputStream;
     63 import java.io.DataInputStream;
     64 import java.io.IOException;
     65 import java.io.InputStream;
     66 
     67 /**
     68  * Utility methods to read backup tar file.
     69  */
     70 public class TarBackupReader {
     71     private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
     72     private static final int TAR_HEADER_LENGTH_PATH = 100;
     73     private static final int TAR_HEADER_OFFSET_PATH = 0;
     74     private static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
     75     private static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
     76     private static final int TAR_HEADER_LENGTH_MODE = 8;
     77     private static final int TAR_HEADER_OFFSET_MODE = 100;
     78     private static final int TAR_HEADER_LENGTH_MODTIME = 12;
     79     private static final int TAR_HEADER_OFFSET_MODTIME = 136;
     80     private static final int TAR_HEADER_LENGTH_FILESIZE = 12;
     81     private static final int TAR_HEADER_OFFSET_FILESIZE = 124;
     82     private static final int TAR_HEADER_LONG_RADIX = 8;
     83 
     84     private final InputStream mInputStream;
     85     private final BytesReadListener mBytesReadListener;
     86 
     87     private IBackupManagerMonitor mMonitor;
     88 
     89     // Widget blob to be restored out-of-band.
     90     private byte[] mWidgetData = null;
     91 
     92     public TarBackupReader(InputStream inputStream, BytesReadListener bytesReadListener,
     93             IBackupManagerMonitor monitor) {
     94         mInputStream = inputStream;
     95         mBytesReadListener = bytesReadListener;
     96         mMonitor = monitor;
     97     }
     98 
     99     /**
    100      * Consumes a tar file header block [sequence] and accumulates the relevant metadata.
    101      */
    102     public FileMetadata readTarHeaders() throws IOException {
    103         byte[] block = new byte[512];
    104         FileMetadata info = null;
    105 
    106         boolean gotHeader = readTarHeader(block);
    107         if (gotHeader) {
    108             try {
    109                 // okay, presume we're okay, and extract the various metadata
    110                 info = new FileMetadata();
    111                 info.size = extractRadix(block,
    112                         TAR_HEADER_OFFSET_FILESIZE,
    113                         TAR_HEADER_LENGTH_FILESIZE,
    114                         TAR_HEADER_LONG_RADIX);
    115                 info.mtime = extractRadix(block,
    116                         TAR_HEADER_OFFSET_MODTIME,
    117                         TAR_HEADER_LENGTH_MODTIME,
    118                         TAR_HEADER_LONG_RADIX);
    119                 info.mode = extractRadix(block,
    120                         TAR_HEADER_OFFSET_MODE,
    121                         TAR_HEADER_LENGTH_MODE,
    122                         TAR_HEADER_LONG_RADIX);
    123 
    124                 info.path = extractString(block,
    125                         TAR_HEADER_OFFSET_PATH_PREFIX,
    126                         TAR_HEADER_LENGTH_PATH_PREFIX);
    127                 String path = extractString(block,
    128                         TAR_HEADER_OFFSET_PATH,
    129                         TAR_HEADER_LENGTH_PATH);
    130                 if (path.length() > 0) {
    131                     if (info.path.length() > 0) {
    132                         info.path += '/';
    133                     }
    134                     info.path += path;
    135                 }
    136 
    137                 // tar link indicator field: 1 byte at offset 156 in the header.
    138                 int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
    139                 if (typeChar == 'x') {
    140                     // pax extended header, so we need to read that
    141                     gotHeader = readPaxExtendedHeader(info);
    142                     if (gotHeader) {
    143                         // and after a pax extended header comes another real header -- read
    144                         // that to find the real file type
    145                         gotHeader = readTarHeader(block);
    146                     }
    147                     if (!gotHeader) {
    148                         throw new IOException("Bad or missing pax header");
    149                     }
    150 
    151                     typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
    152                 }
    153 
    154                 switch (typeChar) {
    155                     case '0':
    156                         info.type = BackupAgent.TYPE_FILE;
    157                         break;
    158                     case '5': {
    159                         info.type = BackupAgent.TYPE_DIRECTORY;
    160                         if (info.size != 0) {
    161                             Slog.w(TAG, "Directory entry with nonzero size in header");
    162                             info.size = 0;
    163                         }
    164                         break;
    165                     }
    166                     case 0: {
    167                         // presume EOF
    168                         if (MORE_DEBUG) {
    169                             Slog.w(TAG, "Saw type=0 in tar header block, info=" + info);
    170                         }
    171                         return null;
    172                     }
    173                     default: {
    174                         Slog.e(TAG, "Unknown tar entity type: " + typeChar);
    175                         throw new IOException("Unknown entity type " + typeChar);
    176                     }
    177                 }
    178 
    179                 // Parse out the path
    180                 //
    181                 // first: apps/shared/unrecognized
    182                 if (FullBackup.SHARED_PREFIX.regionMatches(0,
    183                         info.path, 0, FullBackup.SHARED_PREFIX.length())) {
    184                     // File in shared storage.  !!! TODO: implement this.
    185                     info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
    186                     info.packageName = SHARED_BACKUP_AGENT_PACKAGE;
    187                     info.domain = FullBackup.SHARED_STORAGE_TOKEN;
    188                     if (DEBUG) {
    189                         Slog.i(TAG, "File in shared storage: " + info.path);
    190                     }
    191                 } else if (FullBackup.APPS_PREFIX.regionMatches(0,
    192                         info.path, 0, FullBackup.APPS_PREFIX.length())) {
    193                     // App content!  Parse out the package name and domain
    194 
    195                     // strip the apps/ prefix
    196                     info.path = info.path.substring(FullBackup.APPS_PREFIX.length());
    197 
    198                     // extract the package name
    199                     int slash = info.path.indexOf('/');
    200                     if (slash < 0) {
    201                         throw new IOException("Illegal semantic path in " + info.path);
    202                     }
    203                     info.packageName = info.path.substring(0, slash);
    204                     info.path = info.path.substring(slash + 1);
    205 
    206                     // if it's a manifest or metadata payload we're done, otherwise parse
    207                     // out the domain into which the file will be restored
    208                     if (!info.path.equals(BACKUP_MANIFEST_FILENAME) &&
    209                             !info.path.equals(BACKUP_METADATA_FILENAME)) {
    210                         slash = info.path.indexOf('/');
    211                         if (slash < 0) {
    212                             throw new IOException("Illegal semantic path in non-manifest "
    213                                     + info.path);
    214                         }
    215                         info.domain = info.path.substring(0, slash);
    216                         info.path = info.path.substring(slash + 1);
    217                     }
    218                 }
    219             } catch (IOException e) {
    220                 if (DEBUG) {
    221                     Slog.e(TAG, "Parse error in header: " + e.getMessage());
    222                     if (MORE_DEBUG) {
    223                         hexLog(block);
    224                     }
    225                 }
    226                 throw e;
    227             }
    228         }
    229         return info;
    230     }
    231 
    232     /**
    233      * Tries to read exactly the given number of bytes into a buffer at the stated offset.
    234      *
    235      * @param in - input stream to read bytes from..
    236      * @param buffer - where to write bytes to.
    237      * @param offset - offset in buffer to write bytes to.
    238      * @param size - number of bytes to read.
    239      * @return number of bytes actually read.
    240      * @throws IOException in case of an error.
    241      */
    242     private static int readExactly(InputStream in, byte[] buffer, int offset, int size)
    243             throws IOException {
    244         if (size <= 0) {
    245             throw new IllegalArgumentException("size must be > 0");
    246         }
    247         if (MORE_DEBUG) {
    248             Slog.i(TAG, "  ... readExactly(" + size + ") called");
    249         }
    250         int soFar = 0;
    251         while (soFar < size) {
    252             int nRead = in.read(buffer, offset + soFar, size - soFar);
    253             if (nRead <= 0) {
    254                 if (MORE_DEBUG) {
    255                     Slog.w(TAG, "- wanted exactly " + size + " but got only " + soFar);
    256                 }
    257                 break;
    258             }
    259             soFar += nRead;
    260             if (MORE_DEBUG) {
    261                 Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soFar));
    262             }
    263         }
    264         return soFar;
    265     }
    266 
    267     /**
    268      * Reads app manifest, filling version and hasApk fields in the metadata, and returns array of
    269      * signatures.
    270      *
    271      * @param info - file metadata.
    272      * @return array of signatures or null, in case of an error.
    273      * @throws IOException in case of an error.
    274      */
    275     public Signature[] readAppManifestAndReturnSignatures(FileMetadata info)
    276             throws IOException {
    277         // Fail on suspiciously large manifest files
    278         if (info.size > 64 * 1024) {
    279             throw new IOException("Restore manifest too big; corrupt? size=" + info.size);
    280         }
    281 
    282         byte[] buffer = new byte[(int) info.size];
    283         if (MORE_DEBUG) {
    284             Slog.i(TAG,
    285                     "   readAppManifestAndReturnSignatures() looking for " + info.size + " bytes");
    286         }
    287         if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
    288             mBytesReadListener.onBytesRead(info.size);
    289         } else {
    290             throw new IOException("Unexpected EOF in manifest");
    291         }
    292 
    293         String[] str = new String[1];
    294         int offset = 0;
    295 
    296         try {
    297             offset = extractLine(buffer, offset, str);
    298             int version = Integer.parseInt(str[0]);
    299             if (version == BACKUP_MANIFEST_VERSION) {
    300                 offset = extractLine(buffer, offset, str);
    301                 String manifestPackage = str[0];
    302                 // TODO: handle <original-package>
    303                 if (manifestPackage.equals(info.packageName)) {
    304                     offset = extractLine(buffer, offset, str);
    305                     info.version = Integer.parseInt(str[0]);  // app version
    306                     offset = extractLine(buffer, offset, str);
    307                     // This is the platform version, which we don't use, but we parse it
    308                     // as a safety against corruption in the manifest.
    309                     Integer.parseInt(str[0]);
    310                     offset = extractLine(buffer, offset, str);
    311                     info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
    312                     offset = extractLine(buffer, offset, str);
    313                     info.hasApk = str[0].equals("1");
    314                     offset = extractLine(buffer, offset, str);
    315                     int numSigs = Integer.parseInt(str[0]);
    316                     if (numSigs > 0) {
    317                         Signature[] sigs = new Signature[numSigs];
    318                         for (int i = 0; i < numSigs; i++) {
    319                             offset = extractLine(buffer, offset, str);
    320                             sigs[i] = new Signature(str[0]);
    321                         }
    322                         return sigs;
    323                     } else {
    324                         Slog.i(TAG, "Missing signature on backed-up package " + info.packageName);
    325                         mMonitor = BackupManagerMonitorUtils.monitorEvent(
    326                                 mMonitor,
    327                                 LOG_EVENT_ID_MISSING_SIGNATURE,
    328                                 null,
    329                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    330                                 BackupManagerMonitorUtils.putMonitoringExtra(null,
    331                                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
    332                     }
    333                 } else {
    334                     Slog.i(TAG, "Expected package " + info.packageName
    335                             + " but restore manifest claims " + manifestPackage);
    336                     Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
    337                             EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
    338                     monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
    339                             monitoringExtras,
    340                             EXTRA_LOG_MANIFEST_PACKAGE_NAME, manifestPackage);
    341                     mMonitor = BackupManagerMonitorUtils.monitorEvent(
    342                             mMonitor,
    343                             LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE,
    344                             null,
    345                             LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    346                             monitoringExtras);
    347                 }
    348             } else {
    349                 Slog.i(TAG, "Unknown restore manifest version " + version
    350                         + " for package " + info.packageName);
    351                 Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
    352                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
    353                 monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
    354                         EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
    355                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
    356                         mMonitor,
    357                         BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION,
    358                         null,
    359                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    360                         monitoringExtras);
    361 
    362             }
    363         } catch (NumberFormatException e) {
    364             Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
    365             mMonitor = BackupManagerMonitorUtils.monitorEvent(
    366                     mMonitor,
    367                     BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST,
    368                     null,
    369                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    370                     BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
    371                             info.packageName));
    372         } catch (IllegalArgumentException e) {
    373             Slog.w(TAG, e.getMessage());
    374         }
    375 
    376         return null;
    377     }
    378 
    379     /**
    380      * Chooses restore policy.
    381      *
    382      * @param packageManager - PackageManager instance.
    383      * @param allowApks - allow restore set to include apks.
    384      * @param info - file metadata.
    385      * @param signatures - array of signatures parsed from backup file.
    386      * @return a restore policy constant.
    387      */
    388     public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
    389             boolean allowApks, FileMetadata info, Signature[] signatures,
    390             PackageManagerInternal pmi) {
    391         if (signatures == null) {
    392             return RestorePolicy.IGNORE;
    393         }
    394 
    395         RestorePolicy policy = RestorePolicy.IGNORE;
    396 
    397         // Okay, got the manifest info we need...
    398         try {
    399             PackageInfo pkgInfo = packageManager.getPackageInfo(
    400                     info.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
    401             // Fall through to IGNORE if the app explicitly disallows backup
    402             final int flags = pkgInfo.applicationInfo.flags;
    403             if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
    404                 // Restore system-uid-space packages only if they have
    405                 // defined a custom backup agent
    406                 if ((pkgInfo.applicationInfo.uid
    407                         >= Process.FIRST_APPLICATION_UID)
    408                         || (pkgInfo.applicationInfo.backupAgentName != null)) {
    409                     // Verify signatures against any installed version; if they
    410                     // don't match, then we fall though and ignore the data.  The
    411                     // signatureMatch() method explicitly ignores the signature
    412                     // check for packages installed on the system partition, because
    413                     // such packages are signed with the platform cert instead of
    414                     // the app developer's cert, so they're different on every
    415                     // device.
    416                     if (AppBackupUtils.signaturesMatch(signatures, pkgInfo, pmi)) {
    417                         if ((pkgInfo.applicationInfo.flags
    418                                 & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
    419                             Slog.i(TAG, "Package has restoreAnyVersion; taking data");
    420                             mMonitor = BackupManagerMonitorUtils.monitorEvent(
    421                                     mMonitor,
    422                                     LOG_EVENT_ID_RESTORE_ANY_VERSION,
    423                                     pkgInfo,
    424                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    425                                     null);
    426                             policy = RestorePolicy.ACCEPT;
    427                         } else if (pkgInfo.getLongVersionCode() >= info.version) {
    428                             Slog.i(TAG, "Sig + version match; taking data");
    429                             policy = RestorePolicy.ACCEPT;
    430                             mMonitor = BackupManagerMonitorUtils.monitorEvent(
    431                                     mMonitor,
    432                                     LOG_EVENT_ID_VERSIONS_MATCH,
    433                                     pkgInfo,
    434                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    435                                     null);
    436                         } else {
    437                             // The data is from a newer version of the app than
    438                             // is presently installed.  That means we can only
    439                             // use it if the matching apk is also supplied.
    440                             if (allowApks) {
    441                                 Slog.i(TAG, "Data version " + info.version
    442                                         + " is newer than installed "
    443                                         + "version "
    444                                         + pkgInfo.getLongVersionCode()
    445                                         + " - requiring apk");
    446                                 policy = RestorePolicy.ACCEPT_IF_APK;
    447                             } else {
    448                                 Slog.i(TAG, "Data requires newer version "
    449                                         + info.version + "; ignoring");
    450                                 mMonitor = BackupManagerMonitorUtils
    451                                         .monitorEvent(mMonitor,
    452                                                 LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
    453                                                 pkgInfo,
    454                                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    455                                                 BackupManagerMonitorUtils
    456                                                         .putMonitoringExtra(
    457                                                                 null,
    458                                                                 EXTRA_LOG_OLD_VERSION,
    459                                                                 info.version));
    460 
    461                                 policy = RestorePolicy.IGNORE;
    462                             }
    463                         }
    464                     } else {
    465                         Slog.w(TAG, "Restore manifest signatures do not match "
    466                                 + "installed application for "
    467                                 + info.packageName);
    468                         mMonitor = BackupManagerMonitorUtils.monitorEvent(
    469                                 mMonitor,
    470                                 LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
    471                                 pkgInfo,
    472                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    473                                 null);
    474                     }
    475                 } else {
    476                     Slog.w(TAG, "Package " + info.packageName
    477                             + " is system level with no agent");
    478                     mMonitor = BackupManagerMonitorUtils.monitorEvent(
    479                             mMonitor,
    480                             LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
    481                             pkgInfo,
    482                             LOG_EVENT_CATEGORY_AGENT,
    483                             null);
    484                 }
    485             } else {
    486                 if (DEBUG) {
    487                     Slog.i(TAG,
    488                             "Restore manifest from " + info.packageName + " but allowBackup=false");
    489                 }
    490                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
    491                         mMonitor,
    492                         LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
    493                         pkgInfo,
    494                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    495                         null);
    496             }
    497         } catch (PackageManager.NameNotFoundException e) {
    498             // Okay, the target app isn't installed.  We can process
    499             // the restore properly only if the dataset provides the
    500             // apk file and we can successfully install it.
    501             if (allowApks) {
    502                 if (DEBUG) {
    503                     Slog.i(TAG, "Package " + info.packageName
    504                             + " not installed; requiring apk in dataset");
    505                 }
    506                 policy = RestorePolicy.ACCEPT_IF_APK;
    507             } else {
    508                 policy = RestorePolicy.IGNORE;
    509             }
    510             Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
    511                     null,
    512                     EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
    513             monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
    514                     monitoringExtras,
    515                     EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
    516             mMonitor = BackupManagerMonitorUtils.monitorEvent(
    517                     mMonitor,
    518                     LOG_EVENT_ID_APK_NOT_INSTALLED,
    519                     null,
    520                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    521                     monitoringExtras);
    522         }
    523 
    524         if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) {
    525             Slog.i(TAG, "Cannot restore package " + info.packageName
    526                     + " without the matching .apk");
    527             mMonitor = BackupManagerMonitorUtils.monitorEvent(
    528                     mMonitor,
    529                     LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
    530                     null,
    531                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    532                     BackupManagerMonitorUtils.putMonitoringExtra(null,
    533                             EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
    534         }
    535 
    536         return policy;
    537     }
    538 
    539     // Given an actual file content size, consume the post-content padding mandated
    540     // by the tar format.
    541     public void skipTarPadding(long size) throws IOException {
    542         long partial = (size + 512) % 512;
    543         if (partial > 0) {
    544             final int needed = 512 - (int) partial;
    545             if (MORE_DEBUG) {
    546                 Slog.i(TAG, "Skipping tar padding: " + needed + " bytes");
    547             }
    548             byte[] buffer = new byte[needed];
    549             if (readExactly(mInputStream, buffer, 0, needed) == needed) {
    550                 mBytesReadListener.onBytesRead(needed);
    551             } else {
    552                 throw new IOException("Unexpected EOF in padding");
    553             }
    554         }
    555     }
    556 
    557     /**
    558      * Read a widget metadata file, returning the restored blob.
    559      */
    560     public void readMetadata(FileMetadata info) throws IOException {
    561         // Fail on suspiciously large widget dump files
    562         if (info.size > 64 * 1024) {
    563             throw new IOException("Metadata too big; corrupt? size=" + info.size);
    564         }
    565 
    566         byte[] buffer = new byte[(int) info.size];
    567         if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
    568             mBytesReadListener.onBytesRead(info.size);
    569         } else {
    570             throw new IOException("Unexpected EOF in widget data");
    571         }
    572 
    573         String[] str = new String[1];
    574         int offset = extractLine(buffer, 0, str);
    575         int version = Integer.parseInt(str[0]);
    576         if (version == BACKUP_MANIFEST_VERSION) {
    577             offset = extractLine(buffer, offset, str);
    578             final String pkg = str[0];
    579             if (info.packageName.equals(pkg)) {
    580                 // Data checks out -- the rest of the buffer is a concatenation of
    581                 // binary blobs as described in the comment at writeAppWidgetData()
    582                 ByteArrayInputStream bin = new ByteArrayInputStream(buffer,
    583                         offset, buffer.length - offset);
    584                 DataInputStream in = new DataInputStream(bin);
    585                 while (bin.available() > 0) {
    586                     int token = in.readInt();
    587                     int size = in.readInt();
    588                     if (size > 64 * 1024) {
    589                         throw new IOException("Datum " + Integer.toHexString(token)
    590                                 + " too big; corrupt? size=" + info.size);
    591                     }
    592                     switch (token) {
    593                         case BACKUP_WIDGET_METADATA_TOKEN: {
    594                             if (MORE_DEBUG) {
    595                                 Slog.i(TAG, "Got widget metadata for " + info.packageName);
    596                             }
    597                             mWidgetData = new byte[size];
    598                             in.read(mWidgetData);
    599                             break;
    600                         }
    601                         default: {
    602                             if (DEBUG) {
    603                                 Slog.i(TAG, "Ignoring metadata blob " + Integer.toHexString(token)
    604                                         + " for " + info.packageName);
    605                             }
    606                             in.skipBytes(size);
    607                             break;
    608                         }
    609                     }
    610                 }
    611             } else {
    612                 Slog.w(TAG,
    613                         "Metadata mismatch: package " + info.packageName + " but widget data for "
    614                                 + pkg);
    615 
    616                 Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
    617                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
    618                 monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
    619                         BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
    620                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
    621                         mMonitor,
    622                         BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH,
    623                         null,
    624                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    625                         monitoringExtras);
    626             }
    627         } else {
    628             Slog.w(TAG, "Unsupported metadata version " + version);
    629 
    630             Bundle monitoringExtras = BackupManagerMonitorUtils
    631                     .putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
    632                             info.packageName);
    633             monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
    634                     EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
    635             mMonitor = BackupManagerMonitorUtils.monitorEvent(
    636                     mMonitor,
    637                     BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION,
    638                     null,
    639                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
    640                     monitoringExtras);
    641         }
    642     }
    643 
    644     /**
    645      * Builds a line from a byte buffer starting at 'offset'.
    646      *
    647      * @param buffer - where to read a line from.
    648      * @param offset - offset in buffer to read a line from.
    649      * @param outStr - an output parameter, the result will be put in outStr.
    650      * @return the index of the next unconsumed data in the buffer.
    651      * @throws IOException in case of an error.
    652      */
    653     private static int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException {
    654         final int end = buffer.length;
    655         if (offset >= end) {
    656             throw new IOException("Incomplete data");
    657         }
    658 
    659         int pos;
    660         for (pos = offset; pos < end; pos++) {
    661             byte c = buffer[pos];
    662             // at LF we declare end of line, and return the next char as the
    663             // starting point for the next time through
    664             if (c == '\n') {
    665                 break;
    666             }
    667         }
    668         outStr[0] = new String(buffer, offset, pos - offset);
    669         pos++;  // may be pointing an extra byte past the end but that's okay
    670         return pos;
    671     }
    672 
    673     private boolean readTarHeader(byte[] block) throws IOException {
    674         final int got = readExactly(mInputStream, block, 0, 512);
    675         if (got == 0) {
    676             return false;     // Clean EOF
    677         }
    678         if (got < 512) {
    679             throw new IOException("Unable to read full block header");
    680         }
    681         mBytesReadListener.onBytesRead(512);
    682         return true;
    683     }
    684 
    685     // overwrites 'info' fields based on the pax extended header
    686     private boolean readPaxExtendedHeader(FileMetadata info)
    687             throws IOException {
    688         // We should never see a pax extended header larger than this
    689         if (info.size > 32 * 1024) {
    690             Slog.w(TAG, "Suspiciously large pax header size " + info.size + " - aborting");
    691             throw new IOException("Sanity failure: pax header size " + info.size);
    692         }
    693 
    694         // read whole blocks, not just the content size
    695         int numBlocks = (int) ((info.size + 511) >> 9);
    696         byte[] data = new byte[numBlocks * 512];
    697         if (readExactly(mInputStream, data, 0, data.length) < data.length) {
    698             throw new IOException("Unable to read full pax header");
    699         }
    700         mBytesReadListener.onBytesRead(data.length);
    701 
    702         final int contentSize = (int) info.size;
    703         int offset = 0;
    704         do {
    705             // extract the line at 'offset'
    706             int eol = offset + 1;
    707             while (eol < contentSize && data[eol] != ' ') {
    708                 eol++;
    709             }
    710             if (eol >= contentSize) {
    711                 // error: we just hit EOD looking for the end of the size field
    712                 throw new IOException("Invalid pax data");
    713             }
    714             // eol points to the space between the count and the key
    715             int linelen = (int) extractRadix(data, offset, eol - offset, 10);
    716             int key = eol + 1;  // start of key=value
    717             eol = offset + linelen - 1; // trailing LF
    718             int value;
    719             for (value = key + 1; data[value] != '=' && value <= eol; value++) {
    720                 ;
    721             }
    722             if (value > eol) {
    723                 throw new IOException("Invalid pax declaration");
    724             }
    725 
    726             // pax requires that key/value strings be in UTF-8
    727             String keyStr = new String(data, key, value - key, "UTF-8");
    728             // -1 to strip the trailing LF
    729             String valStr = new String(data, value + 1, eol - value - 1, "UTF-8");
    730 
    731             if ("path".equals(keyStr)) {
    732                 info.path = valStr;
    733             } else if ("size".equals(keyStr)) {
    734                 info.size = Long.parseLong(valStr);
    735             } else {
    736                 if (DEBUG) {
    737                     Slog.i(TAG, "Unhandled pax key: " + key);
    738                 }
    739             }
    740 
    741             offset += linelen;
    742         } while (offset < contentSize);
    743 
    744         return true;
    745     }
    746 
    747     private static long extractRadix(byte[] data, int offset, int maxChars, int radix)
    748             throws IOException {
    749         long value = 0;
    750         final int end = offset + maxChars;
    751         for (int i = offset; i < end; i++) {
    752             final byte b = data[i];
    753             // Numeric fields in tar can terminate with either NUL or SPC
    754             if (b == 0 || b == ' ') {
    755                 break;
    756             }
    757             if (b < '0' || b > ('0' + radix - 1)) {
    758                 throw new IOException("Invalid number in header: '" + (char) b
    759                         + "' for radix " + radix);
    760             }
    761             value = radix * value + (b - '0');
    762         }
    763         return value;
    764     }
    765 
    766     private static String extractString(byte[] data, int offset, int maxChars) throws IOException {
    767         final int end = offset + maxChars;
    768         int eos = offset;
    769         // tar string fields terminate early with a NUL
    770         while (eos < end && data[eos] != 0) {
    771             eos++;
    772         }
    773         return new String(data, offset, eos - offset, "US-ASCII");
    774     }
    775 
    776     private static void hexLog(byte[] block) {
    777         int offset = 0;
    778         int todo = block.length;
    779         StringBuilder buf = new StringBuilder(64);
    780         while (todo > 0) {
    781             buf.append(String.format("%04x   ", offset));
    782             int numThisLine = (todo > 16) ? 16 : todo;
    783             for (int i = 0; i < numThisLine; i++) {
    784                 buf.append(String.format("%02x ", block[offset + i]));
    785             }
    786             Slog.i("hexdump", buf.toString());
    787             buf.setLength(0);
    788             todo -= numThisLine;
    789             offset += numThisLine;
    790         }
    791     }
    792 
    793     public IBackupManagerMonitor getMonitor() {
    794         return mMonitor;
    795     }
    796 
    797     public byte[] getWidgetData() {
    798         return mWidgetData;
    799     }
    800 }
    801