File: guest_os_dlc_helper.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 (217 lines) | stat: -rw-r--r-- 7,651 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
211
212
213
214
215
216
217
// Copyright 2023 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/browser/ash/guest_os/guest_os_dlc_helper.h"

#include <string_view>

#include "base/memory/ptr_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice.pb.h"
#include "content/public/browser/network_service_instance.h"
#include "third_party/cros_system_api/dbus/dlcservice/dbus-constants.h"

namespace guest_os {

namespace {

// How long to wait between retry attempts.
constexpr base::TimeDelta kBetweenRetryDelay = base::Seconds(5);

// Maximum number of times the installation will retry before giving up.
constexpr int kMaxRetries = 5;

GuestOsDlcInstallation::Error ToError(const std::string& error) {
  if (error == dlcservice::kErrorInternal) {
    return GuestOsDlcInstallation::Error::Internal;
  } else if (error == dlcservice::kErrorBusy) {
    return GuestOsDlcInstallation::Error::Busy;
  } else if (error == dlcservice::kErrorNeedReboot) {
    return GuestOsDlcInstallation::Error::NeedReboot;
  } else if (error == dlcservice::kErrorInvalidDlc) {
    return GuestOsDlcInstallation::Error::Invalid;
  } else if (error == dlcservice::kErrorAllocation) {
    return GuestOsDlcInstallation::Error::DiskFull;
  } else if (error == dlcservice::kErrorNoImageFound) {
    // Actually this isn't always actionable with an update but it often is so
    // we advise it.
    return GuestOsDlcInstallation::Error::NeedUpdate;
  }
  // DLC records success the same way as failure, but this method should never
  // be called on success.
  CHECK(error != dlcservice::kErrorNone);
  LOG(ERROR) << "DLC Installation failed with unrecognized error: " << error;
  return GuestOsDlcInstallation::Error::UnknownFailure;
}

enum class Actionability {
  // Errors which we know will just happen again until the user does something.
  UserIntervention,
  // Errors which may not happen again so we automatically retry them.
  Retry,
  // Errors which can neither be actioned or retried by the user.
  None,
};

Actionability GetActionability(GuestOsDlcInstallation::Error err) {
  switch (err) {
    case GuestOsDlcInstallation::Error::Cancelled:
    case GuestOsDlcInstallation::Error::Offline:
    case GuestOsDlcInstallation::Error::NeedUpdate:
    case GuestOsDlcInstallation::Error::NeedReboot:
    case GuestOsDlcInstallation::Error::DiskFull:
      return Actionability::UserIntervention;
    case GuestOsDlcInstallation::Error::Busy:
    case GuestOsDlcInstallation::Error::Internal:
      return Actionability::Retry;
    case GuestOsDlcInstallation::Error::Invalid:
    case GuestOsDlcInstallation::Error::UnknownFailure:
      return Actionability::None;
  }
}

}  // namespace

GuestOsDlcInstallation::GuestOsDlcInstallation(
    std::string dlc_id,
    base::OnceCallback<void(Result)> completion_callback,
    ProgressCallback progress_callback)
    : dlc_id_(std::move(dlc_id)),
      retries_remaining_(kMaxRetries),
      completion_callback_(std::move(completion_callback)),
      progress_callback_(std::move(progress_callback)) {
  // This object represents the installation so begin that installation in
  // its constructor. First, check if the DLC is installed.
  CheckState();
}

GuestOsDlcInstallation::~GuestOsDlcInstallation() {
  if (completion_callback_) {
    std::move(completion_callback_).Run(base::unexpected(Error::Cancelled));
  }
}

void GuestOsDlcInstallation::CancelGracefully() {
  gracefully_cancelled_ = true;
  retries_remaining_ = 0;
}

void GuestOsDlcInstallation::CheckState() {
  ash::DlcserviceClient::Get()->GetDlcState(
      dlc_id_, base::BindOnce(&GuestOsDlcInstallation::OnGetDlcStateCompleted,
                              weak_factory_.GetWeakPtr()));
}

void GuestOsDlcInstallation::OnGetDlcStateCompleted(
    std::string_view err,
    const dlcservice::DlcState& dlc_state) {
  ash::DlcserviceClient::InstallResult result;
  switch (dlc_state.state()) {
    case dlcservice::DlcState::INSTALLED:
      result.dlc_id = dlc_state.id();
      result.root_path = dlc_state.root_path();
      result.error = dlcservice::kErrorNone;
      OnDlcInstallCompleted(result);
      break;
    case dlcservice::DlcState::NOT_INSTALLED:
      StartInstall();
      break;
    case dlcservice::DlcState::INSTALLING:
      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&GuestOsDlcInstallation::CheckState,
                         weak_factory_.GetWeakPtr()),
          kBetweenRetryDelay);
      break;
    default:
      NOTREACHED();
  }
}

void GuestOsDlcInstallation::StartInstall() {
  // Skip calling install if we've canceled.
  if (gracefully_cancelled_) {
    OnDlcInstallCompleted({});
    return;
  }
  dlcservice::InstallRequest install_request;
  install_request.set_id(dlc_id_);
  ash::DlcserviceClient::Get()->Install(
      install_request,
      base::BindOnce(&GuestOsDlcInstallation::OnDlcInstallCompleted,
                     weak_factory_.GetWeakPtr()),
      progress_callback_);
}

void GuestOsDlcInstallation::OnDlcInstallCompleted(
    const ash::DlcserviceClient::InstallResult& result) {
  if (gracefully_cancelled_) {
    std::move(completion_callback_).Run(base::unexpected(Error::Cancelled));
    return;
  }
  CHECK(result.dlc_id == dlc_id_);
  if (result.error == dlcservice::kErrorNone) {
    std::move(completion_callback_)
        .Run(base::ok(base::FilePath(result.root_path)));
    return;
  }

  Error err = ToError(result.error);

  switch (GetActionability(err)) {
    case Actionability::UserIntervention:
      std::move(completion_callback_).Run(base::unexpected(err));
      return;

    case Actionability::Retry:
      if (retries_remaining_ > 0) {
        --retries_remaining_;
        base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
            FROM_HERE,
            base::BindOnce(&GuestOsDlcInstallation::StartInstall,
                           weak_factory_.GetWeakPtr()),
            kBetweenRetryDelay);
        return;
      }
      // If we're out of retries then we can't action this error ourselves.
      [[fallthrough]];

    case Actionability::None:
      // Unless we know the cause of an error (because it was actionable or we
      // have run out of retries), assume being offline causes errors.
      if (content::GetNetworkConnectionTracker()->IsOffline()) {
        err = Error::Offline;
      }

      std::move(completion_callback_).Run(base::unexpected(err));
      return;
  }
}

}  // namespace guest_os

std::ostream& operator<<(std::ostream& stream,
                         guest_os::GuestOsDlcInstallation::Error err) {
  switch (err) {
    case guest_os::GuestOsDlcInstallation::Error::Cancelled:
      return stream << "cancelled";
    case guest_os::GuestOsDlcInstallation::Error::Offline:
      return stream << "offline";
    case guest_os::GuestOsDlcInstallation::Error::NeedUpdate:
      return stream << "need update";
    case guest_os::GuestOsDlcInstallation::Error::NeedReboot:
      return stream << "need reboot";
    case guest_os::GuestOsDlcInstallation::Error::DiskFull:
      return stream << "disk full";
    case guest_os::GuestOsDlcInstallation::Error::Busy:
      return stream << "busy";
    case guest_os::GuestOsDlcInstallation::Error::Internal:
      return stream << "internal";
    case guest_os::GuestOsDlcInstallation::Error::Invalid:
      return stream << "invalid";
    case guest_os::GuestOsDlcInstallation::Error::UnknownFailure:
      return stream << "unknown";
  }
}