public boolean restoreOneFile()

in com/android/server/backup/restore/FullRestoreEngine.java [157:566]


    public boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer,
            PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) {
        if (!isRunning()) {
            Slog.w(TAG, "Restore engine used after halting");
            return false;
        }

        BytesReadListener bytesReadListener = new BytesReadListener() {
            @Override
            public void onBytesRead(long bytesRead) {
                mBytes += bytesRead;
            }
        };

        TarBackupReader tarBackupReader = new TarBackupReader(instream,
                bytesReadListener, monitor);

        FileMetadata info;
        try {
            if (MORE_DEBUG) {
                Slog.v(TAG, "Reading tar header for restoring file");
            }
            info = tarBackupReader.readTarHeaders();
            if (info != null) {
                if (MORE_DEBUG) {
                    info.dump();
                }

                final String pkg = info.packageName;
                if (!pkg.equals(mAgentPackage)) {
                    // In the single-package case, it's a semantic error to expect
                    // one app's data but see a different app's on the wire
                    if (onlyPackage != null) {
                        if (!pkg.equals(onlyPackage.packageName)) {
                            Slog.w(TAG, "Expected data for " + onlyPackage + " but saw " + pkg);
                            setResult(RestoreEngine.TRANSPORT_FAILURE);
                            setRunning(false);
                            return false;
                        }
                    }

                    // okay, change in package; set up our various
                    // bookkeeping if we haven't seen it yet
                    if (!mPackagePolicies.containsKey(pkg)) {
                        mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                    }

                    // Clean up the previous agent relationship if necessary,
                    // and let the observer know we're considering a new app.
                    if (mAgent != null) {
                        if (DEBUG) {
                            Slog.d(TAG, "Saw new package; finalizing old one");
                        }
                        // Now we're really done
                        tearDownPipes();
                        tearDownAgent(mTargetApp);
                        mTargetApp = null;
                        mAgentPackage = null;
                    }
                }

                if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
                    Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
                            info);
                    PackageManagerInternal pmi = LocalServices.getService(
                            PackageManagerInternal.class);
                    RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
                            mBackupManagerService.getPackageManager(), allowApks, info, signatures,
                            pmi);
                    mManifestSignatures.put(info.packageName, signatures);
                    mPackagePolicies.put(pkg, restorePolicy);
                    mPackageInstallers.put(pkg, info.installerPackageName);
                    // We've read only the manifest content itself at this point,
                    // so consume the footer before looping around to the next
                    // input file
                    tarBackupReader.skipTarPadding(info.size);
                    mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
                } else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
                    // Metadata blobs!
                    tarBackupReader.readMetadata(info);

                    // The following only exist because we want to keep refactoring as safe as
                    // possible, without changing too much.
                    // TODO: Refactor, so that there are no funny things like this.
                    // This is read during TarBackupReader.readMetadata().
                    mWidgetData = tarBackupReader.getWidgetData();
                    // This can be nulled during TarBackupReader.readMetadata().
                    monitor = tarBackupReader.getMonitor();

                    tarBackupReader.skipTarPadding(info.size);
                } else {
                    // Non-manifest, so it's actual file data.  Is this a package
                    // we're ignoring?
                    boolean okay = true;
                    RestorePolicy policy = mPackagePolicies.get(pkg);
                    switch (policy) {
                        case IGNORE:
                            okay = false;
                            break;

                        case ACCEPT_IF_APK:
                            // If we're in accept-if-apk state, then the first file we
                            // see MUST be the apk.
                            if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
                                if (DEBUG) {
                                    Slog.d(TAG, "APK file; installing");
                                }
                                // Try to install the app.
                                String installerPackageName = mPackageInstallers.get(pkg);
                                boolean isSuccessfullyInstalled = RestoreUtils.installApk(
                                        instream, mBackupManagerService.getContext(),
                                        mDeleteObserver, mManifestSignatures,
                                        mPackagePolicies, info, installerPackageName,
                                        bytesReadListener);
                                // good to go; promote to ACCEPT
                                mPackagePolicies.put(pkg, isSuccessfullyInstalled
                                        ? RestorePolicy.ACCEPT
                                        : RestorePolicy.IGNORE);
                                // At this point we've consumed this file entry
                                // ourselves, so just strip the tar footer and
                                // go on to the next file in the input stream
                                tarBackupReader.skipTarPadding(info.size);
                                return true;
                            } else {
                                // File data before (or without) the apk.  We can't
                                // handle it coherently in this case so ignore it.
                                mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                                okay = false;
                            }
                            break;

                        case ACCEPT:
                            if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
                                if (DEBUG) {
                                    Slog.d(TAG, "apk present but ACCEPT");
                                }
                                // we can take the data without the apk, so we
                                // *want* to do so.  skip the apk by declaring this
                                // one file not-okay without changing the restore
                                // policy for the package.
                                okay = false;
                            }
                            break;

                        default:
                            // Something has gone dreadfully wrong when determining
                            // the restore policy from the manifest.  Ignore the
                            // rest of this package's data.
                            Slog.e(TAG, "Invalid policy from manifest");
                            okay = false;
                            mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                            break;
                    }

                    // Is it a *file* we need to drop or is it not a canonical path?
                    if (!isRestorableFile(info) || !isCanonicalFilePath(info.path)) {
                        okay = false;
                    }

                    // If the policy is satisfied, go ahead and set up to pipe the
                    // data to the agent.
                    if (MORE_DEBUG && okay && mAgent != null) {
                        Slog.i(TAG, "Reusing existing agent instance");
                    }
                    if (okay && mAgent == null) {
                        if (MORE_DEBUG) {
                            Slog.d(TAG, "Need to launch agent for " + pkg);
                        }

                        try {
                            mTargetApp =
                                    mBackupManagerService.getPackageManager().getApplicationInfo(
                                            pkg, 0);

                            // If we haven't sent any data to this app yet, we probably
                            // need to clear it first. Check that.
                            if (!mClearedPackages.contains(pkg)) {
                                // Apps with their own backup agents are responsible for coherently
                                // managing a full restore.
                                // In some rare cases they can't, especially in case of deferred
                                // restore. In this case check whether this app should be forced to
                                // clear up.
                                // TODO: Fix this properly with manifest parameter.
                                boolean forceClear = shouldForceClearAppDataOnFullRestore(
                                        mTargetApp.packageName);
                                if (mTargetApp.backupAgentName == null || forceClear) {
                                    if (DEBUG) {
                                        Slog.d(TAG,
                                                "Clearing app data preparatory to full restore");
                                    }
                                    mBackupManagerService.clearApplicationDataSynchronous(pkg, true);
                                } else {
                                    if (MORE_DEBUG) {
                                        Slog.d(TAG, "backup agent ("
                                                + mTargetApp.backupAgentName + ") => no clear");
                                    }
                                }
                                mClearedPackages.add(pkg);
                            } else {
                                if (MORE_DEBUG) {
                                    Slog.d(TAG, "We've initialized this app already; no clear "
                                            + "required");
                                }
                            }

                            // All set; now set up the IPC and launch the agent
                            setUpPipes();
                            mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
                                    ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
                            mAgentPackage = pkg;
                        } catch (IOException e) {
                            // fall through to error handling
                        } catch (NameNotFoundException e) {
                            // fall through to error handling
                        }

                        if (mAgent == null) {
                            Slog.e(TAG, "Unable to create agent for " + pkg);
                            okay = false;
                            tearDownPipes();
                            mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                        }
                    }

                    // Sanity check: make sure we never give data to the wrong app.  This
                    // should never happen but a little paranoia here won't go amiss.
                    if (okay && !pkg.equals(mAgentPackage)) {
                        Slog.e(TAG, "Restoring data for " + pkg
                                + " but agent is for " + mAgentPackage);
                        okay = false;
                    }

                    // At this point we have an agent ready to handle the full
                    // restore data as well as a pipe for sending data to
                    // that agent.  Tell the agent to start reading from the
                    // pipe.
                    if (okay) {
                        boolean agentSuccess = true;
                        long toCopy = info.size;
                        final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE);
                        final long timeout = isSharedStorage ?
                                mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis() :
                                mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
                        try {
                            mBackupManagerService.prepareOperationTimeout(token,
                                    timeout,
                                    mMonitorTask,
                                    OP_TYPE_RESTORE_WAIT);

                            if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
                                if (DEBUG) {
                                    Slog.d(TAG, "Restoring OBB file for " + pkg
                                            + " : " + info.path);
                                }
                                mObbConnection.restoreObbFile(pkg, mPipes[0],
                                        info.size, info.type, info.path, info.mode,
                                        info.mtime, token,
                                        mBackupManagerService.getBackupManagerBinder());
                            } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
                                // This is only possible during adb restore.
                                // TODO: Refactor to clearly separate the flows.
                                if (DEBUG) {
                                    Slog.d(TAG, "Restoring key-value file for " + pkg
                                            + " : " + info.path);
                                }
                                KeyValueAdbRestoreEngine restoreEngine =
                                        new KeyValueAdbRestoreEngine(
                                                mBackupManagerService,
                                                mBackupManagerService.getDataDir(), info, mPipes[0],
                                                mAgent, token);
                                new Thread(restoreEngine, "restore-key-value-runner").start();
                            } else {
                                if (MORE_DEBUG) {
                                    Slog.d(TAG, "Invoking agent to restore file " + info.path);
                                }
                                // fire up the app's agent listening on the socket.  If
                                // the agent is running in the system process we can't
                                // just invoke it asynchronously, so we provide a thread
                                // for it here.
                                if (mTargetApp.processName.equals("system")) {
                                    Slog.d(TAG, "system process agent - spinning a thread");
                                    RestoreFileRunnable runner = new RestoreFileRunnable(
                                            mBackupManagerService, mAgent, info, mPipes[0], token);
                                    new Thread(runner, "restore-sys-runner").start();
                                } else {
                                    mAgent.doRestoreFile(mPipes[0], info.size, info.type,
                                            info.domain, info.path, info.mode, info.mtime,
                                            token, mBackupManagerService.getBackupManagerBinder());
                                }
                            }
                        } catch (IOException e) {
                            // couldn't dup the socket for a process-local restore
                            Slog.d(TAG, "Couldn't establish restore");
                            agentSuccess = false;
                            okay = false;
                        } catch (RemoteException e) {
                            // whoops, remote entity went away.  We'll eat the content
                            // ourselves, then, and not copy it over.
                            Slog.e(TAG, "Agent crashed during full restore");
                            agentSuccess = false;
                            okay = false;
                        }

                        // Copy over the data if the agent is still good
                        if (okay) {
                            if (MORE_DEBUG) {
                                Slog.v(TAG, "  copying to restore agent: " + toCopy + " bytes");
                            }
                            boolean pipeOkay = true;
                            FileOutputStream pipe = new FileOutputStream(
                                    mPipes[1].getFileDescriptor());
                            while (toCopy > 0) {
                                int toRead = (toCopy > buffer.length)
                                        ? buffer.length : (int) toCopy;
                                int nRead = instream.read(buffer, 0, toRead);
                                if (nRead >= 0) {
                                    mBytes += nRead;
                                }
                                if (nRead <= 0) {
                                    break;
                                }
                                toCopy -= nRead;

                                // send it to the output pipe as long as things
                                // are still good
                                if (pipeOkay) {
                                    try {
                                        pipe.write(buffer, 0, nRead);
                                    } catch (IOException e) {
                                        Slog.e(TAG, "Failed to write to restore pipe: "
                                                + e.getMessage());
                                        pipeOkay = false;
                                    }
                                }
                            }

                            // done sending that file!  Now we just need to consume
                            // the delta from info.size to the end of block.
                            tarBackupReader.skipTarPadding(info.size);

                            // and now that we've sent it all, wait for the remote
                            // side to acknowledge receipt
                            agentSuccess = mBackupManagerService.waitUntilOperationComplete(token);
                        }

                        // okay, if the remote end failed at any point, deal with
                        // it by ignoring the rest of the restore on it
                        if (!agentSuccess) {
                            Slog.w(TAG, "Agent failure restoring " + pkg + "; ending restore");
                            mBackupManagerService.getBackupHandler().removeMessages(
                                    MSG_RESTORE_OPERATION_TIMEOUT);
                            tearDownPipes();
                            tearDownAgent(mTargetApp);
                            mAgent = null;
                            mPackagePolicies.put(pkg, RestorePolicy.IGNORE);

                            // If this was a single-package restore, we halt immediately
                            // with an agent error under these circumstances
                            if (onlyPackage != null) {
                                setResult(RestoreEngine.TARGET_FAILURE);
                                setRunning(false);
                                return false;
                            }
                        }
                    }

                    // Problems setting up the agent communication, an explicitly
                    // dropped file, or an already-ignored package: skip to the
                    // next stream entry by reading and discarding this file.
                    if (!okay) {
                        if (MORE_DEBUG) {
                            Slog.d(TAG, "[discarding file content]");
                        }
                        long bytesToConsume = (info.size + 511) & ~511;
                        while (bytesToConsume > 0) {
                            int toRead = (bytesToConsume > buffer.length)
                                    ? buffer.length : (int) bytesToConsume;
                            long nRead = instream.read(buffer, 0, toRead);
                            if (nRead >= 0) {
                                mBytes += nRead;
                            }
                            if (nRead <= 0) {
                                break;
                            }
                            bytesToConsume -= nRead;
                        }
                    }
                }
            }
        } catch (IOException e) {
            if (DEBUG) {
                Slog.w(TAG, "io exception on restore socket read: " + e.getMessage());
            }
            setResult(RestoreEngine.TRANSPORT_FAILURE);
            info = null;
        }

        // If we got here we're either running smoothly or we've finished
        if (info == null) {
            if (MORE_DEBUG) {
                Slog.i(TAG, "No [more] data for this package; tearing down");
            }
            tearDownPipes();
            setRunning(false);
            if (mustKillAgent) {
                tearDownAgent(mTargetApp);
            }
        }
        return (info != null);
    }