dlcservice_client.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. // Copyright (c) 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "chromeos/dbus/dlcservice/dlcservice_client.h"
  5. #include <stdint.h>
  6. #include <algorithm>
  7. #include <deque>
  8. #include <map>
  9. #include <string>
  10. #include <unordered_set>
  11. #include <utility>
  12. #include <vector>
  13. #include "base/bind.h"
  14. #include "base/callback.h"
  15. #include "base/command_line.h"
  16. #include "base/location.h"
  17. #include "base/logging.h"
  18. #include "base/no_destructor.h"
  19. #include "base/strings/string_number_conversions.h"
  20. #include "base/strings/string_util.h"
  21. #include "base/task/single_thread_task_runner.h"
  22. #include "base/threading/thread_task_runner_handle.h"
  23. #include "chromeos/dbus/constants/dbus_switches.h"
  24. #include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
  25. #include "dbus/bus.h"
  26. #include "dbus/message.h"
  27. #include "dbus/object_path.h"
  28. #include "dbus/object_proxy.h"
  29. #include "third_party/abseil-cpp/absl/types/optional.h"
  30. #include "third_party/cros_system_api/dbus/service_constants.h"
  31. namespace chromeos {
  32. namespace {
  33. DlcserviceClient* g_instance = nullptr;
  34. class DlcserviceErrorResponseHandler {
  35. public:
  36. explicit DlcserviceErrorResponseHandler(dbus::ErrorResponse* err_response)
  37. : err_(dlcservice::kErrorInternal) {
  38. if (!err_response) {
  39. LOG(ERROR) << "Failed to set err since ErrorResponse is null.";
  40. return;
  41. }
  42. VerifyAndSetError(err_response);
  43. VerifyAndSetErrorMessage(err_response);
  44. VLOG(1) << "Handling err=" << err_ << " err_msg=" << err_msg_;
  45. }
  46. DlcserviceErrorResponseHandler(const DlcserviceErrorResponseHandler&) =
  47. delete;
  48. DlcserviceErrorResponseHandler& operator=(
  49. const DlcserviceErrorResponseHandler&) = delete;
  50. ~DlcserviceErrorResponseHandler() = default;
  51. std::string get_err() { return err_; }
  52. std::string get_err_msg() { return err_msg_; }
  53. private:
  54. void VerifyAndSetError(dbus::ErrorResponse* err_response) {
  55. const std::string& err = err_response->GetErrorName();
  56. static const base::NoDestructor<std::unordered_set<std::string>> err_set({
  57. dlcservice::kErrorNone,
  58. dlcservice::kErrorInternal,
  59. dlcservice::kErrorBusy,
  60. dlcservice::kErrorNeedReboot,
  61. dlcservice::kErrorInvalidDlc,
  62. dlcservice::kErrorNoImageFound,
  63. });
  64. // Lookup the dlcservice error code and provide default on invalid.
  65. auto itr = err_set->find(err);
  66. if (itr == err_set->end()) {
  67. LOG(ERROR) << "Failed to set error based on ErrorResponse "
  68. "defaulted to kErrorInternal, was:" << err;
  69. err_ = dlcservice::kErrorInternal;
  70. return;
  71. }
  72. err_ = *itr;
  73. }
  74. void VerifyAndSetErrorMessage(dbus::ErrorResponse* err_response) {
  75. if (!dbus::MessageReader(err_response).PopString(&err_msg_)) {
  76. LOG(ERROR) << "Failed to set error message from ErrorResponse.";
  77. }
  78. }
  79. // Holds the dlcservice specific error.
  80. std::string err_;
  81. // Holds the entire error message from error response.
  82. std::string err_msg_;
  83. };
  84. } // namespace
  85. // The DlcserviceClient implementation used in production.
  86. class DlcserviceClientImpl : public DlcserviceClient {
  87. public:
  88. DlcserviceClientImpl() : dlcservice_proxy_(nullptr) {}
  89. DlcserviceClientImpl(const DlcserviceClientImpl&) = delete;
  90. DlcserviceClientImpl& operator=(const DlcserviceClientImpl&) = delete;
  91. ~DlcserviceClientImpl() override = default;
  92. void Install(const dlcservice::InstallRequest& install_request,
  93. InstallCallback install_callback,
  94. ProgressCallback progress_callback) override {
  95. CheckServiceAvailable("Install");
  96. const std::string& id = install_request.id();
  97. // If another installation for the same DLC ID was already called, go ahead
  98. // and hold the installation fields.
  99. if (installation_holder_.find(id) != installation_holder_.end()) {
  100. HoldInstallation(install_request, std::move(install_callback),
  101. std::move(progress_callback));
  102. return;
  103. }
  104. if (installing_) {
  105. EnqueueTask(base::BindOnce(
  106. &DlcserviceClientImpl::Install, weak_ptr_factory_.GetWeakPtr(),
  107. std::move(install_request), std::move(install_callback),
  108. std::move(progress_callback)));
  109. return;
  110. }
  111. TaskStarted();
  112. dbus::MethodCall method_call(dlcservice::kDlcServiceInterface,
  113. dlcservice::kInstallMethod);
  114. dbus::MessageWriter writer(&method_call);
  115. writer.AppendProtoAsArrayOfBytes(install_request);
  116. VLOG(1) << "Requesting to install DLC(s).";
  117. // TODO(b/166782419): dlcservice hashes preloadable DLC images which can
  118. // cause timeouts during preloads. Transitioning into F20 will fix this as
  119. // preloading will be deprecated.
  120. constexpr int timeout_ms = 5 * 60 * 1000;
  121. dlcservice_proxy_->CallMethodWithErrorResponse(
  122. &method_call, timeout_ms,
  123. base::BindOnce(&DlcserviceClientImpl::OnInstall,
  124. weak_ptr_factory_.GetWeakPtr(), install_request,
  125. std::move(install_callback),
  126. std::move(progress_callback)));
  127. }
  128. void Uninstall(const std::string& dlc_id,
  129. UninstallCallback uninstall_callback) override {
  130. CheckServiceAvailable("Uninstall");
  131. dbus::MethodCall method_call(dlcservice::kDlcServiceInterface,
  132. dlcservice::kUninstallMethod);
  133. dbus::MessageWriter writer(&method_call);
  134. writer.AppendString(dlc_id);
  135. VLOG(1) << "Requesting to uninstall DLC=" << dlc_id;
  136. dlcservice_proxy_->CallMethodWithErrorResponse(
  137. &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
  138. base::BindOnce(&DlcserviceClientImpl::OnUninstall,
  139. weak_ptr_factory_.GetWeakPtr(),
  140. std::move(uninstall_callback)));
  141. }
  142. void Purge(const std::string& dlc_id, PurgeCallback purge_callback) override {
  143. CheckServiceAvailable("Purge");
  144. dbus::MethodCall method_call(dlcservice::kDlcServiceInterface,
  145. dlcservice::kPurgeMethod);
  146. dbus::MessageWriter writer(&method_call);
  147. writer.AppendString(dlc_id);
  148. VLOG(1) << "Requesting to purge DLC=" << dlc_id;
  149. dlcservice_proxy_->CallMethodWithErrorResponse(
  150. &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
  151. base::BindOnce(&DlcserviceClientImpl::OnPurge,
  152. weak_ptr_factory_.GetWeakPtr(),
  153. std::move(purge_callback)));
  154. }
  155. void GetDlcState(const std::string& dlc_id,
  156. GetDlcStateCallback callback) override {
  157. CheckServiceAvailable("GetDlcState");
  158. dbus::MethodCall method_call(dlcservice::kDlcServiceInterface,
  159. dlcservice::kGetDlcStateMethod);
  160. dbus::MessageWriter writer(&method_call);
  161. writer.AppendString(dlc_id);
  162. VLOG(1) << "Requesting DLC state of" << dlc_id;
  163. dlcservice_proxy_->CallMethodWithErrorResponse(
  164. &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
  165. base::BindOnce(&DlcserviceClientImpl::OnGetDlcState,
  166. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  167. }
  168. void GetExistingDlcs(GetExistingDlcsCallback callback) override {
  169. CheckServiceAvailable("GetExistingDlcs");
  170. dbus::MethodCall method_call(dlcservice::kDlcServiceInterface,
  171. dlcservice::kGetExistingDlcsMethod);
  172. VLOG(1) << "Requesting to get existing DLC(s).";
  173. dlcservice_proxy_->CallMethodWithErrorResponse(
  174. &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
  175. base::BindOnce(&DlcserviceClientImpl::OnGetExistingDlcs,
  176. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  177. }
  178. void DlcStateChangedForTest(dbus::Signal* signal) override {
  179. DlcStateChanged(signal);
  180. }
  181. void AddObserver(Observer* observer) override {
  182. observers_.AddObserver(observer);
  183. }
  184. void RemoveObserver(Observer* observer) override {
  185. observers_.RemoveObserver(observer);
  186. }
  187. void Init(dbus::Bus* bus) {
  188. dlcservice_proxy_ = bus->GetObjectProxy(
  189. dlcservice::kDlcServiceServiceName,
  190. dbus::ObjectPath(dlcservice::kDlcServiceServicePath));
  191. dlcservice_proxy_->ConnectToSignal(
  192. dlcservice::kDlcServiceInterface, dlcservice::kDlcStateChangedSignal,
  193. base::BindRepeating(&DlcserviceClientImpl::DlcStateChanged,
  194. weak_ptr_factory_.GetWeakPtr()),
  195. base::BindOnce(&DlcserviceClientImpl::DlcStateChangedConnected,
  196. weak_ptr_factory_.GetWeakPtr()));
  197. dlcservice_proxy_->WaitForServiceToBeAvailable(
  198. base::BindOnce(&DlcserviceClientImpl::OnServiceAvailable,
  199. weak_ptr_factory_.GetWeakPtr()));
  200. }
  201. private:
  202. // Fields related to an installation allowing for multiple installations to be
  203. // in flight concurrently and handled by this dlcservice client. The callbacks
  204. // are used to report progress and the final installation.
  205. struct InstallationHolder {
  206. InstallCallback install_callback;
  207. ProgressCallback progress_callback;
  208. InstallationHolder(InstallCallback install_callback,
  209. ProgressCallback progress_callback)
  210. : install_callback(std::move(install_callback)),
  211. progress_callback(std::move(progress_callback)) {}
  212. };
  213. void OnServiceAvailable(bool service_available) {
  214. if (service_available)
  215. VLOG(1) << "dlcservice is available.";
  216. else
  217. LOG(ERROR) << "dlcservice is not available.";
  218. service_available_ = service_available;
  219. }
  220. // Set the indication that an install is being performed which was requested
  221. // from this client (Chrome specifically).
  222. void TaskStarted() { installing_ = true; }
  223. // Clears any state an installation had setup while being performed.
  224. void TaskEnded() { installing_ = false; }
  225. void HoldInstallation(const dlcservice::InstallRequest& install_request,
  226. InstallCallback install_callback,
  227. ProgressCallback progress_callback) {
  228. installation_holder_[install_request.id()].emplace_back(
  229. std::move(install_callback), std::move(progress_callback));
  230. }
  231. void ReleaseInstallation(const std::string& id) {
  232. installation_holder_.erase(id);
  233. }
  234. void EnqueueTask(base::OnceClosure task) {
  235. pending_tasks_.emplace_back(std::move(task));
  236. }
  237. void CheckAndRunPendingTask() {
  238. TaskEnded();
  239. if (!pending_tasks_.empty()) {
  240. std::move(pending_tasks_.front()).Run();
  241. pending_tasks_.pop_front();
  242. }
  243. }
  244. void SendProgress(const dlcservice::DlcState& dlc_state) {
  245. auto id = dlc_state.id();
  246. auto progress = dlc_state.progress();
  247. VLOG(2) << "Installation for DLC " << id << " in progress: " << progress;
  248. for (auto& installation_state : installation_holder_[id])
  249. installation_state.progress_callback.Run(progress);
  250. }
  251. void SendCompleted(const dlcservice::DlcState& dlc_state) {
  252. auto id = dlc_state.id();
  253. if (dlc_state.state() == dlcservice::DlcState::NOT_INSTALLED) {
  254. LOG(ERROR) << "Failed to install DLC " << id
  255. << " with error code: " << dlc_state.last_error_code();
  256. } else {
  257. VLOG(1) << "DLC " << id << " installed successfully.";
  258. if (dlc_state.last_error_code() != dlcservice::kErrorNone) {
  259. LOG(WARNING) << "DLC installation was sucessful but non-success "
  260. << "error code: " << dlc_state.last_error_code();
  261. }
  262. }
  263. InstallResult result = {
  264. .error = dlc_state.last_error_code(),
  265. .dlc_id = id,
  266. .root_path = dlc_state.root_path(),
  267. };
  268. for (auto& installation_state : installation_holder_[id])
  269. std::move(installation_state.install_callback).Run(result);
  270. ReleaseInstallation(id);
  271. }
  272. void DlcStateChanged(dbus::Signal* signal) {
  273. dlcservice::DlcState dlc_state;
  274. if (!dbus::MessageReader(signal).PopArrayOfBytesAsProto(&dlc_state)) {
  275. LOG(ERROR) << "Failed to parse proto as install status.";
  276. return;
  277. }
  278. // Notify all observers of change in the state of this DLC.
  279. for (Observer& observer : observers_) {
  280. observer.OnDlcStateChanged(dlc_state);
  281. }
  282. // Skip DLCs not installing from this dlcservice client.
  283. if (installation_holder_.find(dlc_state.id()) == installation_holder_.end())
  284. return;
  285. switch (dlc_state.state()) {
  286. case dlcservice::DlcState::NOT_INSTALLED:
  287. case dlcservice::DlcState::INSTALLED:
  288. SendCompleted(dlc_state);
  289. break;
  290. case dlcservice::DlcState::INSTALLING:
  291. SendProgress(dlc_state);
  292. // Need to return here since we don't want to try starting another
  293. // pending install from the queue (would waste time checking).
  294. return;
  295. default:
  296. NOTREACHED();
  297. }
  298. // Try to run a pending install since we have complete/failed the current
  299. // install, but do not waste trying to run a pending install when the
  300. // current install is running at the moment.
  301. CheckAndRunPendingTask();
  302. }
  303. void DlcStateChangedConnected(const std::string& interface,
  304. const std::string& signal,
  305. bool success) {
  306. LOG_IF(ERROR, !success) << "Failed to connect to DlcStateChanged signal.";
  307. }
  308. void OnInstall(const dlcservice::InstallRequest& install_request,
  309. InstallCallback install_callback,
  310. ProgressCallback progress_callback,
  311. dbus::Response* response,
  312. dbus::ErrorResponse* err_response) {
  313. const std::string& id = install_request.id();
  314. if (response) {
  315. HoldInstallation(install_request, std::move(install_callback),
  316. std::move(progress_callback));
  317. return;
  318. }
  319. const auto err = DlcserviceErrorResponseHandler(err_response).get_err();
  320. if (err == dlcservice::kErrorBusy) {
  321. EnqueueTask(base::BindOnce(&DlcserviceClientImpl::Install,
  322. weak_ptr_factory_.GetWeakPtr(),
  323. install_request, std::move(install_callback),
  324. std::move(progress_callback)));
  325. } else {
  326. HoldInstallation(install_request, std::move(install_callback),
  327. std::move(progress_callback));
  328. dlcservice::DlcState dlc_state;
  329. dlc_state.set_id(id);
  330. dlc_state.set_last_error_code(err);
  331. SendCompleted(dlc_state);
  332. }
  333. CheckAndRunPendingTask();
  334. }
  335. void OnUninstall(UninstallCallback uninstall_callback,
  336. dbus::Response* response,
  337. dbus::ErrorResponse* err_response) {
  338. std::move(uninstall_callback)
  339. .Run(response ? dlcservice::kErrorNone
  340. : DlcserviceErrorResponseHandler(err_response).get_err());
  341. }
  342. void OnPurge(PurgeCallback purge_callback,
  343. dbus::Response* response,
  344. dbus::ErrorResponse* err_response) {
  345. std::move(purge_callback)
  346. .Run(response ? dlcservice::kErrorNone
  347. : DlcserviceErrorResponseHandler(err_response).get_err());
  348. }
  349. void OnGetDlcState(GetDlcStateCallback callback,
  350. dbus::Response* response,
  351. dbus::ErrorResponse* err_response) {
  352. dlcservice::DlcState dlc_state;
  353. if (response &&
  354. dbus::MessageReader(response).PopArrayOfBytesAsProto(&dlc_state)) {
  355. std::move(callback).Run(dlcservice::kErrorNone, dlc_state);
  356. } else {
  357. std::move(callback).Run(
  358. DlcserviceErrorResponseHandler(err_response).get_err(),
  359. dlcservice::DlcState());
  360. }
  361. }
  362. void OnGetExistingDlcs(GetExistingDlcsCallback callback,
  363. dbus::Response* response,
  364. dbus::ErrorResponse* err_response) {
  365. dlcservice::DlcsWithContent dlcs_with_content;
  366. if (response && dbus::MessageReader(response).PopArrayOfBytesAsProto(
  367. &dlcs_with_content)) {
  368. std::move(callback).Run(dlcservice::kErrorNone, dlcs_with_content);
  369. } else {
  370. std::move(callback).Run(
  371. DlcserviceErrorResponseHandler(err_response).get_err(),
  372. dlcservice::DlcsWithContent());
  373. }
  374. }
  375. // TODO(b/164310699): This check is added in order to see if dlcservice daemon
  376. // not being available is the cause of flakes in the CQ.
  377. void CheckServiceAvailable(const std::string& method_name) {
  378. if (!service_available_)
  379. LOG(WARNING) << method_name
  380. << " called when dlcservice is not available.";
  381. }
  382. // DLC ID to `InstallationHolder` mapping.
  383. std::map<std::string, std::vector<InstallationHolder>> installation_holder_;
  384. dbus::ObjectProxy* dlcservice_proxy_;
  385. // TODO(crbug.com/928805): Once platform dlcservice batches, can be removed.
  386. // Specifically when platform dlcservice doesn't return a busy status.
  387. // Whether an install is currently in progress. Can be used to decide whether
  388. // to queue up incoming install requests.
  389. bool installing_ = false;
  390. // A list of postponed installs to dlcservice.
  391. std::deque<base::OnceClosure> pending_tasks_;
  392. // A list of observers that are listening on state changes, etc.
  393. base::ObserverList<Observer> observers_;
  394. // Indicates if dlcservice daemon is available.
  395. bool service_available_ = false;
  396. // Note: This should remain the last member so it'll be destroyed and
  397. // invalidate its weak pointers before any other members are destroyed.
  398. base::WeakPtrFactory<DlcserviceClientImpl> weak_ptr_factory_{this};
  399. };
  400. DlcserviceClient::DlcserviceClient() {
  401. CHECK(!g_instance);
  402. g_instance = this;
  403. }
  404. DlcserviceClient::~DlcserviceClient() {
  405. CHECK_EQ(this, g_instance);
  406. g_instance = nullptr;
  407. }
  408. // static
  409. void DlcserviceClient::Initialize(dbus::Bus* bus) {
  410. CHECK(bus);
  411. (new DlcserviceClientImpl())->Init(bus);
  412. }
  413. // static
  414. void DlcserviceClient::InitializeFake() {
  415. new FakeDlcserviceClient();
  416. }
  417. // static
  418. void DlcserviceClient::Shutdown() {
  419. CHECK(g_instance);
  420. delete g_instance;
  421. }
  422. // static
  423. DlcserviceClient* DlcserviceClient::Get() {
  424. return g_instance;
  425. }
  426. } // namespace chromeos