1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
// 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 "chromeos/ash/components/data_migration/file_receiver.h"
#include <optional>
#include <utility>
#include "base/check.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
namespace data_migration {
namespace {
// Theoretically, this should never fail because the NC mojo service has told
// the caller that the transfer was a success by the time this function is
// called. That being said, file transfer is long and complex with lots of I/O
// involved. So this just double-checks the result before notifying the caller
// that the transfer succeeded.
bool DidFileTransferComplete(const base::FilePath& path,
int64_t expected_size_in_bytes) {
static constexpr char kFailureLogPrefix[] =
"File transfer verification failed. ";
if (!base::PathExists(path)) {
LOG(DFATAL) << kFailureLogPrefix << "File does not exist.";
return false;
}
if (!base::PathIsReadable(path)) {
LOG(DFATAL) << kFailureLogPrefix << "File is not even readable.";
return false;
}
std::optional<int64_t> actual_file_size_in_bytes = base::GetFileSize(path);
if (!actual_file_size_in_bytes.has_value()) {
LOG(DFATAL) << kFailureLogPrefix << "Failed to get file size.";
return false;
}
if (actual_file_size_in_bytes.value() != expected_size_in_bytes) {
LOG(DFATAL) << kFailureLogPrefix << "actual_file_size_in_bytes="
<< actual_file_size_in_bytes.value()
<< " expected_size_in_bytes=" << expected_size_in_bytes;
return false;
}
return true;
}
} // namespace
FileReceiver::Observer::Observer(
base::OnceClosure on_file_registered_in,
base::OnceCallback<void(bool)> on_file_transfer_complete_in)
: on_file_registered(std::move(on_file_registered_in)),
on_file_transfer_complete(std::move(on_file_transfer_complete_in)) {
CHECK(on_file_registered);
CHECK(on_file_transfer_complete);
}
FileReceiver::Observer::Observer(Observer&&) = default;
FileReceiver::Observer& FileReceiver::Observer::operator=(Observer&&) = default;
FileReceiver::Observer::~Observer() = default;
FileReceiver::FileReceiver(int64_t payload_id,
base::FilePath path,
Observer observer,
NearbyConnectionsManager* nearby_connections_manager)
: payload_id_(payload_id),
path_(std::move(path)),
observer_(std::move(observer)),
nearby_connections_manager_(nearby_connections_manager) {
CHECK(!path_.empty());
CHECK(nearby_connections_manager_);
nearby_connections_manager_->RegisterPayloadStatusListener(payload_id_,
GetWeakPtr());
RegisterPayloadPath(/*attempt_number=*/1);
}
FileReceiver::~FileReceiver() {
if (!transfer_completed_successfully_) {
VLOG(1) << "FileReceiver destroyed before file transfer completed. "
"Canceling payload transfer.";
// Invalidate weak ptrs first so that we do not synchronously get an
// `OnStatusUpdate(<canceled>)` notification from our own call to
// `NearbyConnectionsManager::Cancel()`.
weak_ptr_factory_.InvalidateWeakPtrs();
// Note this is a no-op if the transfer completed with a failure.
nearby_connections_manager_->Cancel(payload_id_);
}
// Closes all file descriptors associated with this payload. Prevents them
// from accumulating over the course of a long data migration.
nearby_connections_manager_->ClearIncomingPayloadWithId(payload_id_);
}
void FileReceiver::RegisterPayloadPath(int attempt_number) {
// Past 3 attempts, the partition is probably in such a bad state that
// retrying more will not help.
constexpr int kMaxNumAttempts = 3;
if (attempt_number > kMaxNumAttempts) {
LOG(ERROR) << "RegisterPayloadPath() failed " << kMaxNumAttempts
<< " times. File transfer is a failure";
CompleteTransfer(/*verification_status=*/false);
return;
}
// Tell the NC library that incoming payload from the remote device with
// `payload_id_` should get written to `path_`. Registration must happen
// before the incoming payload arrives, or file transmission will fail.
nearby_connections_manager_->RegisterPayloadPath(
payload_id_, path_,
base::BindOnce(&FileReceiver::OnRegisterPayloadPathComplete,
file_receiver_weak_factory_.GetWeakPtr(), attempt_number));
}
void FileReceiver::OnRegisterPayloadPathComplete(
int attempt_number,
NearbyConnectionsManager::ConnectionsStatus result) {
constexpr base::TimeDelta kRetryDelay = base::Milliseconds(250);
if (result == NearbyConnectionsManager::ConnectionsStatus::kSuccess) {
VLOG(1) << "data_migration file successfully registered with NC";
CHECK(observer_.on_file_registered);
std::move(observer_.on_file_registered).Run();
} else {
// This can legitimately happen from transient file I/O errors in the NC
// library. There are no network operations involved though, so
// exponential backoff and jitter are unnecessary.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FileReceiver::RegisterPayloadPath,
file_receiver_weak_factory_.GetWeakPtr(),
attempt_number + 1),
kRetryDelay);
}
}
void FileReceiver::OnStatusUpdate(PayloadTransferUpdatePtr update,
std::optional<Medium> upgraded_medium) {
CHECK(update);
if (upgraded_medium) {
VLOG(4) << "File transfer for using upgraded_medium " << *upgraded_medium;
} else {
VLOG(4) << "File transfer for has unknown upgraded_medium";
}
switch (update->status) {
case ::nearby::connections::mojom::PayloadStatus::kSuccess:
VLOG(1) << "File transfer completed";
VerifyFileTransferResult(update->total_bytes);
return;
case ::nearby::connections::mojom::PayloadStatus::kInProgress: {
// Catch for divide-by-zero when calculating `progress_as_percentage`.
if (update->total_bytes == 0) {
LOG(ERROR) << "File has expected size of zero bytes.";
return;
}
if (update->bytes_transferred > update->total_bytes) {
LOG(ERROR) << "File bytes_transferred(" << update->bytes_transferred
<< ") > expected(" << update->total_bytes << ")";
return;
}
VLOG(4) << "File transfer completion percentage: "
<< base::ClampFloor(
static_cast<double>(update->bytes_transferred) /
update->total_bytes * 100.f);
return;
}
case ::nearby::connections::mojom::PayloadStatus::kCanceled:
case ::nearby::connections::mojom::PayloadStatus::kFailure:
LOG(ERROR) << "File transfer failed with status: " << update->status;
CompleteTransfer(false);
return;
}
}
void FileReceiver::VerifyFileTransferResult(int64_t expected_size_in_bytes) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&DidFileTransferComplete, path_, expected_size_in_bytes),
base::BindOnce(&FileReceiver::CompleteTransfer,
file_receiver_weak_factory_.GetWeakPtr()));
}
void FileReceiver::CompleteTransfer(bool verification_status) {
if (!observer_.on_file_transfer_complete) {
// This can only happen if the NC mojo service notifies us multiple times of
// a successful or failed transfer.
LOG(DFATAL) << "Received multiple payload completion status updates";
return;
}
VLOG(1) << "File verification_status=" << verification_status;
transfer_completed_successfully_ = verification_status;
std::move(observer_.on_file_transfer_complete).Run(verification_status);
}
} // namespace data_migration
|