blob: 17a796fdd515afa6b0edc01bce45902252f27a9f [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/update_client/op_install.h"
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "components/crx_file/crx_verifier.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_cache.h"
#include "components/update_client/pipeline_util.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/task_traits.h"
#include "components/update_client/unpacker.h"
#include "components/update_client/unzipper.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"
#include "third_party/puffin/src/include/puffin/puffpatch.h"
namespace update_client {
namespace {
// The sequence of calls is:
//
// [Original Sequence] [Blocking Pool]
//
// CrxCache::Put (optional)
// Unpack
// Unpacker::Unpack
// Install
// InstallBlocking
// installer->Install
// CallbackChecker::Done
// [lambda to delete unpack path]
// InstallComplete
// [original callback]
// CallbackChecker ensures that a progress callback is not posted after a
// completion callback. It is only accessed and modified on the main sequence.
// Both callbacks maintain a reference to an instance of this class.
class CallbackChecker : public base::RefCountedThreadSafe<CallbackChecker> {
public:
CallbackChecker(
base::OnceCallback<void(const CrxInstaller::Result&)> callback,
CrxInstaller::ProgressCallback progress_callback)
: callback_(std::move(callback)), progress_callback_(progress_callback) {}
CallbackChecker(const CallbackChecker&) = delete;
CallbackChecker& operator=(const CallbackChecker&) = delete;
void Progress(int progress) { progress_callback_.Run(progress); }
void Done(const CrxInstaller::Result& result) {
progress_callback_ = base::DoNothing();
std::move(callback_).Run(result);
}
private:
friend class base::RefCountedThreadSafe<CallbackChecker>;
~CallbackChecker() = default;
base::OnceCallback<void(const CrxInstaller::Result&)> callback_;
CrxInstaller::ProgressCallback progress_callback_;
};
// Runs on the original sequence.
void InstallComplete(
base::OnceCallback<void(const CrxInstaller::Result&)>
installer_result_callback,
base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)>
callback,
base::RepeatingCallback<void(base::Value::Dict)> event_adder,
base::FilePath crx_file,
const CrxInstaller::Result& result) {
event_adder.Run(
MakeSimpleOperationEvent(result.result, protocol_request::kEventCrx3));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(installer_result_callback), result));
if (result.result.category != ErrorCategory::kNone) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), base::unexpected(result.result)));
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), crx_file));
}
// Runs in the blocking thread pool.
void InstallBlocking(
CrxInstaller::ProgressCallback progress_callback,
base::OnceCallback<void(const CrxInstaller::Result&)> callback,
const base::FilePath& unpack_path,
const std::string& public_key,
std::unique_ptr<CrxInstaller::InstallParams> install_params,
scoped_refptr<CrxInstaller> installer) {
installer->Install(unpack_path, public_key, std::move(install_params),
progress_callback, std::move(callback));
}
// Runs on the original sequence.
void Install(base::OnceCallback<void(const CrxInstaller::Result&)> callback,
std::unique_ptr<CrxInstaller::InstallParams> install_params,
scoped_refptr<CrxInstaller> installer,
CrxInstaller::ProgressCallback progress_callback,
const Unpacker::Result& result) {
if (result.error != UnpackerError::kNone) {
std::move(callback).Run(
CrxInstaller::Result({.category = ErrorCategory::kUnpack,
.code = static_cast<int>(result.error),
.extra = result.extended_error}));
return;
}
progress_callback.Run(-1);
// Prepare the callbacks. Delete unpack_path when the completion
// callback is called.
auto checker = base::MakeRefCounted<CallbackChecker>(
base::BindOnce(
[](base::OnceCallback<void(const CrxInstaller::Result&)> callback,
const base::FilePath& unpack_path,
const CrxInstaller::Result& result) {
base::ThreadPool::PostTaskAndReply(
FROM_HERE, kTaskTraits,
base::BindOnce(IgnoreResult(&base::DeletePathRecursively),
unpack_path),
base::BindOnce(std::move(callback), result));
},
std::move(callback), result.unpack_path),
progress_callback);
// Run installer.
base::ThreadPool::PostTask(
FROM_HERE, kTaskTraits,
base::BindOnce(&InstallBlocking,
base::BindPostTaskToCurrentDefault(base::BindRepeating(
&CallbackChecker::Progress, checker)),
base::BindPostTaskToCurrentDefault(
base::BindOnce(&CallbackChecker::Done, checker)),
result.unpack_path, result.public_key,
std::move(install_params), installer));
}
// Runs on the original sequence.
void Unpack(base::OnceCallback<void(const Unpacker::Result&)> callback,
const std::string& id,
const base::FilePath& crx_file,
std::unique_ptr<Unzipper> unzipper,
const std::vector<uint8_t>& pk_hash,
crx_file::VerifierFormat crx_format,
base::expected<base::FilePath, UnpackerError> cache_result) {
if (!cache_result.has_value()) {
// Caching is optional: continue with the install, but add a task to clean
// up crx_file.
callback = base::BindOnce(
[](const base::FilePath& crx_file,
base::OnceCallback<void(const Unpacker::Result&)> callback,
const Unpacker::Result& result) {
base::ThreadPool::PostTaskAndReply(
FROM_HERE, kTaskTraits,
base::BindOnce(IgnoreResult(&base::DeleteFile), crx_file),
base::BindOnce(std::move(callback), result));
},
crx_file, std::move(callback));
}
// Unpack the file.
base::ThreadPool::CreateSequencedTaskRunner(kTaskTraits)
->PostTask(
FROM_HERE,
base::BindOnce(
&Unpacker::Unpack, id, pk_hash,
// If and only if cached, the original path no longer exists.
cache_result.has_value() ? cache_result.value() : crx_file,
std::move(unzipper), crx_format,
base::BindPostTaskToCurrentDefault(std::move(callback))));
}
} // namespace
base::OnceClosure InstallOperation(
scoped_refptr<CrxCache> crx_cache,
std::unique_ptr<Unzipper> unzipper,
crx_file::VerifierFormat crx_format,
const std::string& id,
const std::string& file_hash,
const std::vector<uint8_t>& pk_hash,
scoped_refptr<CrxInstaller> installer,
std::unique_ptr<CrxInstaller::InstallParams> install_params,
base::RepeatingCallback<void(base::Value::Dict)> event_adder,
base::RepeatingCallback<void(ComponentState)> state_tracker,
CrxInstaller::ProgressCallback progress_callback,
base::OnceCallback<void(const CrxInstaller::Result&)>
installer_result_callback,
const base::FilePath& crx_file,
base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)>
callback) {
state_tracker.Run(ComponentState::kUpdating);
crx_cache->Put(
// TODO(crbug.com/399617574): Remove FP.
crx_file, id, file_hash, /*fp=*/{},
base::BindOnce(
&Unpack,
base::BindOnce(
&Install,
base::BindOnce(&InstallComplete,
std::move(installer_result_callback),
std::move(callback), event_adder, crx_file),
std::move(install_params), installer, progress_callback),
id, crx_file, std::move(unzipper), pk_hash, crx_format));
return base::DoNothing();
}
} // namespace update_client