blob: e79aefa1569e31275b3ca2ee0eb0cb023e203bd8 [file] [log] [blame]
// 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 "chrome/updater/out_of_process_unzipper.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/command_line.h"
#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/logging.h"
#include "base/memory/ref_counted.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "components/services/unzip/public/mojom/unzipper.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "third_party/zlib/google/zip.h"
namespace updater {
namespace {
// A DecodeOperation is a cancellable invocation of DecodeXz.
class DecodeOperation : public base::RefCountedThreadSafe<DecodeOperation> {
public:
DecodeOperation(const base::FilePath& out_path,
base::OnceCallback<void(bool)> callback)
: out_path_(out_path), callback_(std::move(callback)) {
// DecodeOperation is created on one sequence but forever used on another.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
// Start must be called only once. May block.
void Start(UpdaterScope scope, const base::FilePath& in_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::optional<base::FilePath> updater_path =
GetUpdaterExecutablePath(scope);
if (!updater_path) {
Done(false);
return;
}
base::CommandLine command_line(*updater_path);
command_line.AppendSwitch(kUnzipWorkerSwitch);
if (IsSystemInstall(scope)) {
command_line.AppendSwitch(kSystemSwitch);
}
base::LaunchOptions options;
mojo::PlatformChannel channel;
channel.PrepareToPassRemoteEndpoint(&options, &command_line);
base::Process process = base::LaunchProcess(command_line, options);
VLOG(2) << "Launching " << command_line.GetArgumentsString();
if (!process.IsValid()) {
VLOG(2) << "Failure.";
Done(false);
return;
}
channel.RemoteProcessLaunchAttempted();
mojo::ScopedMessagePipeHandle pipe = mojo::OutgoingInvitation::SendIsolated(
channel.TakeLocalEndpoint(), {}, process.Handle());
if (!pipe) {
VLOG(0) << "Failed to send Mojo invitation to the unzipper process.";
Done(false);
return;
}
mojo::PendingRemote<unzip::mojom::Unzipper> pending_remote(
std::move(pipe), unzip::mojom::Unzipper::Version_);
if (!pending_remote) {
VLOG(0) << "Failed to establish IPC with the unzipper process.";
Done(false);
return;
}
unzipper_.Bind(std::move(pending_remote));
unzipper_.set_disconnect_handler(
base::BindOnce(&DecodeOperation::Done, this, false));
base::File in(in_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WIN_EXCLUSIVE_WRITE |
base::File::FLAG_WIN_SEQUENTIAL_SCAN |
base::File::FLAG_WIN_SHARE_DELETE);
if (!in.IsValid()) {
VLOG(1) << "Failed to open input file.";
Done(false);
return;
}
base::File out(out_path_, base::File::FLAG_CREATE | base::File::FLAG_READ |
base::File::FLAG_WRITE |
base::File::FLAG_WIN_EXCLUSIVE_WRITE |
base::File::FLAG_WIN_SEQUENTIAL_SCAN |
base::File::FLAG_WIN_SHARE_DELETE);
if (!out.IsValid()) {
VLOG(1) << "Failed to open output file.";
Done(false);
return;
}
unzipper_->DecodeXz(std::move(in), std::move(out),
base::BindOnce(&DecodeOperation::Done, this));
}
// Resets the unzipper remote and triggers the completion callback.
void Cancel() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Done(false);
}
private:
// `Done` may be called multiple times, for example if cancellation is posted
// to a task runner, but concurrently, the job completes or the remote
// disconnects.
void Done(bool result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!result && unzipper_.is_bound()) {
base::ThreadPool::PostTask(FROM_HERE, {base::MayBlock()},
base::GetDeleteFileCallback(out_path_));
}
std::move(callback_).Run(result);
callback_ = base::DoNothing();
unzipper_.reset();
}
private:
friend class base::RefCountedThreadSafe<DecodeOperation>;
~DecodeOperation() = default;
SEQUENCE_CHECKER(sequence_checker_);
mojo::Remote<unzip::mojom::Unzipper> unzipper_;
const base::FilePath out_path_;
base::OnceCallback<void(bool)> callback_;
};
} // namespace
OutOfProcessUnzipper::OutOfProcessUnzipper(UpdaterScope scope)
: scope_(scope) {}
OutOfProcessUnzipper::~OutOfProcessUnzipper() = default;
void OutOfProcessUnzipper::Unzip(const base::FilePath& zip_path,
const base::FilePath& output_path,
UnzipCompleteCallback callback) {
// For now, run in-process until the mojo Unzip interface supports symlink
// preservation.
std::move(callback).Run(zip::Unzip(zip_path, output_path, {},
// Allow internal symbolic links in zip files on macOS.
#if BUILDFLAG(IS_POSIX)
zip::UnzipSymlinkOption::PRESERVE
#else
zip::UnzipSymlinkOption::DONT_PRESERVE
#endif
));
}
base::OnceClosure OutOfProcessUnzipper::DecodeXz(
const base::FilePath& xz_path,
const base::FilePath& output_path,
UnzipCompleteCallback done_callback) {
auto op = base::MakeRefCounted<DecodeOperation>(output_path,
std::move(done_callback));
const scoped_refptr<base::SequencedTaskRunner> runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
runner->PostTask(
FROM_HERE, base::BindOnce(&DecodeOperation::Start, op, scope_, xz_path));
return base::BindPostTask(runner,
base::BindOnce(&DecodeOperation::Cancel, op));
}
OutOfProcessUnzipperFactory::OutOfProcessUnzipperFactory(UpdaterScope scope)
: scope_(scope) {}
std::unique_ptr<update_client::Unzipper> OutOfProcessUnzipperFactory::Create()
const {
return std::make_unique<OutOfProcessUnzipper>(scope_);
}
} // namespace updater