以下分析代码基于mtk 8167a的sdk,android版本是8.0.1。代码的分析直接看以Timothy开头的注释。
main()函数
main()函数的位置是recovery.cpp。
int main(int argc, char **argv) {
// Timothy:初始化日志。因为recovery里面没有logcat,所以会将关键信息打印在屏幕上,日志写到文件里。
// We don't have logcat yet under recovery; so we'll print error on screen and
// log to stdout (which is redirected to recovery.log) as we used to do.
android::base::InitLogging(argv, &UiLogger);
// Take last pmsg contents and rewrite it to the current pmsg session.
static const char filter[] = "recovery/";
// Do we need to rotate?
bool doRotate = false;
__android_log_pmsg_file_read(
LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
logbasename, &doRotate);
// Take action to refresh pmsg contents
__android_log_pmsg_file_read(
LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
logrotate, &doRotate);
// Timothy:这部分是adb reboot side_load命令进入recovery后走的分支,目前不关注。
// If this binary is started with the single argument "--adbd",
// instead of being the normal recovery binary, it turns into kind
// of a stripped-down version of adbd that only supports the
// 'sideload' command. Note this must be a real argument, not
// anything in the command file or bootloader control block; the
// only way recovery should be run with this argument is when it
// starts a copy of itself from the apply_from_adb() function.
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
minadbd_main();
return 0;
}
time_t start = time(NULL);
//Timothy:重定向标准输出,TEMPORARY_LOG_FILE这个宏的定义是/tmp/recovery.log。
// redirect_stdio should be called only in non-sideload mode. Otherwise
// we may have two logger instances with different timestamps.
redirect_stdio(TEMPORARY_LOG_FILE);
printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
// Timothy:这一部分是挂载必要的分区。
load_volume_table();
has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
has_nvdata = volume_for_mount_point(NVDATA_ROOT) != nullptr;
mt_init_partition_type();
// Timothy:这一部分是解析启动recovery的参数,例如是否是ota升级、是否清除cache分区、
// 是否清除data分区等等。
// 另外还有个关键信息是本地化信息locale,这决定了界面上显示的语言。
std::vector<std::string> args = get_args(argc, argv);
std::vector<char*> args_to_parse(args.size());
std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
[](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
const char *update_package = NULL;
bool should_wipe_data = false;
bool should_prompt_and_wipe_data = false;
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;
bool shutdown_after = false;
int retry_count = 0;
bool security_update = false;
int arg;
int option_index;
while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
&option_index)) != -1) {
switch (arg) {
case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
case 'u': update_package = optarg; break;
case 'w': should_wipe_data = true; break;
case 'c': should_wipe_cache = true; break;
case 't': show_text = true; break;
case 's': sideload = true; break;
case 'a': sideload = true; sideload_auto_reboot = true; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
case 'e': security_update = true; break;
case 0: {
std::string option = OPTIONS[option_index].name;
if (option == "wipe_ab") {
should_wipe_ab = true;
} else if (option == "wipe_package_size") {
android::base::ParseUint(optarg, &wipe_package_size);
} else if (option == "prompt_and_wipe_data") {
should_prompt_and_wipe_data = true;
}
break;
}
case '?':
LOG(ERROR) << "Invalid command argument";
continue;
}
}
if (locale.empty()) {
if (has_cache) {
locale = load_locale_from_cache();
}
if (locale.empty()) {
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);
// Timothy:这部分是初始化显示的界面,暂时不关注。
Device* device = make_device();
if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
printf("Quiescent recovery mode.\n");
ui = new StubRecoveryUI();
} else {
ui = device->GetUI();
if (!ui->Init(locale)) {
printf("Failed to initialize UI, use stub UI instead.\n");
ui = new StubRecoveryUI();
}
}
// Set background string to "installing security update" for security update,
// otherwise set it to "installing system update".
ui->SetSystemUpdateText(security_update);
int st_cur, st_max;
if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
ui->SetStage(st_cur, st_max);
}
ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);
// Timothy:这部分是跟selinux相关的,暂时不关注。
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
// Timothy:根据注释,这个函数时在recovery启动时调用。调用的时机是UI完成初始化,参数已经解析完毕,
// 但是实际的升级尚未开始。
device->StartRecovery();
printf("Command:");
for (const auto& arg : args) {
printf(" \"%s\"", arg.c_str());
}
printf("\n\n");
// Timothy:这里把系统里所有的属性都打印出来了。既然这里能读属性,所以猜测在recovery里面也能写属性。
// 那么recovery和正常启动的Android应该能够通过property来传递一些值。
property_list(print_property, NULL);
printf("\n");
ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);
fprintf(stdout, "update_package = %s\n", update_package ? update_package : "NULL");
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
// It's not entirely true that we will modify the flash. But we want
// to log the update attempt since update_package is non-NULL.
modified_flash = true;
// Timothy:这里检查电量,如果低于BATTERY_OK_PERCENTAGE就不升级,直接跳过。
// BATTERY_OK_PERCENTAGE这个宏的定义是20
if (!is_battery_ok()) {
ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
BATTERY_OK_PERCENTAGE);
// Log the error code to last_install when installation skips due to
// low battery.
log_failure_code(kLowBattery, update_package);
status = INSTALL_SKIPPED;
} else if (bootreason_in_blacklist()) {
// Skip update-on-reboot when bootreason is kernel_panic or similar
ui->Print("bootreason is in the blacklist; skip OTA installation\n");
log_failure_code(kBootreasonInBlacklist, update_package);
status = INSTALL_SKIPPED;
} else {
// Timothy:这里执行实际的安装升级包
status = install_package(update_package, &should_wipe_cache,
TEMPORARY_INSTALL_FILE, true, retry_count);
// Timothy:如果升级成功,而且传进来的command包含清除cache分区,就清除cache分区。
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
// Timothy:如果升级失败,就在屏幕上打印升级失败,如果重试次数没到上线,将重试。
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
// When I/O error happens, reboot and retry installation RETRY_LIMIT
// times before we abandon this OTA update.
if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
copy_logs();
set_retry_bootloader_message(retry_count, argc, argv);
// Print retry count on screen.
ui->Print("Retry attempt %d\n", retry_count);
// Reboot and retry the update
if (!reboot("reboot,recovery")) {
ui->Print("Reboot failed\n");
} else {
while (true) {
pause();
}
}
}
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error
// message is visible.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
}
// Timothy:这后面就是除了升级以外的其他命令了。例如恢复出厂设置之类的。
} else if (should_wipe_data) {
if (!wipe_data(device)) {
status = INSTALL_ERROR;
}
} else if (should_prompt_and_wipe_data) {
ui->ShowText(true);
ui->SetBackground(RecoveryUI::ERROR);
if (!prompt_and_wipe_data(device)) {
status = INSTALL_ERROR;
}
ui->ShowText(false);
} else if (should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
} else if (should_wipe_ab) {
if (!wipe_ab_device(wipe_package_size)) {
status = INSTALL_ERROR;
}
} else if (sideload) {
// 'adb reboot sideload' acts the same as user presses key combinations
// to enter the sideload mode. When 'sideload-auto-reboot' is used, text
// display will NOT be turned on by default. And it will reboot after
// sideload finishes even if there are errors. Unless one turns on the
// text display during the installation. This is to enable automated
// testing.
if (!sideload_auto_reboot) {
ui->ShowText(true);
}
status = apply_from_adb(&should_wipe_cache, TEMPORARY_INSTALL_FILE);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
}
ui->Print("\nInstall from ADB complete (status: %d).\n", status);
if (sideload_auto_reboot) {
ui->Print("Rebooting automatically.\n");
}
} else if (!just_exit) {
// If this is an eng or userdebug build, automatically turn on the text display if no command
// is specified. Note that this should be called before setting the background to avoid
// flickering the background image.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
status = INSTALL_NONE; // No command specified
ui->ShowText(true);
ui->SetBackground(RecoveryUI::NO_COMMAND);
}
// Timothy:在这个函数里面,如果升级成功,将删除cache分区下的ota包。然后里面有个mtk加的操作,
// 调用一个函数将结果写入data分区下的一个文件。但是这个函数实际上并没有写,很费解。
mt_main_write_result(status, update_package);
if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
ui->SetBackground(RecoveryUI::ERROR);
ui->ShowText(true);
if (!ui->IsTextVisible()) {
sleep(5);
}
}
// Timothy:这里的注释解释了一个我们曾经遇到的问题。在ota升级的过程中,如果按了音量键,将会显示
// 一个打印了升级过程的文字界面,而不是默认的进度条界面。在这个界面出现之后,升级成功将不会自动
// 重启,而是等待用户的操作。也就是注释中的第一种情况。
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
// 1. If the recovery menu is visible, prompt and wait for commands.
// 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
// recovery to sideload a package.)
// 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
// without waiting.
// 4. In all other cases, reboot the device. Therefore, normal users will observe the device
// reboot after it shows the "error" screen for 5s.
if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
Device::BuiltinAction temp = prompt_and_wait(device, status);
if (temp != Device::NO_ACTION) {
after = temp;
}
}
// Timothy:这个函数的作用是清除cache分区下的command文件,准备重启,
// 并将日志文件拷贝到cache/recovery下面。
// Save logs and clean up before rebooting or shutting down.
finish_recovery();
// Timothy:我查了一下,ANDROID_RB_PROPERTY这个宏的定义是sys.powerctl,
// 用这个属性可以进行关机、重启等操作,在正常的adb shell里面也可以用。
// property_set(ANDROID_RB_PROPERTY, "shutdown,");
// property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
// property_set(ANDROID_RB_PROPERTY, "reboot,");
// property_set(ANDROID_RB_PROPERTY, "reboot,edl");
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;
}
install_package()函数
install_package()函数的位置是install.cpp。
int install_package(const std::string &path, bool *wipe_cache, const std::string &install_file,
bool needs_mount, int retry_count) {
// Timothy:检查参数是否为空
CHECK(!path.empty());
CHECK(!install_file.empty());
CHECK(wipe_cache != nullptr);
modified_flash = true;
// Timothy:记录升级的开始时间,后面要计算升级所用时长。
auto start = std::chrono::system_clock::now();
// Timothy:记录开始升级前的温度。
int start_temperature = GetMaxValueFromThermalZone();
int max_temperature = start_temperature;
int result;
std::vector<std::string> log_buffer;
if (setup_install_mounts() != 0) { // Timothy:检查tmp分区和cache分区是否挂载
LOG(ERROR) << "failed to set up expected mounts for install; aborting";
result = INSTALL_ERROR;
} else {
// Timothy:这里进行实际的安装
result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count,
&max_temperature);
}
// Measure the time spent to apply OTA update in seconds.
std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
int time_total = static_cast<int>(duration.count());
// Timothy:这部分跟加密分区有关,暂时不关注。
bool has_cache = volume_for_mount_point("/cache") != nullptr;
// Skip logging the uncrypt_status on devices without /cache.
if (has_cache) {
static constexpr const char *UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS;
} else {
std::string uncrypt_status;
if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
PLOG(WARNING) << "failed to read uncrypt status";
} else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status;
} else {
log_buffer.push_back(android::base::Trim(uncrypt_status));
}
}
}
// Timothy:这部分打印了一些日志。
// The first two lines need to be the package name and install result.
std::vector<std::string> log_header = {
path,
result == INSTALL_SUCCESS ? "1" : "0",
"time_total: " + std::to_string(time_total),
"retry: " + std::to_string(retry_count),
};
int end_temperature = GetMaxValueFromThermalZone();
max_temperature = std::max(end_temperature, max_temperature);
if (start_temperature > 0) {
log_buffer.push_back("temperature_start: " + std::to_string(start_temperature));
}
if (end_temperature > 0) {
log_buffer.push_back("temperature_end: " + std::to_string(end_temperature));
}
if (max_temperature > 0) {
log_buffer.push_back("temperature_max: " + std::to_string(max_temperature));
}
std::string log_content =
android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n";
if (!android::base::WriteStringToFile(log_content, install_file)) {
PLOG(ERROR) << "failed to write " << install_file;
}
// Write a copy into last_log.
LOG(INFO) << log_content;
return result;
}