这里主要分析non A/B模式下的recovery流程
A/B模式下的recovery在boot中
后续会不断补充,如果有疏漏或者错误的地方,请指出,共同学习,谢谢!
首先列出recovery流程的几个重要点,接着会详细分析
首先看recovery.cpp的main函数,后面会解释为什么
[recovery.cpp]
int main(int argc, char **argv) {
//因为这个时候还没启动logcat,这里应该是设置将log打印到屏幕上和recovery.log中
android::base::InitLogging(argv, &UiLogger);
//下面是加载pmsg log文件
// Take last pmsg contents and rewrite it to the current pmsg session.
static const char filter[] = "recovery/";
// Do we need to rotate?
//这里应该和重命名log文件有关
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);
//看注释,这里是启动迷你版的adbd,为了使用adb sideload命令
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
minadbd_main();
return 0;
}
//猜测这里是把log输出重定向到/tmp/recovery.log
redirect_stdio(TEMPORARY_LOG_FILE);
//加载recovery.fstab并建立分区表信息,在etc/目录下,recovery模式连adb可以看到
load_volume_table();
//从上面建立的分区表信息中读取是否有cache分区,因为log等重要信息都存在cache分区里
has_cache = volume_for_path(CACHE_ROOT) != nullptr;
..........
}
详细看下是如何加载分区表的
[roots.cpp]
void load_volume_table()
{
int i;
int ret;
fstab = fs_mgr_read_fstab_default();
if (!fstab) {
LOG(ERROR) << "failed to read default fstab";
return;
}
//将对应的信息加入到一条链表中
ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
if (ret < 0 ) {
LOG(ERROR) << "failed to add /tmp entry to fstab";
fs_mgr_free_fstab(fstab);
fstab = NULL;
return;
}
printf("recovery filesystem table\n");
printf("=========================\n");
//在last_log中打印分区表信息
//打印的顺序是:
//编号 | 挂载节点 | 文件系统类型 | 块设备 | 长度
for (i = 0; i < fstab->num_entries; ++i) {
Volume* v = &fstab->recs[i];
printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
v->blk_device, v->length);
}
printf("\n");
}
跟踪fs_mgr_read_fstab_default
[fs_mgr_fstab.cpp]
struct fstab *fs_mgr_read_fstab_default()
{
std::string hw;
std::string default_fstab;
//下面应该是去其它位置查询fstab文件,由于/sbin/recovery是有的,这里default_fstab是"/etc/recovery.fstab"
//A/B和non A/B下fstab文件是不同的
if (access("/sbin/recovery", F_OK) == 0) {
default_fstab = "/etc/recovery.fstab";
} else if (fs_mgr_get_boot_config("hardware", &hw)) { // normal boot
for (const char *prefix : {"/odm/etc/fstab.","/vendor/etc/fstab.", "/fstab."}) {
default_fstab = prefix + hw;
if (access(default_fstab.c_str(), F_OK) == 0) break;
}
} else {
LWARNING << __FUNCTION__ << "(): failed to find device hardware name";
}
//fs_mgr_read_fstab_dt函数是读取/proc/device-tree/firmware/android/fstab文件
//fs_mgr_read_fstab是解析/etc/recovery.fstab文件
//in_place_merge将上面读取的结果合并
struct fstab *fstab_dt = fs_mgr_read_fstab_dt();
struct fstab *fstab = fs_mgr_read_fstab(default_fstab.c_str());
return in_place_merge(fstab_dt, fstab);
}
[recovery.cpp]
int main(int argc, char **argv) {
........
//通过get_args方法获取传入的参数,顺便解析到args中,后面会在log中打出参数
//get_args方法获取misc分区的command文件
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()); });
//调用getopt_long解析参数,这是固定用法,可以网上搜索资料,大致意思就是根据OPTIONS,将参数转换成对应的字符
//例如:传入的参数是"Command: "/sbin/recovery" "--update_package=/sdcard/update.zip" "--launch_app=update_launcher" "--requester=com.asus.UpdateLauncher" "--locale=zh_CN_#Hans""
//其中包含update_package,那么getopt_long依次返回的结果就是"u" "l"
while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
&option_index)) != -1) {
}
//加载客制化的东西,比如语言之类的
// load_locale_from_cache不展开说了,大致过程就是从之前解析分区表得到的fstab中查询/cache/recovery/last_locale文件是否存在,如果存在就读取里面的值
if (locale.empty()) {
if (has_cache) {
locale = load_locale_from_cache();
}
if (locale.empty()) {
locale = DEFAULT_LOCALE;
}
}
........
}
获取参数比较重要,仔细看看:
//提前了解下bootloader_message结构体
struct bootloader_message {
char command[32];
char status[32];
char recovery[768];
char stage[32];
char reserved[1184];
};
static std::vector<std::string> get_args(const int argc, char** const argv) {
CHECK_GT(argc, 0);
bootloader_message boot = {};
std::string err;
//通过函数从misc分区中去读BCB数据库到boot变量中
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<std::string> args(argv, argv + argc);
//如果传入参数为空,先看misc分区中的 bootloader_message是否有内容
if (args.size() == 1) {
boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination
std::string boot_recovery(boot.recovery);
std::vector<std::string> 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 << "\"";
}
}
//如果参数为空,并且有cache分区,那么解析"/cache/recovery/command"中内容
if (args.size() == 1 && has_cache) {
std::string content;
if (ensure_path_mounted(COMMAND_FILE) == 0 &&
android::base::ReadFileToString(COMMAND_FILE, &content)) {
std::vector<std::string> 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;
}
}
//将得到的参数写入到misc分区中
std::vector<std::string> options(args.cbegin() + 1, args.cend());
if (!update_bootloader_message(options, &err)) {
LOG(ERROR) << "Failed to set BCB message: " << err;
}
return args;
}
[recovery.cpp]
int main(int argc, char **argv) {
........
//设置更新的类型,如果参数中没有--security,那么在安装界面会显示”正在安装系统更新”,否则显示“正在安装安全更新”
//这里会调用SetSystemUpdateText 方法把显示哪种文字的选择存在installing_text中,后面解析具体命令的时候会调用GetCurrentText来显示
ui->SetSystemUpdateText(security_update);
//显示recovery的背景
ui->SetBackground(RecoveryUI::NONE);
//参数中没有show_text,此处为false
//ShowText的意思是显示菜单,Android默认不显示的
if (show_text) ui->ShowText(true);
//下面设置selinux权限
//相关文件:plat_file_contexts,nonplat_file_contexts,存放在recovery模式根目录下
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
//啥都没做
device->StartRecovery();
//把手机中的属性值打出来
property_list(print_property, NULL);
........
}
[recovery.cpp]
int main(int argc, char **argv) {
........
//下面就是具体升级的地方,重点!
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
modified_flash = true;
//首先判断电量是否OK,这里用的是系统提供的API
//is_battery_ok()方法的判断逻辑是:
1.手机非充电模式下,电量大于20%
2.手机充电模式下,注意,这里充电必须使用AC电源充电,USB连接电脑是不行的,这种情况下电量大于15%
在Android.mk中有”LOCAL_HAL_STATIC_LIBRARIES := libhealthd”这就是应用的库
recovery.cpp中包含”#include ”
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 {
//真正执行升级的地方
status = install_package(update_package, &should_wipe_cache,
TEMPORARY_INSTALL_FILE, true, retry_count);
//如果升级成功,并且主动要求清除cache,这里是通过升级脚本来判断的
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
//如果是IO出现错误,那么重新来一次
if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
copy_logs();
//调用set_retry_bootloader_message向bootloader_message写命令
set_retry_bootloader_message(retry_count, args);
// 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);
}
}
}
} else if
//下面是各种命令,比如清除cache,恢复工厂等,不细看
……
}else if (!just_exit) { //如果传入的参数中没有指定,那么进recovery就会走到这
status = INSTALL_NONE; // No command specified
//在recovery背景显示”No commnad”这是根据图片no_command_text.png来显示的
ui->SetBackground(RecoveryUI::NO_COMMAND);
//Android默认user版本不显示选项,只显示机器人倒地和”No command”如果想要进入recovery模式,按power键1~2s,再短按音量上键即可
//通常要把判断移除,直接显示选项
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
........
}
[recovery.cpp]
int main(int argc, char **argv) {
........
//如果升级错误,显示”Error!”,通常测试就会提供错误的图片
if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
//将log保存下来
copy_logs();
ui->SetBackground(RecoveryUI::ERROR);
}
//如果传入的参数中没有指定,那么稍后会自动重启,如果有那么关机
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
//当然,这只针对user版本,因为userdebug和eng版本在上面执行了ShowText,所以下面IsTextVisible得到的结果是true,就不会重启或关机
if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) ||
ui->IsTextVisible()) {
//prompt_and_wait()函数是个死循环 开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如重启,升级等
Device::BuiltinAction temp = prompt_and_wait(device, status);
if (temp != Device::NO_ACTION) {
after = temp;
}
}
........
}
看下log如何保存的:
[recovery.cpp]
static void copy_logs() {
//如果啥都没有改,不需要存log
if (!modified_flash) {
return;
}
// Always write to pmsg, this allows the OTA logs to be caught in logcat -L
//这里说是将log写入到pmsg中,下次通过logcat -L就可以查看log,为啥实验不行呢,提示”logcat read failure”
copy_log_file_to_pmsg(TEMPORARY_LOG_FILE, LAST_LOG_FILE);
copy_log_file_to_pmsg(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE);
// We can do nothing for now if there's no /cache partition.
if (!has_cache) {
return;
}
//确保要存入的log路径是挂载的
ensure_path_mounted(LAST_LOG_FILE);
ensure_path_mounted(LAST_KMSG_FILE);
//更改log的文件名,调整顺序
//如果有多份log,last_log是最新的,last_log.1第二,last_log.2第三,依次往后,last_kmsg同样
rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);
//拷贝log文件到cache/recovery/目录
copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
//保存kernel log, 这里面能看到selinux权限的问题,挺有用的
save_kernel_log(LAST_KMSG_FILE);
//下面是修改文件权限
chmod(LOG_FILE, 0600);
chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);
chmod(LAST_KMSG_FILE, 0600);
chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);
chmod(LAST_LOG_FILE, 0640);
chmod(LAST_INSTALL_FILE, 0644);
sync();
}
[recovery.cpp]
int main(int argc, char **argv) {
........
// Save logs and clean up before rebooting or shutting down.
finish_recovery();
//下面就是检测上面的after变量,没啥
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;
}
跟踪finish_recovery():
[recovery.cpp]
static void finish_recovery() {
//保存locale log到last_locale中,不知道这有什么用
if (!locale.empty() && has_cache) {
LOG(INFO) << "Saving locale \"" << locale << "\"";
FILE* fp = fopen_path(LOCALE_FILE, "w");
if (!android::base::WriteStringToFd(locale, fileno(fp))) {
PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
}
check_and_fclose(fp, LOCALE_FILE);
}
//拷贝log,上面已经介绍过
copy_logs();
//因为是正常退出recovery,所以需要把BSB清除掉,否则会循环进入recovery
std::string err;
if (!clear_bootloader_message(&err)) {
LOG(ERROR) << "Failed to clear BCB message: " << err;
}
// 把"/cache/recovery/command"清除掉,不然也会导致重新进入recovery
if (has_cache) {
if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
}
ensure_path_unmounted(CACHE_ROOT);
}
sync(); // For good measure.
}