From 152e0eb20b16c2854ca9690b1cc3b64fbb8962fe Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Sun, 13 May 2018 00:34:45 -0700 Subject: wear_ui: Address a TODO regarding {intro,loop}_frames. Also add tests for LoadAnimation that cover the change. Test: Run `recovery_unit_test` on marlin. Change-Id: I0380a5cdd0d85d55baecf7759eb647b6a9f3a085 --- tests/unit/screen_ui_test.cpp | 56 ++++++++++++++++++++++++++++++++++++++++--- wear_ui.cpp | 5 ---- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp index 03e23ca42..269222faa 100644 --- a/tests/unit/screen_ui_test.cpp +++ b/tests/unit/screen_ui_test.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include @@ -23,6 +24,8 @@ #include #include +#include +#include #include #include "common/test_constants.h" @@ -224,6 +227,19 @@ class TestableScreenRecoveryUI : public ScreenRecoveryUI { int KeyHandler(int key, bool visible) const; + // The following functions expose the protected members for test purpose. + void RunLoadAnimation() { + LoadAnimation(); + } + + size_t GetLoopFrames() const { + return loop_frames; + } + + size_t GetIntroFrames() const { + return intro_frames; + } + bool GetRtlLocale() const { return rtl_locale_; } @@ -260,14 +276,15 @@ class ScreenRecoveryUITest : public ::testing::Test { void SetUp() override { ui_ = std::make_unique(); - std::string testdata_dir = from_testdata_base(""); - Paths::Get().set_resource_dir(testdata_dir); - res_set_resource_dir(testdata_dir); + testdata_dir_ = from_testdata_base(""); + Paths::Get().set_resource_dir(testdata_dir_); + res_set_resource_dir(testdata_dir_); ASSERT_TRUE(ui_->Init(kTestLocale)); } std::unique_ptr ui_; + std::string testdata_dir_; }; TEST_F(ScreenRecoveryUITest, Init) { @@ -352,3 +369,36 @@ TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) { std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(), std::placeholders::_1, std::placeholders::_2))); } + +TEST_F(ScreenRecoveryUITest, LoadAnimation) { + // Make a few copies of loop00000.png from testdata. + std::string image_data; + ASSERT_TRUE(android::base::ReadFileToString(testdata_dir_ + "/loop00000.png", &image_data)); + + std::vector tempfiles; + TemporaryDir resource_dir; + for (const auto& name : { "00002", "00100", "00050" }) { + tempfiles.push_back(android::base::StringPrintf("%s/loop%s.png", resource_dir.path, name)); + ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back())); + } + for (const auto& name : { "00", "01" }) { + tempfiles.push_back(android::base::StringPrintf("%s/intro%s.png", resource_dir.path, name)); + ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back())); + } + Paths::Get().set_resource_dir(resource_dir.path); + + ui_->RunLoadAnimation(); + + ASSERT_EQ(2u, ui_->GetIntroFrames()); + ASSERT_EQ(3u, ui_->GetLoopFrames()); + + for (const auto& name : tempfiles) { + ASSERT_EQ(0, unlink(name.c_str())); + } +} + +TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) { + TemporaryDir resource_dir; + Paths::Get().set_resource_dir(resource_dir.path); + ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), ""); +} diff --git a/wear_ui.cpp b/wear_ui.cpp index f157d3ca3..f4a839923 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -32,11 +32,6 @@ WearRecoveryUI::WearRecoveryUI() kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) { // TODO: kMenuUnusableRows should be computed based on the lines in draw_screen_locked(). - // TODO: The following three variables are likely not needed. The first two are detected - // automatically in ScreenRecoveryUI::LoadAnimation(), based on the actual files seen on device. - intro_frames = 22; - loop_frames = 60; - touch_screen_allowed_ = true; } -- cgit v1.2.3 From 6a6480496517dc9343b80380637b40094b72d3a4 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Fri, 4 May 2018 11:24:10 -0700 Subject: recovery: Refactor common setup into main() Move more common setup into the main function. Main() handles all 1 time setup, such as ui, logging, and secontext. Recovery_main() takes in command line arguments, does any necessary recovery work, and can be called multiple times from main(). Test: Recovery works Bug: 78793464 Change-Id: I2d2595fc342b4ddfa80f4e06b30e44263132acd9 --- common.h | 1 + private/recovery.h | 19 ----- recovery.cpp | 172 ++-------------------------------------- recovery.h | 24 ++++++ recovery_main.cpp | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 253 insertions(+), 189 deletions(-) delete mode 100644 private/recovery.h create mode 100644 recovery.h diff --git a/common.h b/common.h index 3dc36a960..c24431bd1 100644 --- a/common.h +++ b/common.h @@ -32,6 +32,7 @@ struct selabel_handle; extern struct selabel_handle* sehandle; extern RecoveryUI* ui; extern bool modified_flash; +extern bool has_cache; // The current stage, e.g. "1/2". extern std::string stage; diff --git a/private/recovery.h b/private/recovery.h deleted file mode 100644 index 5b2ca4b3f..000000000 --- a/private/recovery.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -int start_recovery(int argc, char** argv); diff --git a/recovery.cpp b/recovery.cpp index f03cec3fe..2360057d0 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "private/recovery.h" +#include "recovery.h" #include #include @@ -32,7 +32,6 @@ #include #include #include -#include #include #include @@ -49,12 +48,8 @@ #include #include #include -#include #include /* for property_list */ #include -#include -#include -#include #include #include "adb_install.h" @@ -70,7 +65,6 @@ #include "otautil/sysutil.h" #include "roots.h" #include "screen_ui.h" -#include "stub_ui.h" #include "ui.h" static constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; @@ -88,13 +82,9 @@ static constexpr const char* SDCARD_ROOT = "/sdcard"; // into target_files.zip. Assert the version defined in code and in Android.mk are consistent. static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions."); -static bool has_cache = false; - -RecoveryUI* ui = nullptr; bool modified_flash = false; std::string stage; const char* reason = nullptr; -struct selabel_handle* sehandle; /* * The recovery tool communicates with the main system through /cache files. @@ -146,77 +136,6 @@ bool is_ro_debuggable() { return android::base::GetBoolProperty("ro.debuggable", false); } -// command line args come from, in decreasing precedence: -// - the actual command line -// - the bootloader control block (one per line, after "recovery") -// - the contents of COMMAND_FILE (one per line) -static std::vector get_args(const int argc, char** const argv) { - CHECK_GT(argc, 0); - - bootloader_message boot = {}; - std::string err; - if (!read_bootloader_message(&boot, &err)) { - LOG(ERROR) << err; - // If fails, leave a zeroed bootloader_message. - boot = {}; - } - stage = std::string(boot.stage); - - if (boot.command[0] != 0) { - std::string boot_command = std::string(boot.command, sizeof(boot.command)); - LOG(INFO) << "Boot command: " << boot_command; - } - - if (boot.status[0] != 0) { - std::string boot_status = std::string(boot.status, sizeof(boot.status)); - LOG(INFO) << "Boot status: " << boot_status; - } - - std::vector args(argv, argv + argc); - - // --- if arguments weren't supplied, look in the bootloader control block - if (args.size() == 1) { - boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination - std::string boot_recovery(boot.recovery); - std::vector tokens = android::base::Split(boot_recovery, "\n"); - if (!tokens.empty() && tokens[0] == "recovery") { - for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { - // Skip empty and '\0'-filled tokens. - if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); - } - LOG(INFO) << "Got " << args.size() << " arguments from boot message"; - } else if (boot.recovery[0] != 0) { - LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; - } - } - - // --- if that doesn't work, try the command file (if we have /cache). - if (args.size() == 1 && has_cache) { - std::string content; - if (ensure_path_mounted(COMMAND_FILE) == 0 && - android::base::ReadFileToString(COMMAND_FILE, &content)) { - std::vector tokens = android::base::Split(content, "\n"); - // All the arguments in COMMAND_FILE are needed (unlike the BCB message, - // COMMAND_FILE doesn't use filename as the first argument). - for (auto it = tokens.begin(); it != tokens.end(); it++) { - // Skip empty and '\0'-filled tokens. - if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); - } - LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; - } - } - - // Write the arguments (excluding the filename in args[0]) back into the - // bootloader control block. So the device will always boot into recovery to - // finish the pending work, until finish_recovery() is called. - std::vector options(args.cbegin() + 1, args.cend()); - if (!update_bootloader_message(options, &err)) { - LOG(ERROR) << "Failed to set BCB message: " << err; - } - - return args; -} - // Set the BCB to reboot back into recovery (it won't resume the install from // sdcard though). static void set_sdcard_update_bootloader_message() { @@ -921,21 +840,6 @@ static void print_property(const char* key, const char* name, void* /* cookie */ printf("%s=%s\n", key, name); } -static std::string load_locale_from_cache() { - if (ensure_path_mounted(LOCALE_FILE) != 0) { - LOG(ERROR) << "Can't mount " << LOCALE_FILE; - return ""; - } - - std::string content; - if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { - PLOG(ERROR) << "Can't read " << LOCALE_FILE; - return ""; - } - - return android::base::Trim(content); -} - void ui_print(const char* format, ...) { std::string buffer; va_list ap; @@ -1079,15 +983,7 @@ static void log_failure_code(ErrorCode code, const std::string& update_package) LOG(INFO) << log_content; } -int start_recovery(int argc, char** argv) { - time_t start = time(nullptr); - - printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); - - load_volume_table(); - has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; - - std::vector args = get_args(argc, argv); +Device::BuiltinAction start_recovery(Device* device, const std::vector& args) { std::vector args_to_parse(args.size()); std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), [](const std::string& arg) { return const_cast(arg.c_str()); }); @@ -1117,7 +1013,6 @@ int start_recovery(int argc, char** argv) { bool should_wipe_cache = false; bool should_wipe_ab = false; size_t wipe_package_size = 0; - bool show_text = false; bool sideload = false; bool sideload_auto_reboot = false; bool just_exit = false; @@ -1132,7 +1027,7 @@ int start_recovery(int argc, char** argv) { &option_index)) != -1) { switch (arg) { case 't': - show_text = true; + // Handled in recovery_main.cpp break; case 'x': just_exit = true; @@ -1140,7 +1035,7 @@ int start_recovery(int argc, char** argv) { case 0: { std::string option = OPTIONS[option_index].name; if (option == "locale") { - locale = optarg; + // Handled in recovery_main.cpp } else if (option == "prompt_and_wipe_data") { should_prompt_and_wipe_data = true; } else if (option == "reason") { @@ -1175,37 +1070,9 @@ int start_recovery(int argc, char** argv) { } } - if (locale.empty()) { - if (has_cache) { - locale = load_locale_from_cache(); - } - - if (locale.empty()) { - static constexpr const char* DEFAULT_LOCALE = "en-US"; - locale = DEFAULT_LOCALE; - } - } - - printf("locale is [%s]\n", locale.c_str()); printf("stage is [%s]\n", stage.c_str()); printf("reason is [%s]\n", reason); - Device* device = make_device(); - if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { - printf("Quiescent recovery mode.\n"); - device->ResetUI(new StubRecoveryUI()); - } else { - if (!device->GetUI()->Init(locale)) { - printf("Failed to initialize UI; using stub UI instead.\n"); - device->ResetUI(new StubRecoveryUI()); - } - } - ui = device->GetUI(); - - if (!has_cache) { - device->RemoveMenuItemForAction(Device::WIPE_CACHE); - } - // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); @@ -1215,15 +1082,6 @@ int start_recovery(int argc, char** argv) { ui->SetStage(st_cur, st_max); } - ui->SetBackground(RecoveryUI::NONE); - if (show_text) ui->ShowText(true); - - sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - if (!sehandle) { - ui->Print("Warning: No file_contexts\n"); - } - device->StartRecovery(); printf("Command:"); @@ -1373,25 +1231,5 @@ int start_recovery(int argc, char** argv) { // Save logs and clean up before rebooting or shutting down. finish_recovery(); - switch (after) { - case Device::SHUTDOWN: - ui->Print("Shutting down...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); - break; - - case Device::REBOOT_BOOTLOADER: - ui->Print("Rebooting to bootloader...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); - break; - - default: - ui->Print("Rebooting...\n"); - reboot("reboot,"); - break; - } - while (true) { - pause(); - } - // Should be unreachable. - return EXIT_SUCCESS; + return after; } diff --git a/recovery.h b/recovery.h new file mode 100644 index 000000000..00e22daa6 --- /dev/null +++ b/recovery.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "device.h" + +Device::BuiltinAction start_recovery(Device* device, const std::vector& args); diff --git a/recovery_main.cpp b/recovery_main.cpp index 3147511ee..e21c782d0 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -14,22 +14,57 @@ * limitations under the License. */ +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include /* private pmsg functions */ +#include +#include +#include #include "common.h" +#include "device.h" #include "logging.h" #include "minadbd/minadbd.h" #include "otautil/paths.h" -#include "private/recovery.h" +#include "otautil/sysutil.h" +#include "recovery.h" +#include "roots.h" +#include "stub_ui.h" #include "ui.h" +static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; +static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; + +static constexpr const char* CACHE_ROOT = "/cache"; + +bool has_cache = false; + +RecoveryUI* ui = nullptr; +struct selabel_handle* sehandle; + static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, const char* /* tag */, const char* /* file */, unsigned int /* line */, const char* message) { @@ -41,6 +76,92 @@ static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity s } } +// command line args come from, in decreasing precedence: +// - the actual command line +// - the bootloader control block (one per line, after "recovery") +// - the contents of COMMAND_FILE (one per line) +static std::vector get_args(const int argc, char** const argv) { + CHECK_GT(argc, 0); + + bootloader_message boot = {}; + std::string err; + if (!read_bootloader_message(&boot, &err)) { + LOG(ERROR) << err; + // If fails, leave a zeroed bootloader_message. + boot = {}; + } + stage = std::string(boot.stage); + + if (boot.command[0] != 0) { + std::string boot_command = std::string(boot.command, sizeof(boot.command)); + LOG(INFO) << "Boot command: " << boot_command; + } + + if (boot.status[0] != 0) { + std::string boot_status = std::string(boot.status, sizeof(boot.status)); + LOG(INFO) << "Boot status: " << boot_status; + } + + std::vector args(argv, argv + argc); + + // --- if arguments weren't supplied, look in the bootloader control block + if (args.size() == 1) { + boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination + std::string boot_recovery(boot.recovery); + std::vector tokens = android::base::Split(boot_recovery, "\n"); + if (!tokens.empty() && tokens[0] == "recovery") { + for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from boot message"; + } else if (boot.recovery[0] != 0) { + LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; + } + } + + // --- if that doesn't work, try the command file (if we have /cache). + if (args.size() == 1 && has_cache) { + std::string content; + if (ensure_path_mounted(COMMAND_FILE) == 0 && + android::base::ReadFileToString(COMMAND_FILE, &content)) { + std::vector tokens = android::base::Split(content, "\n"); + // All the arguments in COMMAND_FILE are needed (unlike the BCB message, + // COMMAND_FILE doesn't use filename as the first argument). + for (auto it = tokens.begin(); it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; + } + } + + // Write the arguments (excluding the filename in args[0]) back into the + // bootloader control block. So the device will always boot into recovery to + // finish the pending work, until finish_recovery() is called. + std::vector options(args.cbegin() + 1, args.cend()); + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } + + return args; +} + +static std::string load_locale_from_cache() { + if (ensure_path_mounted(LOCALE_FILE) != 0) { + LOG(ERROR) << "Can't mount " << LOCALE_FILE; + return ""; + } + + std::string content; + if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { + PLOG(ERROR) << "Can't read " << LOCALE_FILE; + return ""; + } + + return android::base::Trim(content); +} + static void redirect_stdio(const char* filename) { int pipefd[2]; if (pipe(pipefd) == -1) { @@ -154,9 +275,108 @@ int main(int argc, char** argv) { return 0; } + time_t start = time(nullptr); + // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger // instances with different timestamps. redirect_stdio(Paths::Get().temporary_log_file().c_str()); - return start_recovery(argc, argv); + printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); + + load_volume_table(); + has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; + + std::vector args = get_args(argc, argv); + std::vector args_to_parse(args.size()); + std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), + [](const std::string& arg) { return const_cast(arg.c_str()); }); + + static constexpr struct option OPTIONS[] = { + { "locale", required_argument, nullptr, 0 }, + { "show_text", no_argument, nullptr, 't' }, + { nullptr, 0, nullptr, 0 }, + }; + + bool show_text = false; + std::string locale; + + int arg; + int option_index; + while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS, + &option_index)) != -1) { + switch (arg) { + case 't': + show_text = true; + break; + case 0: { + std::string option = OPTIONS[option_index].name; + if (option == "locale") { + locale = optarg; + } + break; + } + } + } + + if (locale.empty()) { + if (has_cache) { + locale = load_locale_from_cache(); + } + + if (locale.empty()) { + static constexpr const char* DEFAULT_LOCALE = "en-US"; + locale = DEFAULT_LOCALE; + } + } + + printf("locale is [%s]\n", locale.c_str()); + + Device* device = make_device(); + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + printf("Quiescent recovery mode.\n"); + device->ResetUI(new StubRecoveryUI()); + } else { + if (!device->GetUI()->Init(locale)) { + printf("Failed to initialize UI; using stub UI instead.\n"); + device->ResetUI(new StubRecoveryUI()); + } + } + ui = device->GetUI(); + + if (!has_cache) { + device->RemoveMenuItemForAction(Device::WIPE_CACHE); + } + + ui->SetBackground(RecoveryUI::NONE); + if (show_text) ui->ShowText(true); + + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + if (!sehandle) { + ui->Print("Warning: No file_contexts\n"); + } + + Device::BuiltinAction after = start_recovery(device, args); + + switch (after) { + case Device::SHUTDOWN: + ui->Print("Shutting down...\n"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); + break; + + case Device::REBOOT_BOOTLOADER: + ui->Print("Rebooting to bootloader...\n"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); + break; + + default: + ui->Print("Rebooting...\n"); + reboot("reboot,"); + break; + } + while (true) { + pause(); + } + // Should be unreachable. + return EXIT_SUCCESS; } -- cgit v1.2.3 From c2b2bb5d788b68c641c66f4cf40e653d41f77754 Mon Sep 17 00:00:00 2001 From: Tianjie Xu Date: Tue, 15 May 2018 15:09:59 -0700 Subject: Last command file should be updated after a command finishes The last command file has the last executed stash command by convention. So we should update the file after we actually finishes executing the command; instead of after loading src/tgt. Bug: 79756267 Test: unit tests pass Change-Id: I4535b5836e7eb13b3abe3d02f9f362ec5e9ec969 --- updater/blockimg.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 156a82939..236644e7f 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -132,8 +132,7 @@ static bool FsyncDir(const std::string& dirname) { return true; } -// Update the last command index in the last_command_file if the current command writes to the -// stash either explicitly or implicitly. +// Update the last executed command index in the last_command_file. static bool UpdateLastCommandIndex(int command_index, const std::string& command_string) { const std::string& last_command_file = Paths::Get().last_command_file(); std::string last_command_tmp = last_command_file + ".tmp"; @@ -1161,10 +1160,6 @@ static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* return -1; } - if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) { - LOG(WARNING) << "Failed to update the last command file."; - } - params.stashed += *src_blocks; // Can be deleted when the write has completed. if (!stash_exists) { @@ -1275,10 +1270,6 @@ static int PerformCommandStash(CommandParameters& params) { LOG(INFO) << "stashing " << blocks << " blocks to " << id; int result = WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr); if (result == 0) { - if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) { - LOG(WARNING) << "Failed to update the last command file."; - } - params.stashed += blocks; } return result; @@ -1701,7 +1692,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, params.createdstash = res; // When performing an update, save the index and cmdline of the current command into - // the last_command_file if this command writes to the stash either explicitly of implicitly. + // the last_command_file. // Upon resuming an update, read the saved index first; then // 1. In verification mode, check if the 'move' or 'diff' commands before the saved index has // the expected target blocks already. If not, these commands cannot be skipped and we need @@ -1797,6 +1788,11 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, PLOG(ERROR) << "fsync failed"; goto pbiudone; } + + if (!UpdateLastCommandIndex(params.cmdindex, params.cmdline)) { + LOG(WARNING) << "Failed to update the last command file."; + } + fprintf(cmd_pipe, "set_progress %.4f\n", static_cast(params.written) / total_blocks); fflush(cmd_pipe); } -- cgit v1.2.3 From f5e319ac96a565d8ea3ff984b0f9f34fc4b6f157 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Fri, 4 May 2018 11:24:10 -0700 Subject: recovery: Refactor common setup into main() Move more common setup into the main function. Main() handles all 1 time setup, such as ui, logging, and secontext. Recovery_main() takes in command line arguments, does any necessary recovery work, and can be called multiple times from main(). Test: Recovery works Bug: 78793464 Change-Id: I2d2595fc342b4ddfa80f4e06b30e44263132acd9 Merged-In: I2d2595fc342b4ddfa80f4e06b30e44263132acd9 --- common.h | 1 + private/recovery.h | 19 ----- recovery.cpp | 172 ++-------------------------------------- recovery.h | 24 ++++++ recovery_main.cpp | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 253 insertions(+), 189 deletions(-) delete mode 100644 private/recovery.h create mode 100644 recovery.h diff --git a/common.h b/common.h index 3dc36a960..c24431bd1 100644 --- a/common.h +++ b/common.h @@ -32,6 +32,7 @@ struct selabel_handle; extern struct selabel_handle* sehandle; extern RecoveryUI* ui; extern bool modified_flash; +extern bool has_cache; // The current stage, e.g. "1/2". extern std::string stage; diff --git a/private/recovery.h b/private/recovery.h deleted file mode 100644 index 5b2ca4b3f..000000000 --- a/private/recovery.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -int start_recovery(int argc, char** argv); diff --git a/recovery.cpp b/recovery.cpp index e427998a8..b1a2900fd 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "private/recovery.h" +#include "recovery.h" #include #include @@ -32,7 +32,6 @@ #include #include #include -#include #include #include @@ -49,12 +48,8 @@ #include #include #include -#include #include /* for property_list */ #include -#include -#include -#include #include #include "adb_install.h" @@ -70,7 +65,6 @@ #include "otautil/sysutil.h" #include "roots.h" #include "screen_ui.h" -#include "stub_ui.h" #include "ui.h" static constexpr const char* CACHE_LOG_DIR = "/cache/recovery"; @@ -88,13 +82,9 @@ static constexpr const char* SDCARD_ROOT = "/sdcard"; // into target_files.zip. Assert the version defined in code and in Android.mk are consistent. static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions."); -static bool has_cache = false; - -RecoveryUI* ui = nullptr; bool modified_flash = false; std::string stage; const char* reason = nullptr; -struct selabel_handle* sehandle; /* * The recovery tool communicates with the main system through /cache files. @@ -146,77 +136,6 @@ bool is_ro_debuggable() { return android::base::GetBoolProperty("ro.debuggable", false); } -// command line args come from, in decreasing precedence: -// - the actual command line -// - the bootloader control block (one per line, after "recovery") -// - the contents of COMMAND_FILE (one per line) -static std::vector get_args(const int argc, char** const argv) { - CHECK_GT(argc, 0); - - bootloader_message boot = {}; - std::string err; - if (!read_bootloader_message(&boot, &err)) { - LOG(ERROR) << err; - // If fails, leave a zeroed bootloader_message. - boot = {}; - } - stage = std::string(boot.stage); - - if (boot.command[0] != 0) { - std::string boot_command = std::string(boot.command, sizeof(boot.command)); - LOG(INFO) << "Boot command: " << boot_command; - } - - if (boot.status[0] != 0) { - std::string boot_status = std::string(boot.status, sizeof(boot.status)); - LOG(INFO) << "Boot status: " << boot_status; - } - - std::vector args(argv, argv + argc); - - // --- if arguments weren't supplied, look in the bootloader control block - if (args.size() == 1) { - boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination - std::string boot_recovery(boot.recovery); - std::vector tokens = android::base::Split(boot_recovery, "\n"); - if (!tokens.empty() && tokens[0] == "recovery") { - for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { - // Skip empty and '\0'-filled tokens. - if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); - } - LOG(INFO) << "Got " << args.size() << " arguments from boot message"; - } else if (boot.recovery[0] != 0) { - LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; - } - } - - // --- if that doesn't work, try the command file (if we have /cache). - if (args.size() == 1 && has_cache) { - std::string content; - if (ensure_path_mounted(COMMAND_FILE) == 0 && - android::base::ReadFileToString(COMMAND_FILE, &content)) { - std::vector tokens = android::base::Split(content, "\n"); - // All the arguments in COMMAND_FILE are needed (unlike the BCB message, - // COMMAND_FILE doesn't use filename as the first argument). - for (auto it = tokens.begin(); it != tokens.end(); it++) { - // Skip empty and '\0'-filled tokens. - if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); - } - LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; - } - } - - // Write the arguments (excluding the filename in args[0]) back into the - // bootloader control block. So the device will always boot into recovery to - // finish the pending work, until finish_recovery() is called. - std::vector options(args.cbegin() + 1, args.cend()); - if (!update_bootloader_message(options, &err)) { - LOG(ERROR) << "Failed to set BCB message: " << err; - } - - return args; -} - // Set the BCB to reboot back into recovery (it won't resume the install from // sdcard though). static void set_sdcard_update_bootloader_message() { @@ -921,21 +840,6 @@ static void print_property(const char* key, const char* name, void* /* cookie */ printf("%s=%s\n", key, name); } -static std::string load_locale_from_cache() { - if (ensure_path_mounted(LOCALE_FILE) != 0) { - LOG(ERROR) << "Can't mount " << LOCALE_FILE; - return ""; - } - - std::string content; - if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { - PLOG(ERROR) << "Can't read " << LOCALE_FILE; - return ""; - } - - return android::base::Trim(content); -} - void ui_print(const char* format, ...) { std::string buffer; va_list ap; @@ -1059,15 +963,7 @@ static void log_failure_code(ErrorCode code, const std::string& update_package) LOG(INFO) << log_content; } -int start_recovery(int argc, char** argv) { - time_t start = time(nullptr); - - printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); - - load_volume_table(); - has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; - - std::vector args = get_args(argc, argv); +Device::BuiltinAction start_recovery(Device* device, const std::vector& args) { std::vector args_to_parse(args.size()); std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), [](const std::string& arg) { return const_cast(arg.c_str()); }); @@ -1097,7 +993,6 @@ int start_recovery(int argc, char** argv) { bool should_wipe_cache = false; bool should_wipe_ab = false; size_t wipe_package_size = 0; - bool show_text = false; bool sideload = false; bool sideload_auto_reboot = false; bool just_exit = false; @@ -1112,7 +1007,7 @@ int start_recovery(int argc, char** argv) { &option_index)) != -1) { switch (arg) { case 't': - show_text = true; + // Handled in recovery_main.cpp break; case 'x': just_exit = true; @@ -1120,7 +1015,7 @@ int start_recovery(int argc, char** argv) { case 0: { std::string option = OPTIONS[option_index].name; if (option == "locale") { - locale = optarg; + // Handled in recovery_main.cpp } else if (option == "prompt_and_wipe_data") { should_prompt_and_wipe_data = true; } else if (option == "reason") { @@ -1155,37 +1050,9 @@ int start_recovery(int argc, char** argv) { } } - if (locale.empty()) { - if (has_cache) { - locale = load_locale_from_cache(); - } - - if (locale.empty()) { - static constexpr const char* DEFAULT_LOCALE = "en-US"; - locale = DEFAULT_LOCALE; - } - } - - printf("locale is [%s]\n", locale.c_str()); printf("stage is [%s]\n", stage.c_str()); printf("reason is [%s]\n", reason); - Device* device = make_device(); - if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { - printf("Quiescent recovery mode.\n"); - device->ResetUI(new StubRecoveryUI()); - } else { - if (!device->GetUI()->Init(locale)) { - printf("Failed to initialize UI; using stub UI instead.\n"); - device->ResetUI(new StubRecoveryUI()); - } - } - ui = device->GetUI(); - - if (!has_cache) { - device->RemoveMenuItemForAction(Device::WIPE_CACHE); - } - // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); @@ -1195,15 +1062,6 @@ int start_recovery(int argc, char** argv) { ui->SetStage(st_cur, st_max); } - ui->SetBackground(RecoveryUI::NONE); - if (show_text) ui->ShowText(true); - - sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - if (!sehandle) { - ui->Print("Warning: No file_contexts\n"); - } - device->StartRecovery(); printf("Command:"); @@ -1353,25 +1211,5 @@ int start_recovery(int argc, char** argv) { // Save logs and clean up before rebooting or shutting down. finish_recovery(); - switch (after) { - case Device::SHUTDOWN: - ui->Print("Shutting down...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); - break; - - case Device::REBOOT_BOOTLOADER: - ui->Print("Rebooting to bootloader...\n"); - android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); - break; - - default: - ui->Print("Rebooting...\n"); - reboot("reboot,"); - break; - } - while (true) { - pause(); - } - // Should be unreachable. - return EXIT_SUCCESS; + return after; } diff --git a/recovery.h b/recovery.h new file mode 100644 index 000000000..00e22daa6 --- /dev/null +++ b/recovery.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "device.h" + +Device::BuiltinAction start_recovery(Device* device, const std::vector& args); diff --git a/recovery_main.cpp b/recovery_main.cpp index 3147511ee..e21c782d0 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -14,22 +14,57 @@ * limitations under the License. */ +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include /* private pmsg functions */ +#include +#include +#include #include "common.h" +#include "device.h" #include "logging.h" #include "minadbd/minadbd.h" #include "otautil/paths.h" -#include "private/recovery.h" +#include "otautil/sysutil.h" +#include "recovery.h" +#include "roots.h" +#include "stub_ui.h" #include "ui.h" +static constexpr const char* COMMAND_FILE = "/cache/recovery/command"; +static constexpr const char* LOCALE_FILE = "/cache/recovery/last_locale"; + +static constexpr const char* CACHE_ROOT = "/cache"; + +bool has_cache = false; + +RecoveryUI* ui = nullptr; +struct selabel_handle* sehandle; + static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity severity, const char* /* tag */, const char* /* file */, unsigned int /* line */, const char* message) { @@ -41,6 +76,92 @@ static void UiLogger(android::base::LogId /* id */, android::base::LogSeverity s } } +// command line args come from, in decreasing precedence: +// - the actual command line +// - the bootloader control block (one per line, after "recovery") +// - the contents of COMMAND_FILE (one per line) +static std::vector get_args(const int argc, char** const argv) { + CHECK_GT(argc, 0); + + bootloader_message boot = {}; + std::string err; + if (!read_bootloader_message(&boot, &err)) { + LOG(ERROR) << err; + // If fails, leave a zeroed bootloader_message. + boot = {}; + } + stage = std::string(boot.stage); + + if (boot.command[0] != 0) { + std::string boot_command = std::string(boot.command, sizeof(boot.command)); + LOG(INFO) << "Boot command: " << boot_command; + } + + if (boot.status[0] != 0) { + std::string boot_status = std::string(boot.status, sizeof(boot.status)); + LOG(INFO) << "Boot status: " << boot_status; + } + + std::vector args(argv, argv + argc); + + // --- if arguments weren't supplied, look in the bootloader control block + if (args.size() == 1) { + boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination + std::string boot_recovery(boot.recovery); + std::vector tokens = android::base::Split(boot_recovery, "\n"); + if (!tokens.empty() && tokens[0] == "recovery") { + for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from boot message"; + } else if (boot.recovery[0] != 0) { + LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\""; + } + } + + // --- if that doesn't work, try the command file (if we have /cache). + if (args.size() == 1 && has_cache) { + std::string content; + if (ensure_path_mounted(COMMAND_FILE) == 0 && + android::base::ReadFileToString(COMMAND_FILE, &content)) { + std::vector tokens = android::base::Split(content, "\n"); + // All the arguments in COMMAND_FILE are needed (unlike the BCB message, + // COMMAND_FILE doesn't use filename as the first argument). + for (auto it = tokens.begin(); it != tokens.end(); it++) { + // Skip empty and '\0'-filled tokens. + if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); + } + LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE; + } + } + + // Write the arguments (excluding the filename in args[0]) back into the + // bootloader control block. So the device will always boot into recovery to + // finish the pending work, until finish_recovery() is called. + std::vector options(args.cbegin() + 1, args.cend()); + if (!update_bootloader_message(options, &err)) { + LOG(ERROR) << "Failed to set BCB message: " << err; + } + + return args; +} + +static std::string load_locale_from_cache() { + if (ensure_path_mounted(LOCALE_FILE) != 0) { + LOG(ERROR) << "Can't mount " << LOCALE_FILE; + return ""; + } + + std::string content; + if (!android::base::ReadFileToString(LOCALE_FILE, &content)) { + PLOG(ERROR) << "Can't read " << LOCALE_FILE; + return ""; + } + + return android::base::Trim(content); +} + static void redirect_stdio(const char* filename) { int pipefd[2]; if (pipe(pipefd) == -1) { @@ -154,9 +275,108 @@ int main(int argc, char** argv) { return 0; } + time_t start = time(nullptr); + // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger // instances with different timestamps. redirect_stdio(Paths::Get().temporary_log_file().c_str()); - return start_recovery(argc, argv); + printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); + + load_volume_table(); + has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; + + std::vector args = get_args(argc, argv); + std::vector args_to_parse(args.size()); + std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), + [](const std::string& arg) { return const_cast(arg.c_str()); }); + + static constexpr struct option OPTIONS[] = { + { "locale", required_argument, nullptr, 0 }, + { "show_text", no_argument, nullptr, 't' }, + { nullptr, 0, nullptr, 0 }, + }; + + bool show_text = false; + std::string locale; + + int arg; + int option_index; + while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS, + &option_index)) != -1) { + switch (arg) { + case 't': + show_text = true; + break; + case 0: { + std::string option = OPTIONS[option_index].name; + if (option == "locale") { + locale = optarg; + } + break; + } + } + } + + if (locale.empty()) { + if (has_cache) { + locale = load_locale_from_cache(); + } + + if (locale.empty()) { + static constexpr const char* DEFAULT_LOCALE = "en-US"; + locale = DEFAULT_LOCALE; + } + } + + printf("locale is [%s]\n", locale.c_str()); + + Device* device = make_device(); + if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { + printf("Quiescent recovery mode.\n"); + device->ResetUI(new StubRecoveryUI()); + } else { + if (!device->GetUI()->Init(locale)) { + printf("Failed to initialize UI; using stub UI instead.\n"); + device->ResetUI(new StubRecoveryUI()); + } + } + ui = device->GetUI(); + + if (!has_cache) { + device->RemoveMenuItemForAction(Device::WIPE_CACHE); + } + + ui->SetBackground(RecoveryUI::NONE); + if (show_text) ui->ShowText(true); + + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + if (!sehandle) { + ui->Print("Warning: No file_contexts\n"); + } + + Device::BuiltinAction after = start_recovery(device, args); + + switch (after) { + case Device::SHUTDOWN: + ui->Print("Shutting down...\n"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); + break; + + case Device::REBOOT_BOOTLOADER: + ui->Print("Rebooting to bootloader...\n"); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); + break; + + default: + ui->Print("Rebooting...\n"); + reboot("reboot,"); + break; + } + while (true) { + pause(); + } + // Should be unreachable. + return EXIT_SUCCESS; } -- cgit v1.2.3 From 238beb73739071735a6bcbe462e27ab09a747f02 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Wed, 9 May 2018 16:25:40 -0700 Subject: updater_sample: add switch slot demo - Add util/UpdateEngineProperties.java - Set SWITCH_SLOT_ON_REBOOT=0 when update is applied - Allow user to switch slot to the updated partition manually - Add config "ab_config.force_switch_slot" - Add ab_force_switch_slot to tools/gen_update_config.py Test: manually on the marlin device Bug: 79492522 Change-Id: I52f818b576d52a052b5427ba3f732cb2371ddb06 Signed-off-by: Zhomart Mukhamejanov --- updater_sample/README.md | 6 +- updater_sample/res/layout/activity_main.xml | 17 +++++ updater_sample/res/raw/sample.json | 5 ++ updater_sample/res/values/strings.xml | 2 + .../android/systemupdatersample/UpdateConfig.java | 48 +++++++++++-- .../systemupdatersample/ui/MainActivity.java | 83 ++++++++++++++++++++-- .../util/UpdateEngineProperties.java | 37 ++++++++++ .../tests/res/raw/update_config_stream_001.json | 3 + .../tests/res/raw/update_config_stream_002.json | 3 + .../systemupdatersample/UpdateConfigTest.java | 8 ++- updater_sample/tools/gen_update_config.py | 14 +++- 11 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 updater_sample/src/com/example/android/systemupdatersample/util/UpdateEngineProperties.java diff --git a/updater_sample/README.md b/updater_sample/README.md index 95e57dbe9..c68c07caf 100644 --- a/updater_sample/README.md +++ b/updater_sample/README.md @@ -44,6 +44,10 @@ saved uncompressed (`ZIP_STORED`), so that their data can be downloaded directly with the offset and length. As `payload.bin` itself is already in compressed format, the size penalty is marginal. +if `ab_config.force_switch_slot` set true device will boot to the +updated partition on next reboot; otherwise button "Switch Slot" will +become active, and user can manually set updated partition as the active slot. + Config files can be generated using `tools/gen_update_config.py`. Running `./tools/gen_update_config.py --help` shows usage of the script. @@ -85,8 +89,8 @@ which HTTP headers are supported. - [x] Add stop/reset the update - [x] Add demo for passing HTTP headers to `UpdateEngine#applyPayload` - [x] [Package compatibility check](https://source.android.com/devices/architecture/vintf/match-rules) +- [x] Deferred switch slot demo - [ ] Add tests for `MainActivity` -- [ ] Change partition demo - [ ] Verify system partition checksum for package - [ ] Add non-A/B updates demo diff --git a/updater_sample/res/layout/activity_main.xml b/updater_sample/res/layout/activity_main.xml index 7a12d3474..d9e56b4b3 100644 --- a/updater_sample/res/layout/activity_main.xml +++ b/updater_sample/res/layout/activity_main.xml @@ -178,6 +178,23 @@ android:text="Reset" /> + + +