1 // 2 // Copyright (C) 2011 The Android Open Source Project 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 17 #include "update_engine/payload_consumer/download_action.h" 18 19 #include <gmock/gmock.h> 20 #include <gtest/gtest.h> 21 22 #include <memory> 23 #include <string> 24 #include <utility> 25 #include <vector> 26 27 #include <base/bind.h> 28 #include <base/files/file_path.h> 29 #include <base/files/file_util.h> 30 #include <base/location.h> 31 #include <base/strings/stringprintf.h> 32 #include <brillo/bind_lambda.h> 33 #include <brillo/message_loops/fake_message_loop.h> 34 #include <brillo/message_loops/message_loop.h> 35 36 #include "update_engine/common/action_pipe.h" 37 #include "update_engine/common/hash_calculator.h" 38 #include "update_engine/common/mock_http_fetcher.h" 39 #include "update_engine/common/mock_prefs.h" 40 #include "update_engine/common/test_utils.h" 41 #include "update_engine/common/utils.h" 42 #include "update_engine/fake_p2p_manager_configuration.h" 43 #include "update_engine/fake_system_state.h" 44 #include "update_engine/payload_consumer/mock_download_action.h" 45 #include "update_engine/update_manager/fake_update_manager.h" 46 47 namespace chromeos_update_engine { 48 49 using base::FilePath; 50 using base::ReadFileToString; 51 using base::WriteFile; 52 using std::string; 53 using std::unique_ptr; 54 using std::vector; 55 using test_utils::ScopedTempFile; 56 using testing::AtLeast; 57 using testing::InSequence; 58 using testing::Return; 59 using testing::_; 60 61 class DownloadActionTest : public ::testing::Test { }; 62 63 namespace { 64 65 class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate { 66 public: 67 explicit DownloadActionTestProcessorDelegate(ErrorCode expected_code) 68 : processing_done_called_(false), 69 expected_code_(expected_code) {} 70 ~DownloadActionTestProcessorDelegate() override { 71 EXPECT_TRUE(processing_done_called_); 72 } 73 void ProcessingDone(const ActionProcessor* processor, 74 ErrorCode code) override { 75 brillo::MessageLoop::current()->BreakLoop(); 76 brillo::Blob found_data; 77 ASSERT_TRUE(utils::ReadFile(path_, &found_data)); 78 if (expected_code_ != ErrorCode::kDownloadWriteError) { 79 ASSERT_EQ(expected_data_.size(), found_data.size()); 80 for (unsigned i = 0; i < expected_data_.size(); i++) { 81 EXPECT_EQ(expected_data_[i], found_data[i]); 82 } 83 } 84 processing_done_called_ = true; 85 } 86 87 void ActionCompleted(ActionProcessor* processor, 88 AbstractAction* action, 89 ErrorCode code) override { 90 const string type = action->Type(); 91 if (type == DownloadAction::StaticType()) { 92 EXPECT_EQ(expected_code_, code); 93 } else { 94 EXPECT_EQ(ErrorCode::kSuccess, code); 95 } 96 } 97 98 string path_; 99 brillo::Blob expected_data_; 100 bool processing_done_called_; 101 ErrorCode expected_code_; 102 }; 103 104 class TestDirectFileWriter : public DirectFileWriter { 105 public: 106 TestDirectFileWriter() : fail_write_(0), current_write_(0) {} 107 void set_fail_write(int fail_write) { fail_write_ = fail_write; } 108 109 virtual bool Write(const void* bytes, size_t count) { 110 if (++current_write_ == fail_write_) { 111 return false; 112 } 113 return DirectFileWriter::Write(bytes, count); 114 } 115 116 private: 117 // If positive, fail on the |fail_write_| call to Write. 118 int fail_write_; 119 int current_write_; 120 }; 121 122 void StartProcessorInRunLoop(ActionProcessor* processor, 123 MockHttpFetcher* http_fetcher) { 124 processor->StartProcessing(); 125 http_fetcher->SetOffset(1); 126 } 127 128 void TestWithData(const brillo::Blob& data, 129 int fail_write, 130 bool use_download_delegate) { 131 brillo::FakeMessageLoop loop(nullptr); 132 loop.SetAsCurrent(); 133 FakeSystemState fake_system_state; 134 135 // TODO(adlr): see if we need a different file for build bots 136 ScopedTempFile output_temp_file; 137 TestDirectFileWriter writer; 138 EXPECT_EQ( 139 0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0)); 140 writer.set_fail_write(fail_write); 141 142 // We pull off the first byte from data and seek past it. 143 string hash = HashCalculator::HashOfBytes(&data[1], data.size() - 1); 144 uint64_t size = data.size(); 145 InstallPlan install_plan; 146 install_plan.payload_type = InstallPayloadType::kDelta; 147 install_plan.payload_size = size; 148 install_plan.payload_hash = hash; 149 install_plan.source_slot = 0; 150 install_plan.target_slot = 1; 151 // We mark both slots as bootable. Only the target slot should be unbootable 152 // after the download starts. 153 fake_system_state.fake_boot_control()->SetSlotBootable( 154 install_plan.source_slot, true); 155 fake_system_state.fake_boot_control()->SetSlotBootable( 156 install_plan.target_slot, true); 157 ObjectFeederAction<InstallPlan> feeder_action; 158 feeder_action.set_obj(install_plan); 159 MockPrefs prefs; 160 MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(), 161 data.size(), 162 nullptr); 163 // takes ownership of passed in HttpFetcher 164 DownloadAction download_action(&prefs, 165 fake_system_state.boot_control(), 166 fake_system_state.hardware(), 167 &fake_system_state, 168 http_fetcher); 169 download_action.SetTestFileWriter(&writer); 170 BondActions(&feeder_action, &download_action); 171 MockDownloadActionDelegate download_delegate; 172 if (use_download_delegate) { 173 InSequence s; 174 download_action.set_delegate(&download_delegate); 175 if (data.size() > kMockHttpFetcherChunkSize) 176 EXPECT_CALL(download_delegate, 177 BytesReceived(_, 1 + kMockHttpFetcherChunkSize, _)); 178 EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(AtLeast(1)); 179 } 180 ErrorCode expected_code = ErrorCode::kSuccess; 181 if (fail_write > 0) 182 expected_code = ErrorCode::kDownloadWriteError; 183 DownloadActionTestProcessorDelegate delegate(expected_code); 184 delegate.expected_data_ = brillo::Blob(data.begin() + 1, data.end()); 185 delegate.path_ = output_temp_file.path(); 186 ActionProcessor processor; 187 processor.set_delegate(&delegate); 188 processor.EnqueueAction(&feeder_action); 189 processor.EnqueueAction(&download_action); 190 191 loop.PostTask(FROM_HERE, 192 base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher)); 193 loop.Run(); 194 EXPECT_FALSE(loop.PendingTasks()); 195 196 EXPECT_TRUE(fake_system_state.fake_boot_control()->IsSlotBootable( 197 install_plan.source_slot)); 198 EXPECT_FALSE(fake_system_state.fake_boot_control()->IsSlotBootable( 199 install_plan.target_slot)); 200 } 201 } // namespace 202 203 TEST(DownloadActionTest, SimpleTest) { 204 brillo::Blob small; 205 const char* foo = "foo"; 206 small.insert(small.end(), foo, foo + strlen(foo)); 207 TestWithData(small, 208 0, // fail_write 209 true); // use_download_delegate 210 } 211 212 TEST(DownloadActionTest, LargeTest) { 213 brillo::Blob big(5 * kMockHttpFetcherChunkSize); 214 char c = '0'; 215 for (unsigned int i = 0; i < big.size(); i++) { 216 big[i] = c; 217 c = ('9' == c) ? '0' : c + 1; 218 } 219 TestWithData(big, 220 0, // fail_write 221 true); // use_download_delegate 222 } 223 224 TEST(DownloadActionTest, FailWriteTest) { 225 brillo::Blob big(5 * kMockHttpFetcherChunkSize); 226 char c = '0'; 227 for (unsigned int i = 0; i < big.size(); i++) { 228 big[i] = c; 229 c = ('9' == c) ? '0' : c + 1; 230 } 231 TestWithData(big, 232 2, // fail_write 233 true); // use_download_delegate 234 } 235 236 TEST(DownloadActionTest, NoDownloadDelegateTest) { 237 brillo::Blob small; 238 const char* foo = "foofoo"; 239 small.insert(small.end(), foo, foo + strlen(foo)); 240 TestWithData(small, 241 0, // fail_write 242 false); // use_download_delegate 243 } 244 245 namespace { 246 class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate { 247 public: 248 void ProcessingStopped(const ActionProcessor* processor) { 249 brillo::MessageLoop::current()->BreakLoop(); 250 } 251 }; 252 253 void TerminateEarlyTestStarter(ActionProcessor* processor) { 254 processor->StartProcessing(); 255 CHECK(processor->IsRunning()); 256 processor->StopProcessing(); 257 } 258 259 void TestTerminateEarly(bool use_download_delegate) { 260 brillo::FakeMessageLoop loop(nullptr); 261 loop.SetAsCurrent(); 262 263 brillo::Blob data(kMockHttpFetcherChunkSize + 264 kMockHttpFetcherChunkSize / 2); 265 memset(data.data(), 0, data.size()); 266 267 ScopedTempFile temp_file; 268 { 269 DirectFileWriter writer; 270 EXPECT_EQ(0, writer.Open(temp_file.path().c_str(), O_WRONLY | O_CREAT, 0)); 271 272 // takes ownership of passed in HttpFetcher 273 ObjectFeederAction<InstallPlan> feeder_action; 274 InstallPlan install_plan; 275 feeder_action.set_obj(install_plan); 276 FakeSystemState fake_system_state_; 277 MockPrefs prefs; 278 DownloadAction download_action( 279 &prefs, 280 fake_system_state_.boot_control(), 281 fake_system_state_.hardware(), 282 &fake_system_state_, 283 new MockHttpFetcher(data.data(), data.size(), nullptr)); 284 download_action.SetTestFileWriter(&writer); 285 MockDownloadActionDelegate download_delegate; 286 if (use_download_delegate) { 287 download_action.set_delegate(&download_delegate); 288 EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(0); 289 } 290 TerminateEarlyTestProcessorDelegate delegate; 291 ActionProcessor processor; 292 processor.set_delegate(&delegate); 293 processor.EnqueueAction(&feeder_action); 294 processor.EnqueueAction(&download_action); 295 BondActions(&feeder_action, &download_action); 296 297 loop.PostTask(FROM_HERE, 298 base::Bind(&TerminateEarlyTestStarter, &processor)); 299 loop.Run(); 300 EXPECT_FALSE(loop.PendingTasks()); 301 } 302 303 // 1 or 0 chunks should have come through 304 const off_t resulting_file_size(utils::FileSize(temp_file.path())); 305 EXPECT_GE(resulting_file_size, 0); 306 if (resulting_file_size != 0) 307 EXPECT_EQ(kMockHttpFetcherChunkSize, 308 static_cast<size_t>(resulting_file_size)); 309 } 310 311 } // namespace 312 313 TEST(DownloadActionTest, TerminateEarlyTest) { 314 TestTerminateEarly(true); 315 } 316 317 TEST(DownloadActionTest, TerminateEarlyNoDownloadDelegateTest) { 318 TestTerminateEarly(false); 319 } 320 321 class DownloadActionTestAction; 322 323 template<> 324 class ActionTraits<DownloadActionTestAction> { 325 public: 326 typedef InstallPlan OutputObjectType; 327 typedef InstallPlan InputObjectType; 328 }; 329 330 // This is a simple Action class for testing. 331 class DownloadActionTestAction : public Action<DownloadActionTestAction> { 332 public: 333 DownloadActionTestAction() : did_run_(false) {} 334 typedef InstallPlan InputObjectType; 335 typedef InstallPlan OutputObjectType; 336 ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); } 337 ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); } 338 ActionProcessor* processor() { return processor_; } 339 void PerformAction() { 340 did_run_ = true; 341 ASSERT_TRUE(HasInputObject()); 342 EXPECT_TRUE(expected_input_object_ == GetInputObject()); 343 ASSERT_TRUE(processor()); 344 processor()->ActionComplete(this, ErrorCode::kSuccess); 345 } 346 string Type() const { return "DownloadActionTestAction"; } 347 InstallPlan expected_input_object_; 348 bool did_run_; 349 }; 350 351 namespace { 352 // This class is an ActionProcessorDelegate that simply terminates the 353 // run loop when the ActionProcessor has completed processing. It's used 354 // only by the test PassObjectOutTest. 355 class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate { 356 public: 357 void ProcessingDone(const ActionProcessor* processor, ErrorCode code) { 358 brillo::MessageLoop::current()->BreakLoop(); 359 } 360 }; 361 362 } // namespace 363 364 TEST(DownloadActionTest, PassObjectOutTest) { 365 brillo::FakeMessageLoop loop(nullptr); 366 loop.SetAsCurrent(); 367 368 DirectFileWriter writer; 369 EXPECT_EQ(0, writer.Open("/dev/null", O_WRONLY | O_CREAT, 0)); 370 371 // takes ownership of passed in HttpFetcher 372 InstallPlan install_plan; 373 install_plan.payload_size = 1; 374 install_plan.payload_hash = HashCalculator::HashOfString("x"); 375 ObjectFeederAction<InstallPlan> feeder_action; 376 feeder_action.set_obj(install_plan); 377 MockPrefs prefs; 378 FakeSystemState fake_system_state_; 379 DownloadAction download_action(&prefs, 380 fake_system_state_.boot_control(), 381 fake_system_state_.hardware(), 382 &fake_system_state_, 383 new MockHttpFetcher("x", 1, nullptr)); 384 download_action.SetTestFileWriter(&writer); 385 386 DownloadActionTestAction test_action; 387 test_action.expected_input_object_ = install_plan; 388 BondActions(&feeder_action, &download_action); 389 BondActions(&download_action, &test_action); 390 391 ActionProcessor processor; 392 PassObjectOutTestProcessorDelegate delegate; 393 processor.set_delegate(&delegate); 394 processor.EnqueueAction(&feeder_action); 395 processor.EnqueueAction(&download_action); 396 processor.EnqueueAction(&test_action); 397 398 loop.PostTask(FROM_HERE, 399 base::Bind([&processor] { processor.StartProcessing(); })); 400 loop.Run(); 401 EXPECT_FALSE(loop.PendingTasks()); 402 403 EXPECT_EQ(true, test_action.did_run_); 404 } 405 406 // Test fixture for P2P tests. 407 class P2PDownloadActionTest : public testing::Test { 408 protected: 409 P2PDownloadActionTest() 410 : start_at_offset_(0), 411 fake_um_(fake_system_state_.fake_clock()) {} 412 413 ~P2PDownloadActionTest() override {} 414 415 // Derived from testing::Test. 416 void SetUp() override { 417 loop_.SetAsCurrent(); 418 } 419 420 // Derived from testing::Test. 421 void TearDown() override { 422 EXPECT_FALSE(loop_.PendingTasks()); 423 } 424 425 // To be called by tests to setup the download. The 426 // |starting_offset| parameter is for where to resume. 427 void SetupDownload(off_t starting_offset) { 428 start_at_offset_ = starting_offset; 429 // Prepare data 10 kB of data. 430 data_.clear(); 431 for (unsigned int i = 0; i < 10 * 1000; i++) 432 data_ += 'a' + (i % 25); 433 434 // Setup p2p. 435 FakeP2PManagerConfiguration *test_conf = new FakeP2PManagerConfiguration(); 436 p2p_manager_.reset(P2PManager::Construct( 437 test_conf, nullptr, &fake_um_, "cros_au", 3, 438 base::TimeDelta::FromDays(5))); 439 fake_system_state_.set_p2p_manager(p2p_manager_.get()); 440 } 441 442 // To be called by tests to perform the download. The 443 // |use_p2p_to_share| parameter is used to indicate whether the 444 // payload should be shared via p2p. 445 void StartDownload(bool use_p2p_to_share) { 446 EXPECT_CALL(*fake_system_state_.mock_payload_state(), 447 GetUsingP2PForSharing()) 448 .WillRepeatedly(Return(use_p2p_to_share)); 449 450 ScopedTempFile output_temp_file; 451 TestDirectFileWriter writer; 452 EXPECT_EQ( 453 0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0)); 454 InstallPlan install_plan; 455 install_plan.payload_size = data_.length(); 456 install_plan.payload_hash = "1234hash"; 457 ObjectFeederAction<InstallPlan> feeder_action; 458 feeder_action.set_obj(install_plan); 459 MockPrefs prefs; 460 http_fetcher_ = new MockHttpFetcher(data_.c_str(), 461 data_.length(), 462 nullptr); 463 // Note that DownloadAction takes ownership of the passed in HttpFetcher. 464 download_action_.reset(new DownloadAction(&prefs, 465 fake_system_state_.boot_control(), 466 fake_system_state_.hardware(), 467 &fake_system_state_, 468 http_fetcher_)); 469 download_action_->SetTestFileWriter(&writer); 470 BondActions(&feeder_action, download_action_.get()); 471 DownloadActionTestProcessorDelegate delegate(ErrorCode::kSuccess); 472 delegate.expected_data_ = brillo::Blob(data_.begin() + start_at_offset_, 473 data_.end()); 474 delegate.path_ = output_temp_file.path(); 475 processor_.set_delegate(&delegate); 476 processor_.EnqueueAction(&feeder_action); 477 processor_.EnqueueAction(download_action_.get()); 478 479 loop_.PostTask(FROM_HERE, base::Bind( 480 &P2PDownloadActionTest::StartProcessorInRunLoopForP2P, 481 base::Unretained(this))); 482 loop_.Run(); 483 } 484 485 // Mainloop used to make StartDownload() synchronous. 486 brillo::FakeMessageLoop loop_{nullptr}; 487 488 // The DownloadAction instance under test. 489 unique_ptr<DownloadAction> download_action_; 490 491 // The HttpFetcher used in the test. 492 MockHttpFetcher* http_fetcher_; 493 494 // The P2PManager used in the test. 495 unique_ptr<P2PManager> p2p_manager_; 496 497 // The ActionProcessor used for running the actions. 498 ActionProcessor processor_; 499 500 // A fake system state. 501 FakeSystemState fake_system_state_; 502 503 // The data being downloaded. 504 string data_; 505 506 private: 507 // Callback used in StartDownload() method. 508 void StartProcessorInRunLoopForP2P() { 509 processor_.StartProcessing(); 510 http_fetcher_->SetOffset(start_at_offset_); 511 } 512 513 // The requested starting offset passed to SetupDownload(). 514 off_t start_at_offset_; 515 516 chromeos_update_manager::FakeUpdateManager fake_um_; 517 }; 518 519 TEST_F(P2PDownloadActionTest, IsWrittenTo) { 520 if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) { 521 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 522 << "Please update your system to support this feature."; 523 return; 524 } 525 526 SetupDownload(0); // starting_offset 527 StartDownload(true); // use_p2p_to_share 528 529 // Check the p2p file and its content matches what was sent. 530 string file_id = download_action_->p2p_file_id(); 531 EXPECT_NE("", file_id); 532 EXPECT_EQ(static_cast<int>(data_.length()), 533 p2p_manager_->FileGetSize(file_id)); 534 EXPECT_EQ(static_cast<int>(data_.length()), 535 p2p_manager_->FileGetExpectedSize(file_id)); 536 string p2p_file_contents; 537 EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id), 538 &p2p_file_contents)); 539 EXPECT_EQ(data_, p2p_file_contents); 540 } 541 542 TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) { 543 if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) { 544 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 545 << "Please update your system to support this feature."; 546 return; 547 } 548 549 SetupDownload(1000); // starting_offset 550 StartDownload(true); // use_p2p_to_share 551 552 // DownloadAction should convey that the file is not being shared. 553 // and that we don't have any p2p files. 554 EXPECT_EQ(download_action_->p2p_file_id(), ""); 555 EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0); 556 } 557 558 TEST_F(P2PDownloadActionTest, CanAppend) { 559 if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) { 560 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 561 << "Please update your system to support this feature."; 562 return; 563 } 564 565 SetupDownload(1000); // starting_offset 566 567 // Prepare the file with existing data before starting to write to 568 // it via DownloadAction. 569 string file_id = utils::CalculateP2PFileId("1234hash", data_.length()); 570 ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length())); 571 string existing_data; 572 for (unsigned int i = 0; i < 1000; i++) 573 existing_data += '0' + (i % 10); 574 ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(), 575 1000), 1000); 576 577 StartDownload(true); // use_p2p_to_share 578 579 // DownloadAction should convey the same file_id and the file should 580 // have the expected size. 581 EXPECT_EQ(download_action_->p2p_file_id(), file_id); 582 EXPECT_EQ(static_cast<ssize_t>(data_.length()), 583 p2p_manager_->FileGetSize(file_id)); 584 EXPECT_EQ(static_cast<ssize_t>(data_.length()), 585 p2p_manager_->FileGetExpectedSize(file_id)); 586 string p2p_file_contents; 587 // Check that the first 1000 bytes wasn't touched and that we 588 // appended the remaining as appropriate. 589 EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id), 590 &p2p_file_contents)); 591 EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000)); 592 EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000)); 593 } 594 595 TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) { 596 if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) { 597 LOG(WARNING) << "Skipping test because /tmp does not support xattr. " 598 << "Please update your system to support this feature."; 599 return; 600 } 601 602 SetupDownload(1000); // starting_offset 603 604 // Prepare the file with all existing data before starting to write 605 // to it via DownloadAction. 606 string file_id = utils::CalculateP2PFileId("1234hash", data_.length()); 607 ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length())); 608 string existing_data; 609 for (unsigned int i = 0; i < 1000; i++) 610 existing_data += '0' + (i % 10); 611 ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(), 612 1000), 1000); 613 614 // Check that the file is there. 615 EXPECT_EQ(1000, p2p_manager_->FileGetSize(file_id)); 616 EXPECT_EQ(1, p2p_manager_->CountSharedFiles()); 617 618 StartDownload(false); // use_p2p_to_share 619 620 // DownloadAction should have deleted the p2p file. Check that it's gone. 621 EXPECT_EQ(-1, p2p_manager_->FileGetSize(file_id)); 622 EXPECT_EQ(0, p2p_manager_->CountSharedFiles()); 623 } 624 625 } // namespace chromeos_update_engine 626