blob: 697f088c93471174508c1c46929ca6ba6b4e4fb6 [file] [log] [blame]
Xiaochu Liu1ccaed92018-06-13 14:19:55 -07001// Copyright 2018 The Chromium OS 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
Amin Hassanic4cc1ee2019-11-14 11:51:35 -08005#include "dlcservice/dlc_service.h"
Xiaochu Liu1ccaed92018-06-13 14:19:55 -07006
Xiaochu Liue9155842018-11-13 14:24:05 -08007#include <algorithm>
Jae Hoon Kim69cda482019-07-18 14:36:22 -07008#include <memory>
Xiaochu Liuc2b65f52018-07-20 12:47:05 -07009#include <utility>
Xiaochu Liuc2b65f52018-07-20 12:47:05 -070010
Qijiang Fan713061e2021-03-08 15:45:12 +090011#include <base/check.h>
Xiaochu Liuc2b65f52018-07-20 12:47:05 -070012#include <base/files/file_enumerator.h>
13#include <base/files/file_util.h>
Jae Hoon Kim69cda482019-07-18 14:36:22 -070014#include <base/files/scoped_temp_dir.h>
Jae Hoon Kim4a7e06b2020-05-13 09:14:14 -070015#include <base/strings/stringprintf.h>
Andrew55e91e62020-04-28 08:41:52 -070016#include <base/strings/string_util.h>
Xiaochu Liud0184e32018-10-19 17:21:33 -070017#include <brillo/errors/error.h>
Xiaochu Liu5f0b2ba2018-11-26 16:42:04 -080018#include <chromeos/dbus/service_constants.h>
Xiaochu Liu30067a62019-02-08 13:59:10 -080019#include <dbus/dlcservice/dbus-constants.h>
Xiaochu Liuc2b65f52018-07-20 12:47:05 -070020
Amin Hassani86649982020-03-31 16:03:37 -070021#include "dlcservice/dlc.h"
Jae Hoon Kimc98e3a32020-03-13 09:40:48 -070022#include "dlcservice/error.h"
Amin Hassani40c0f112020-04-15 09:55:00 -070023#include "dlcservice/ref_count.h"
Xiaochu Liu6da17272018-11-06 14:04:01 -080024#include "dlcservice/utils.h"
Xiaochu Liuc524c612018-10-23 11:02:52 -070025
Jae Hoon Kim5d095de2019-07-31 13:44:42 -070026using base::Callback;
Jae Hoon Kimc98e3a32020-03-13 09:40:48 -070027using brillo::ErrorPtr;
Jae Hoon Kimb2baf582019-11-12 17:00:57 -080028using brillo::MessageLoop;
Jae Hoon Kim69cda482019-07-18 14:36:22 -070029using std::string;
Jae Hoon Kim80075fb2019-07-24 12:35:58 -070030using update_engine::Operation;
31using update_engine::StatusResult;
Jae Hoon Kim69cda482019-07-18 14:36:22 -070032
Xiaochu Liu1ccaed92018-06-13 14:19:55 -070033namespace dlcservice {
34
Jae Hoon Kimf4eed882020-01-28 17:29:16 -080035DlcService::DlcService()
Amin Hassanif656f292020-06-08 16:20:01 -070036 : periodic_install_check_id_(MessageLoop::kTaskIdNull),
Amin Hassani78a5ec82020-05-19 09:47:49 -070037 weak_ptr_factory_(this) {}
38
39DlcService::~DlcService() {
Amin Hassanif656f292020-06-08 16:20:01 -070040 if (periodic_install_check_id_ != MessageLoop::kTaskIdNull &&
41 !brillo::MessageLoop::current()->CancelTask(periodic_install_check_id_))
Amin Hassani78a5ec82020-05-19 09:47:49 -070042 LOG(ERROR)
43 << "Failed to cancel delayed update_engine check during cleanup.";
44}
45
46void DlcService::Initialize() {
Jae Hoon Kim6e3026d2020-07-10 11:04:29 -070047 auto* system_state = SystemState::Get();
Amin Hassani759090b2020-04-13 12:31:10 -070048 const auto prefs_dir = system_state->dlc_prefs_dir();
Amin Hassanife63fc22020-04-07 11:34:34 -070049 if (!base::PathExists(prefs_dir)) {
50 CHECK(CreateDir(prefs_dir))
51 << "Failed to create dlc prefs directory: " << prefs_dir;
52 }
53
Jae Hoon Kimf4eed882020-01-28 17:29:16 -080054 dlc_manager_ = std::make_unique<DlcManager>();
Xiaochu Liu30067a62019-02-08 13:59:10 -080055
Xiaochu Liu30067a62019-02-08 13:59:10 -080056 // Register D-Bus signal callbacks.
Amin Hassani759090b2020-04-13 12:31:10 -070057 system_state->update_engine()->RegisterStatusUpdateAdvancedSignalHandler(
Jae Hoon Kimc85824c2021-05-12 13:19:14 -070058 base::BindRepeating(&DlcService::OnStatusUpdateAdvancedSignal,
59 weak_ptr_factory_.GetWeakPtr()),
60 base::BindOnce(&DlcService::OnStatusUpdateAdvancedSignalConnected,
61 weak_ptr_factory_.GetWeakPtr()));
Amin Hassani759090b2020-04-13 12:31:10 -070062
63 system_state->session_manager()->RegisterSessionStateChangedSignalHandler(
Jae Hoon Kimc85824c2021-05-12 13:19:14 -070064 base::BindRepeating(&DlcService::OnSessionStateChangedSignal,
65 weak_ptr_factory_.GetWeakPtr()),
66 base::BindOnce(&DlcService::OnSessionStateChangedSignalConnected,
67 weak_ptr_factory_.GetWeakPtr()));
Xiaochu Liuc2b65f52018-07-20 12:47:05 -070068
Amin Hassanica3cbb72020-04-10 12:26:50 -070069 dlc_manager_->Initialize();
Xiaochu Liu46e94b92018-12-03 14:44:35 -080070}
71
Amin Hassani6d0367d2020-05-10 18:07:03 -070072bool DlcService::Install(const DlcId& id,
Amin Hassani65202242020-03-31 12:26:37 -070073 const string& omaha_url,
Jae Hoon Kimc98e3a32020-03-13 09:40:48 -070074 ErrorPtr* err) {
Andrew0a534ed2020-05-06 09:59:17 -070075 bool result = InstallInternal(id, omaha_url, err);
76 // Only send error metrics in here. Install success metrics is sent in
77 // |DlcBase|.
78 if (!result) {
79 SystemState::Get()->metrics()->SendInstallResultFailure(err);
80 Error::ConvertToDbusError(err);
81 }
82 return result;
83}
84
85bool DlcService::InstallInternal(const DlcId& id,
86 const string& omaha_url,
87 ErrorPtr* err) {
Amin Hassani9a3f20c2020-05-25 16:38:33 -070088 // TODO(ahassani): Currently, we create the DLC images even if later we find
89 // out the update_engine is busy and we have to delete the images. It would be
90 // better to know the update_engine status beforehand so we can tell the DLC
91 // to not create the images, just load them if it can. We can do this more
92 // reliably by caching the last status we saw from update_engine, rather than
93 // pulling for it on every install request. That would also allows us to
94 // properly queue the incoming install requests.
95
96 // Try to install and figure out if install through update_engine is needed.
97 bool external_install_needed = false;
98 if (!dlc_manager_->Install(id, &external_install_needed, err)) {
Andrew936ccf62020-06-12 13:00:23 -070099 LOG(ERROR) << "Failed to install DLC=" << id;
Amin Hassani69dfe6e2019-03-20 14:58:48 -0700100 return false;
101 }
102
Amin Hassani9a3f20c2020-05-25 16:38:33 -0700103 // Install through update_engine only if needed.
Amin Hassanif656f292020-06-08 16:20:01 -0700104 if (!external_install_needed)
105 return true;
106
107 if (!InstallWithUpdateEngine(id, omaha_url, err)) {
Amin Hassani9a3f20c2020-05-25 16:38:33 -0700108 // dlcservice must cancel the install as update_engine won't be able to
109 // install the initialized DLC.
Jae Hoon Kim53514af2020-07-27 11:50:26 -0700110 CancelInstall(*err);
Amin Hassani9a3f20c2020-05-25 16:38:33 -0700111 return false;
112 }
113
Amin Hassanif656f292020-06-08 16:20:01 -0700114 // By now the update_engine is installing the DLC, so schedule a periodic
115 // install checker in case we miss update_engine signals.
116 SchedulePeriodicInstallCheck();
117
Amin Hassani9a3f20c2020-05-25 16:38:33 -0700118 return true;
119}
120
121bool DlcService::InstallWithUpdateEngine(const DlcId& id,
122 const string& omaha_url,
123 ErrorPtr* err) {
Jae Hoon Kim7d0001e2021-07-07 13:04:40 -0700124 // Need to set in order for the cancellation of DLC setup.
125 installing_dlc_id_ = id;
126
Jae Hoon Kim1855d262020-02-14 16:06:35 -0800127 // Check what state update_engine is in.
Amin Hassanif656f292020-06-08 16:20:01 -0700128 if (SystemState::Get()->update_engine_status().current_operation() ==
129 update_engine::UPDATED_NEED_REBOOT) {
130 *err =
131 Error::Create(FROM_HERE, kErrorNeedReboot,
132 "Update Engine applied update, device needs a reboot.");
Jae Hoon Kim1855d262020-02-14 16:06:35 -0800133 return false;
134 }
Jae Hoon Kim582df652019-11-26 15:41:09 -0800135
Jae Hoon Kim4a7e06b2020-05-13 09:14:14 -0700136 LOG(INFO) << "Sending request to update_engine to install DLC=" << id;
Amin Hassani86649982020-03-31 16:03:37 -0700137 // Invokes update_engine to install the DLC.
Amin Hassani111f2bc2020-04-17 11:53:32 -0700138 ErrorPtr tmp_err;
Jae Hoon Kim4a7e06b2020-05-13 09:14:14 -0700139 if (!SystemState::Get()->update_engine()->AttemptInstall(omaha_url, {id},
140 &tmp_err)) {
Jae Hoon Kim52a5df52019-08-14 13:45:38 -0700141 // TODO(kimjae): need update engine to propagate correct error message by
142 // passing in |ErrorPtr| and being set within update engine, current default
143 // is to indicate that update engine is updating because there is no way an
144 // install should have taken place if not through dlcservice. (could also be
145 // the case that an update applied between the time of the last status check
146 // above, but just return |kErrorBusy| because the next time around if an
147 // update has been applied and is in a reboot needed state, it will indicate
148 // correctly then).
Amin Hassani111f2bc2020-04-17 11:53:32 -0700149 LOG(ERROR) << "Update Engine failed to install requested DLCs: "
150 << (tmp_err ? Error::ToString(tmp_err)
151 : "Missing error from update engine proxy.");
Andrew9aa06442020-05-06 10:33:46 -0700152 *err =
153 Error::Create(FROM_HERE, kErrorBusy,
154 "Update Engine failed to schedule install operations.");
Xiaochu Liue9155842018-11-13 14:24:05 -0800155 return false;
156 }
157
Xiaochu Liue9155842018-11-13 14:24:05 -0800158 return true;
Xiaochu Liu1ccaed92018-06-13 14:19:55 -0700159}
160
Amin Hassani605988d2020-05-10 19:36:35 -0700161bool DlcService::Uninstall(const string& id, brillo::ErrorPtr* err) {
Andrew2eb95542020-06-24 14:51:49 -0700162 bool result = dlc_manager_->Uninstall(id, err);
163 SystemState::Get()->metrics()->SendUninstallResult(err);
164 if (!result)
165 Error::ConvertToDbusError(err);
166
167 return result;
Amin Hassanib9d74b12020-04-08 15:35:20 -0700168}
169
Amin Hassani605988d2020-05-10 19:36:35 -0700170bool DlcService::Purge(const string& id, brillo::ErrorPtr* err) {
Amin Hassani40c0f112020-04-15 09:55:00 -0700171 return dlc_manager_->Purge(id, err);
Xiaochu Liu1ccaed92018-06-13 14:19:55 -0700172}
173
Amin Hassani1ac28312020-06-04 18:16:30 -0700174const DlcBase* DlcService::GetDlc(const DlcId& id, brillo::ErrorPtr* err) {
175 return dlc_manager_->GetDlc(id, err);
Jae Hoon Kimee1ba902020-03-11 09:53:01 -0700176}
177
Amin Hassani9ca846f2020-04-17 12:41:01 -0700178DlcIdList DlcService::GetInstalled() {
Jae Hoon Kim9a27d152020-04-10 12:50:14 -0700179 return dlc_manager_->GetInstalled();
180}
181
Amin Hassanid5fc8b22020-04-29 12:44:52 -0700182DlcIdList DlcService::GetExistingDlcs() {
183 return dlc_manager_->GetExistingDlcs();
184}
185
Amin Hassani38f36792020-04-17 11:47:08 -0700186DlcIdList DlcService::GetDlcsToUpdate() {
187 return dlc_manager_->GetDlcsToUpdate();
188}
189
Amin Hassani605988d2020-05-10 19:36:35 -0700190bool DlcService::InstallCompleted(const DlcIdList& ids, ErrorPtr* err) {
191 return dlc_manager_->InstallCompleted(ids, err);
Jae Hoon Kim9a27d152020-04-10 12:50:14 -0700192}
193
Amin Hassani605988d2020-05-10 19:36:35 -0700194bool DlcService::UpdateCompleted(const DlcIdList& ids, ErrorPtr* err) {
195 return dlc_manager_->UpdateCompleted(ids, err);
Jae Hoon Kim9a27d152020-04-10 12:50:14 -0700196}
197
Jae Hoon Kim53514af2020-07-27 11:50:26 -0700198bool DlcService::FinishInstall(ErrorPtr* err) {
199 if (!installing_dlc_id_) {
200 LOG(ERROR) << "No DLC installation to finish.";
201 return false;
202 }
203 auto id = installing_dlc_id_.value();
204 installing_dlc_id_.reset();
205 return dlc_manager_->FinishInstall(id, err);
206}
207
Andrewbcc4bd82020-06-11 14:23:55 -0700208void DlcService::CancelInstall(const ErrorPtr& err_in) {
Jae Hoon Kim53514af2020-07-27 11:50:26 -0700209 if (!installing_dlc_id_) {
210 LOG(ERROR) << "No DLC installation to cancel.";
211 return;
212 }
213 auto id = installing_dlc_id_.value();
214 installing_dlc_id_.reset();
Jae Hoon Kimc98e3a32020-03-13 09:40:48 -0700215 ErrorPtr tmp_err;
Jae Hoon Kim53514af2020-07-27 11:50:26 -0700216 if (!dlc_manager_->CancelInstall(id, err_in, &tmp_err))
217 LOG(ERROR) << "Failed to cancel install for DLC=" << id;
Jae Hoon Kimb2baf582019-11-12 17:00:57 -0800218}
219
Jae Hoon Kim5f95ab92019-11-26 16:52:43 -0800220void DlcService::PeriodicInstallCheck() {
Amin Hassanif656f292020-06-08 16:20:01 -0700221 periodic_install_check_id_ = MessageLoop::kTaskIdNull;
Jae Hoon Kimb2baf582019-11-12 17:00:57 -0800222
Amin Hassanif656f292020-06-08 16:20:01 -0700223 // If we're not installing anything anymore, no need to schedule again.
Jae Hoon Kim53514af2020-07-27 11:50:26 -0700224 if (!installing_dlc_id_)
Jae Hoon Kimb2baf582019-11-12 17:00:57 -0800225 return;
Jae Hoon Kimb2baf582019-11-12 17:00:57 -0800226
Amin Hassanif656f292020-06-08 16:20:01 -0700227 const int kNotSeenStatusDelay = 10;
Jae Hoon Kim6e3026d2020-07-10 11:04:29 -0700228 auto* system_state = SystemState::Get();
Amin Hassanif656f292020-06-08 16:20:01 -0700229 if ((system_state->clock()->Now() -
230 system_state->update_engine_status_timestamp()) >
231 base::TimeDelta::FromSeconds(kNotSeenStatusDelay)) {
232 if (GetUpdateEngineStatus()) {
233 ErrorPtr tmp_error;
234 if (!HandleStatusResult(&tmp_error)) {
Jae Hoon Kim5f95ab92019-11-26 16:52:43 -0800235 return;
236 }
Jae Hoon Kim06c86142019-12-18 17:50:32 -0800237 }
238 }
Amin Hassanif656f292020-06-08 16:20:01 -0700239
240 SchedulePeriodicInstallCheck();
Jae Hoon Kim5f95ab92019-11-26 16:52:43 -0800241}
242
Amin Hassanif656f292020-06-08 16:20:01 -0700243void DlcService::SchedulePeriodicInstallCheck() {
244 if (periodic_install_check_id_ != MessageLoop::kTaskIdNull) {
245 LOG(INFO) << "Another periodic install check already scheduled.";
246 return;
247 }
248
249 periodic_install_check_id_ = brillo::MessageLoop::current()->PostDelayedTask(
250 FROM_HERE,
Jae Hoon Kimc85824c2021-05-12 13:19:14 -0700251 base::BindOnce(&DlcService::PeriodicInstallCheck,
252 weak_ptr_factory_.GetWeakPtr()),
Amin Hassanif656f292020-06-08 16:20:01 -0700253 base::TimeDelta::FromSeconds(kUECheckTimeout));
254}
255
256bool DlcService::HandleStatusResult(brillo::ErrorPtr* err) {
257 // If we are not installing any DLC(s), no need to even handle status result.
Jae Hoon Kim53514af2020-07-27 11:50:26 -0700258 if (!installing_dlc_id_)
Amin Hassanif656f292020-06-08 16:20:01 -0700259 return true;
260
261 const StatusResult& status = SystemState::Get()->update_engine_status();
262 if (!status.is_install()) {
Andrew0a534ed2020-05-06 09:59:17 -0700263 *err = Error::CreateInternal(
264 FROM_HERE, error::kFailedInstallInUpdateEngine,
Andrew3c758722020-06-02 10:25:19 -0700265 "Signal from update_engine indicates that it's not for an install, but "
266 "dlcservice was waiting for an install.");
Andrewbcc4bd82020-06-11 14:23:55 -0700267 CancelInstall(*err);
Andrew0a534ed2020-05-06 09:59:17 -0700268 SystemState::Get()->metrics()->SendInstallResultFailure(err);
Jae Hoon Kimc9c94b42020-02-21 10:44:02 -0800269 return false;
Jae Hoon Kim1c9d8582019-10-14 10:52:05 -0700270 }
271
Amin Hassanif656f292020-06-08 16:20:01 -0700272 switch (status.current_operation()) {
273 case update_engine::UPDATED_NEED_REBOOT:
274 *err =
275 Error::Create(FROM_HERE, kErrorNeedReboot,
276 "Update Engine applied update, device needs a reboot.");
277 break;
278 case Operation::IDLE:
Jae Hoon Kim39aa9de2019-11-20 09:52:06 -0800279 LOG(INFO)
280 << "Signal from update_engine, proceeding to complete installation.";
Andrew0a534ed2020-05-06 09:59:17 -0700281 // Send metrics in |DlcBase::FinishInstall| and not here since we might
282 // be executing this call for multiple DLCs.
Jae Hoon Kim53514af2020-07-27 11:50:26 -0700283 if (!FinishInstall(err)) {
Andrew936ccf62020-06-12 13:00:23 -0700284 LOG(ERROR) << "Failed to finish install.";
Andrew3c758722020-06-02 10:25:19 -0700285 return false;
286 }
Jae Hoon Kim39aa9de2019-11-20 09:52:06 -0800287 return true;
Jae Hoon Kim823ead32019-12-13 09:30:09 -0800288 case Operation::REPORTING_ERROR_EVENT:
Andrew0a534ed2020-05-06 09:59:17 -0700289 *err =
290 Error::CreateInternal(FROM_HERE, error::kFailedInstallInUpdateEngine,
291 "update_engine indicates reporting failure.");
Amin Hassanif656f292020-06-08 16:20:01 -0700292 break;
Amin Hassanic2e46992020-05-21 12:34:05 -0700293 // Only when update_engine's |Operation::DOWNLOADING| should the DLC send
294 // |DlcState::INSTALLING|. Majority of the install process for DLC(s) is
295 // during |Operation::DOWNLOADING|, this also means that only a single
296 // growth from 0.0 to 1.0 for progress reporting will happen.
Jae Hoon Kim39aa9de2019-11-20 09:52:06 -0800297 case Operation::DOWNLOADING:
Amin Hassani78a5ec82020-05-19 09:47:49 -0700298 // TODO(ahassani): Add unittest for this.
Amin Hassanif656f292020-06-08 16:20:01 -0700299 dlc_manager_->ChangeProgress(status.progress());
Amin Hassani78a5ec82020-05-19 09:47:49 -0700300
Jae Hoon Kim39aa9de2019-11-20 09:52:06 -0800301 FALLTHROUGH;
302 default:
Andrew3c758722020-06-02 10:25:19 -0700303 return true;
Jae Hoon Kimc19dabe2019-07-31 09:37:35 -0700304 }
Amin Hassanif656f292020-06-08 16:20:01 -0700305
Andrewbcc4bd82020-06-11 14:23:55 -0700306 CancelInstall(*err);
Andrew0a534ed2020-05-06 09:59:17 -0700307 SystemState::Get()->metrics()->SendInstallResultFailure(err);
Amin Hassanif656f292020-06-08 16:20:01 -0700308 return false;
Jae Hoon Kimc7beafd2019-07-22 18:14:47 -0700309}
310
Amin Hassanif656f292020-06-08 16:20:01 -0700311bool DlcService::GetUpdateEngineStatus() {
Jae Hoon Kimacc2c862019-07-30 21:25:39 -0700312 StatusResult status_result;
Amin Hassani759090b2020-04-13 12:31:10 -0700313 if (!SystemState::Get()->update_engine()->GetStatusAdvanced(&status_result,
314 nullptr)) {
Amin Hassanif656f292020-06-08 16:20:01 -0700315 LOG(ERROR) << "Failed to get update_engine status, will try again later.";
Xiaochu Liue9155842018-11-13 14:24:05 -0800316 return false;
317 }
Amin Hassanif656f292020-06-08 16:20:01 -0700318 SystemState::Get()->set_update_engine_status(status_result);
319 LOG(INFO) << "Got update_engine status: "
320 << status_result.current_operation();
Xiaochu Liue9155842018-11-13 14:24:05 -0800321 return true;
322}
323
Amin Hassanic4cc1ee2019-11-14 11:51:35 -0800324void DlcService::OnStatusUpdateAdvancedSignal(
Jae Hoon Kim80075fb2019-07-24 12:35:58 -0700325 const StatusResult& status_result) {
Amin Hassanif656f292020-06-08 16:20:01 -0700326 // Always set the status.
327 SystemState::Get()->set_update_engine_status(status_result);
Andrew3c758722020-06-02 10:25:19 -0700328
329 ErrorPtr err;
Amin Hassanif656f292020-06-08 16:20:01 -0700330 if (!HandleStatusResult(&err))
Andrew0a534ed2020-05-06 09:59:17 -0700331 DCHECK(err.get());
Xiaochu Liu30067a62019-02-08 13:59:10 -0800332}
333
Amin Hassanic4cc1ee2019-11-14 11:51:35 -0800334void DlcService::OnStatusUpdateAdvancedSignalConnected(
Jae Hoon Kim69cda482019-07-18 14:36:22 -0700335 const string& interface_name, const string& signal_name, bool success) {
Xiaochu Liu30067a62019-02-08 13:59:10 -0800336 if (!success) {
337 LOG(ERROR) << "Failed to connect to update_engine's StatusUpdate signal.";
338 }
Amin Hassanif656f292020-06-08 16:20:01 -0700339 if (!GetUpdateEngineStatus()) {
340 // As a last resort, if we couldn't get the status, just set the status to
341 // IDLE, so things can move forward. This is mostly the case because when
342 // update_engine comes up its first status is IDLE and it will stay that way
343 // for quite a while.
344 StatusResult status;
345 status.set_current_operation(Operation::IDLE);
346 status.set_is_install(false);
347 }
Xiaochu Liu30067a62019-02-08 13:59:10 -0800348}
349
Amin Hassani759090b2020-04-13 12:31:10 -0700350void DlcService::OnSessionStateChangedSignalConnected(
351 const string& interface_name, const string& signal_name, bool success) {
352 if (!success) {
353 LOG(ERROR) << "Failed to connect to session_manager's SessionStateChanged "
354 << "signal.";
355 }
356}
357
358void DlcService::OnSessionStateChangedSignal(const std::string& state) {
Amin Hassani40c0f112020-04-15 09:55:00 -0700359 UserRefCount::SessionChanged(state);
Amin Hassani759090b2020-04-13 12:31:10 -0700360}
361
Xiaochu Liu1ccaed92018-06-13 14:19:55 -0700362} // namespace dlcservice