君子知夫不全不粹之不足以为美也,
故诵数以贯之,
思索以通之,
为其人以处之,
除其害者以持养之;
出自荀子《劝学篇》
以下为Android 10 recovery源码分析
代码来源为:https://www.androidos.net.cn/android/10.0.0_r6/xref
之前两个章节主要说ota的基础,OTA升级详解(二)与 OTA升级详解(一),本节主要说下recovery如何使用zip升级包进行升级。
首先从文件层面说下升级功能的调用流程,说明如下:
recovery-main.cpp 为升级的主入口,
recovery.cpp 为开始recovery升级的处理流程
install/install.cpp 为执行升级的处理流程(调用updater)
updater/updater.cpp 为完成升级的核心流程。
Android-recovery升级代码路径为:bootable/recovery/
主入口代码为:recovery-main.cpp
1、日志相关的工作准备
// 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 constexpr const char filter[] = "recovery/";
// Do we need to rotate?
bool do_rotate = false;
__android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate);
// Take action to refresh pmsg contents
__android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate);
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());
2、 load_volume_table(); 加载系统分区信息,注意这里并明白挂载分区
.mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0
mount_point --挂载点
fs_type --分区类型
blk_device --设备块名
length --分区大小
3、挂载/cache分区,我们的升级命令都放在这个分区下
has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
4、获取升级的参数并写BCB块信息,这里其实完成了两个动作
std::vector<std::string> args = get_args(argc, argv);
if (!update_bootloader_message(options, &err)) {
LOG(ERROR) << "Failed to set BCB message: " << err;
}
a、读取misc分区分区,并将recovery模式升级的标记写到misc分区中,这样做的目的是断电续升,升级中掉电之后,如果下次开机重启,在bootloader中会读取此标记,并重新进入到recovery模式中 update_bootloader_message函数完成此功能。
b、从/cache/recovery/command 中读取升级参数,这里recovery启动进程是未带入参数时,command文件的接口其实有很详细的解释
* The arguments which may be supplied in the recovery.command file:
* --update_package=path - verify install an OTA package file
* --wipe_data - erase user data (and cache), then reboot
* --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
* data (and cache), then reboot
* --wipe_cache - wipe cache (but not user data), then reboot
* --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).
* --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
* --just_exit - do nothing; exit and reboot
5、加载recovery_ui_ext.so,完成升级中与屏幕信息的显示,升级进度,升级结果等。这里就不多说了。
static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
// Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have
// handed out pointers to code or static [or thread-local] data and doesn't collect them all back
// in on dlclose).
void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);
using MakeDeviceType = decltype(&make_device);
MakeDeviceType make_device_func = nullptr;
if (librecovery_ui_ext == nullptr) {
printf("Failed to dlopen %s: %s\n", kDefaultLibRecoveryUIExt, dlerror());
} else {
reinterpret_cast<void*&>(make_device_func) = dlsym(librecovery_ui_ext, "make_device");
if (make_device_func == nullptr) {
printf("Failed to dlsym make_device: %s\n", dlerror());
}
}
6、非fastboot模式升级就开始了recovery模式升级,start_recovery
auto ret = fastboot ? StartFastboot(device, args) : start_recovery(device, args);
进入 recovery.cpp
1、参数解析,这些参数其实就是来源于/cache/recovery/command, 上面已经通过get_arg,读取到了args中
2、界面的各种ui信息显示,点事电量的检查等待辅助动作。
3、函数名为安装升级包,其实还未真正开始进行升级包的安装
status = install_package(update_package, should_wipe_cache, true, retry_count, ui);
4、安装结束之后由finish_recovery()完成收尾工作,保存日志、清除BCB中的标记,设备重启。
static void finish_recovery() {
std::string locale = ui->GetLocale();
// Save the locale to cache, so if recovery is next started up without a '--locale' argument
// (e.g., directly from the bootloader) it will use the last-known locale.
if (!locale.empty() && has_cache) {
LOG(INFO) << "Saving locale \"" << locale << "\"";
if (ensure_path_mounted(LOCALE_FILE) != 0) {
LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
} else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
}
}
copy_logs(save_current_log, has_cache, sehandle);
// Reset to normal system boot so recovery won't cycle indefinitely.
std::string err;
if (!clear_bootloader_message(&err)) {
LOG(ERROR) << "Failed to clear BCB message: " << err;
}
// Remove the command file, so recovery won't repeat indefinitely.
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.
}
install/install.cpp
1、install.cpp其实就进入了安装升级包的准备动作,刚上的install_package,是假的,这里才是 really_install_package
result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
retry_count, &max_temperature, ui);
2、
static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>* log_buffer, int retry_count,
int* max_temperature, RecoveryUI* ui) {
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
ui->Print("Finding update package...\n");
// Give verification half the progress bar...
ui->SetProgressType(RecoveryUI::DETERMINATE);
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
LOG(INFO) << "Update location: " << path;
// Map the update package into memory.
ui->Print("Opening update package...\n");
if (needs_mount) {
if (path[0] == '@') {
ensure_path_mounted(path.substr(1));
} else {
ensure_path_mounted(path);
}
}
/* 将zip映射到内存中 */
auto package = Package::CreateMemoryPackage(
path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
if (!package) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
return INSTALL_CORRUPT;
}
// Verify package.进行zip包进行签名校验
if (!verify_package(package.get(), ui)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
return INSTALL_CORRUPT;
}
// Try to open the package.打开zip包
ZipArchiveHandle zip = package->GetZipArchiveHandle();
if (!zip) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
return INSTALL_CORRUPT;
}
// Additionally verify the compatibility of the package if it's a fresh install.
if (retry_count == 0 && !verify_package_compatibility(zip)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
return INSTALL_CORRUPT;
}
// Verify and install the contents of the package.
ui->Print("Installing update...\n");
if (retry_count > 0) {
ui->Print("Retry attempt: %d\n", retry_count);
}
ui->SetEnableReboot(false);
int result =
/* 执行升级updater进程进行升级 */
try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
ui->SetEnableReboot(true);
ui->Print("\n");
return result;
}
updater/updater.cpp
1、从升级包中读取元数据信息
ReadMetadataFromPackage(zip, &metadata)
2、从升级包中读取updater进程
int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
int status_fd, std::vector<std::string>* cmd) {
CHECK(cmd != nullptr);
// In non-A/B updates we extract the update binary from the package.
static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
ZipString binary_name(UPDATE_BINARY_NAME);
ZipEntry binary_entry;
if (FindEntry(zip, binary_name, &binary_entry) != 0) {
LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
return INSTALL_CORRUPT;
}
const std::string binary_path = Paths::Get().temporary_update_binary();
unlink(binary_path.c_str());
android::base::unique_fd fd(
open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
if (fd == -1) {
PLOG(ERROR) << "Failed to create " << binary_path;
return INSTALL_ERROR;
}
int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
if (error != 0) {
LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
return INSTALL_ERROR;
}
// When executing the update binary contained in the package, the arguments passed are:
// - the version number for this interface
// - an FD to which the program can write in order to update the progress bar.
// - the name of the package zip file.
// - an optional argument "retry" if this update is a retry of a failed update attempt.
*cmd = {
binary_path,
std::to_string(kRecoveryApiVersion),
std::to_string(status_fd),
package,
};
if (retry_count > 0) {
cmd->push_back("retry");
}
return 0;
}
4、创建管道,这里子进程关闭了读端,父进程关闭了写端,这样就是保证从单向的信息通信,从子进程传入信息到父进程中。
android::base::Pipe(&pipe_read, &pipe_write, 0)
5、创建子进程,在子进程中运行update-binary进程
if (pid == 0) {
umask(022);
pipe_read.reset();
// Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
auto chr_args = StringVectorToNullTerminatedArray(args);
/* chr_args[0] 其实就是升级包中的 META-INF/com/google/android/update-binary */
execv(chr_args[0], chr_args.data());
// We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to
// hang. This deadlock results from an improperly copied mutex in the ui functions.
// (Bug: 34769056)
fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
_exit(EXIT_FAILURE);
}
6、recovery获取子进程的信息并显示,进度、ui_print 等等。
FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
while (fgets(buffer, sizeof(buffer), from_child) != nullptr)
execv执行升级进程之后,工作在updater/updater.cpp中完成。
1、这里的主要核心就是构造脚本解析器对updater-script中的命令进行执行,至于这个脚本解析器是如何构造的,如何执行的, 其实我也搞的不是很清楚。
2、安装升级包的核心程序就是Configure edify's functions. 中的那些注册回调函数
int main(int argc, char** argv) {
// Various things log information to stdout or stderr more or less
// at random (though we've tried to standardize on stdout). The
// log file makes more sense if buffering is turned off so things
// appear in the right order.
setbuf(stdout, nullptr);
setbuf(stderr, nullptr);
// We don't have logcat yet under recovery. Update logs will always be written to stdout
// (which is redirected to recovery.log).
android::base::InitLogging(argv, &UpdaterLogger);
if (argc != 4 && argc != 5) {
LOG(ERROR) << "unexpected number of arguments: " << argc;
return 1;
}
/* 支持的版本检查 */
char* version = argv[1];
if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
// We support version 1, 2, or 3.
LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
return 2;
}
// Set up the pipe for sending commands back to the parent process.
int fd = atoi(argv[2]);
FILE* cmd_pipe = fdopen(fd, "wb");
setlinebuf(cmd_pipe);
// Extract the script from the package.
/* 从包中提取脚本 */
const char* package_filename = argv[3];
MemMapping map;
if (!map.MapFile(package_filename)) {
LOG(ERROR) << "failed to map package " << argv[3];
return 3;
}
ZipArchiveHandle za;
int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
if (open_err != 0) {
LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
CloseArchive(za);
return 3;
}
ZipString script_name(SCRIPT_NAME);
ZipEntry script_entry;
int find_err = FindEntry(za, script_name, &script_entry);
if (find_err != 0) {
LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
<< ErrorCodeString(find_err);
CloseArchive(za);
return 4;
}
std::string script;
script.resize(script_entry.uncompressed_length);
int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
script_entry.uncompressed_length);
if (extract_err != 0) {
LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
CloseArchive(za);
return 5;
}
// Configure edify's functions.
/* 注册updater-script中的回调函数 这里主要是一些断言函数 abort assert*/
RegisterBuiltins();
/* 这里主要是一些安装升级包的函数 主要是对有文件系统的分区来说*/
RegisterInstallFunctions();
/* 这里主要注册对裸分区进行升级的函数 */
RegisterBlockImageFunctions();
RegisterDynamicPartitionsFunctions();
RegisterDeviceExtensions();
// Parse the script.
std::unique_ptr<Expr> root;
int error_count = 0;
int error = ParseString(script, &root, &error_count);
if (error != 0 || error_count > 0) {
LOG(ERROR) << error_count << " parse errors";
CloseArchive(za);
return 6;
}
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
}
// Evaluate the parsed script.
UpdaterInfo updater_info;
updater_info.cmd_pipe = cmd_pipe;
updater_info.package_zip = za;
updater_info.version = atoi(version);
updater_info.package_zip_addr = map.addr;
updater_info.package_zip_len = map.length;
State state(script, &updater_info);
if (argc == 5) {
if (strcmp(argv[4], "retry") == 0) {
state.is_retry = true;
} else {
printf("unexpected argument: %s", argv[4]);
}
}
std::string result;
bool status = Evaluate(&state, root, &result);
if (!status) {
if (state.errmsg.empty()) {
LOG(ERROR) << "script aborted (no error message)";
fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
} else {
LOG(ERROR) << "script aborted: " << state.errmsg;
const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
for (const std::string& line : lines) {
// Parse the error code in abort message.
// Example: "E30: This package is for bullhead devices."
if (!line.empty() && line[0] == 'E') {
if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
LOG(ERROR) << "Failed to parse error code: [" << line << "]";
}
}
fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
}
}
// Installation has been aborted. Set the error code to kScriptExecutionFailure unless
// a more specific code has been set in errmsg.
if (state.error_code == kNoError) {
state.error_code = kScriptExecutionFailure;
}
fprintf(cmd_pipe, "log error: %d\n", state.error_code);
// Cause code should provide additional information about the abort.
if (state.cause_code != kNoCause) {
fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
if (state.cause_code == kPatchApplicationFailure) {
LOG(INFO) << "Patch application failed, retry update.";
fprintf(cmd_pipe, "retry_update\n");
} else if (state.cause_code == kEioFailure) {
LOG(INFO) << "Update failed due to EIO, retry update.";
fprintf(cmd_pipe, "retry_update\n");
}
}
if (updater_info.package_zip) {
CloseArchive(updater_info.package_zip);
}
return 7;
} else {
fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
}
if (updater_info.package_zip) {
CloseArchive(updater_info.package_zip);
}
return 0;
}
以上就是基于Android的OTA的recovery模式升级流程。我这里主要是梳理整个升级流程的走向,很多地方还是写的不够细,望读者理解,这里的比较核心与关键的地方我认为有以下几点吧
1、主系统与recovery升级系统,升级消息的传递
2、主系统中fork子进程进行升级进程的执行,并通过pipe管道进行信息交互
3、updater中使用命令与执行的分离,命令在updater-script中,执行在update-binary中。
4、升级程序通过升级包带入的,那么核心升级流程是每次都有机会变更或者优化的,这样就比那些将升级流程预置在系统中的要灵活的很多。
关注微信公众号【嵌入式C部落】,获取更多精华文章,海量编程资料,让我们一起进步,一起成长。