| // Copyright 2025 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_zucchini.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #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/update_client/configurator.h" |
| #include "components/update_client/crx_cache.h" |
| #include "components/update_client/patcher.h" |
| #include "components/update_client/protocol_definition.h" |
| #include "components/update_client/task_traits.h" |
| #include "components/update_client/update_client_errors.h" |
| #include "components/zucchini/zucchini.h" |
| |
| namespace update_client { |
| |
| namespace { |
| |
| // The sequence of calls is: |
| // |
| // [Original Sequence] [Blocking Pool] |
| // |
| // ZucchiniOperation |
| // CacheLookupDone |
| // Patch |
| // Cleanup |
| // PatchDone |
| // [original callback] |
| // |
| // All errors shortcut to PatchDone. |
| |
| base::Value::Dict MakeEvent( |
| base::expected<base::FilePath, CategorizedError> result) { |
| base::Value::Dict event; |
| event.Set("eventtype", protocol_request::kEventZucchini); |
| event.Set("eventresult", |
| static_cast<int>(result.has_value() |
| ? protocol_request::kEventResultSuccess |
| : protocol_request::kEventResultError)); |
| |
| if (!result.has_value()) { |
| CategorizedError error = result.error(); |
| if (error.category_ != ErrorCategory::kNone) { |
| event.Set("errorcat", static_cast<int>(error.category_)); |
| } |
| if (error.code_ != 0) { |
| event.Set("errorcode", error.code_); |
| } |
| if (error.extra_ != 0) { |
| event.Set("extracode1", error.extra_); |
| } |
| } |
| return event; |
| } |
| |
| // Runs on the original sequence. Adds events and calls the original callback. |
| void PatchDone( |
| base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)> |
| callback, |
| base::RepeatingCallback<void(base::Value::Dict)> event_adder, |
| base::expected<base::FilePath, CategorizedError> result) { |
| event_adder.Run(MakeEvent(result)); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), result)); |
| } |
| |
| // Runs in the blocking pool. Deletes any files that are no longer needed. |
| void CleanUp( |
| base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)> |
| callback, |
| const base::FilePath& patch_file, |
| const base::FilePath& new_file, |
| int result) { |
| base::DeleteFile(patch_file); |
| if (result) { |
| base::DeleteFile(new_file); |
| std::move(callback).Run(base::unexpected<CategorizedError>( |
| {.category_ = ErrorCategory::kUnpack, |
| .code_ = static_cast<int>(UnpackerError::kDeltaOperationFailure), |
| .extra_ = result})); |
| return; |
| } |
| std::move(callback).Run(new_file); |
| } |
| |
| // Runs in the blocking pool. Opens file handles and applies the patch. |
| void Patch( |
| scoped_refptr<Patcher> patcher, |
| const base::FilePath& old_file, |
| const base::FilePath& patch_file, |
| const base::FilePath& temp_dir, |
| base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)> |
| callback) { |
| base::FilePath new_file = temp_dir.Append(FILE_PATH_LITERAL("puffpatch_out")); |
| patcher->PatchZucchini( |
| base::File(old_file, base::File::FLAG_OPEN | base::File::FLAG_READ), |
| base::File(patch_file, base::File::FLAG_OPEN | base::File::FLAG_READ), |
| base::File(new_file, base::File::FLAG_CREATE | base::File::FLAG_READ | |
| base::File::FLAG_WRITE | |
| base::File::FLAG_WIN_EXCLUSIVE_WRITE | |
| base::File::FLAG_WIN_SHARE_DELETE | |
| base::File::FLAG_CAN_DELETE_ON_CLOSE), |
| base::BindOnce(&CleanUp, std::move(callback), patch_file, new_file)); |
| } |
| |
| // Runs on the original sequence. |
| void CacheLookupDone( |
| scoped_refptr<Patcher> patcher, |
| const base::FilePath& patch_file, |
| const base::FilePath& temp_dir, |
| base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)> |
| callback, |
| base::expected<base::FilePath, UnpackerError> cache_result) { |
| if (!cache_result.has_value()) { |
| base::ThreadPool::PostTaskAndReply( |
| FROM_HERE, kTaskTraits, |
| base::BindOnce(IgnoreResult(&base::DeleteFile), patch_file), |
| base::BindOnce(std::move(callback), |
| base::unexpected<CategorizedError>( |
| {.category_ = ErrorCategory::kUnpack, |
| .code_ = static_cast<int>(cache_result.error())}))); |
| return; |
| } |
| base::ThreadPool::CreateSequencedTaskRunner(kTaskTraits) |
| ->PostTask(FROM_HERE, |
| base::BindOnce(&Patch, patcher, cache_result.value(), |
| patch_file, temp_dir, std::move(callback))); |
| } |
| |
| } // namespace |
| |
| base::OnceClosure ZucchiniOperation( |
| scoped_refptr<CrxCache> crx_cache, |
| scoped_refptr<Patcher> patcher, |
| base::RepeatingCallback<void(base::Value::Dict)> event_adder, |
| const std::string& id, |
| const std::string& prev_hash, |
| const base::FilePath& patch_file, |
| base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)> |
| callback) { |
| crx_cache->GetByHash( |
| prev_hash, |
| base::BindOnce(&CacheLookupDone, patcher, patch_file, |
| patch_file.DirName(), |
| base::BindPostTaskToCurrentDefault(base::BindOnce( |
| &PatchDone, std::move(callback), event_adder)))); |
| return base::DoNothing(); |
| } |
| |
| } // namespace update_client |