File: file_receiver.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (210 lines) | stat: -rw-r--r-- 8,286 bytes parent folder | download | duplicates (6)
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