文章目录
- Recovery升级准备流程
- Recovery执行升级
- 多进程/线程架构
- update-script、updater-binary与system.patch.dat、system.transfer.list
Recovery升级准备流程
- OTA应用调用Framework内提供的系统接口
RecoverySystem.installPackage()
后,在/cache/recovery/block.map
中写入升级信息。该文件将会在设备重启时,由启动引导程序读取内容,并根据内容启动为recovery模式,调起recovery进行OTA包的升级。
- Recovery启动后,首先挂载分区,读取系统属性,然后通过block.map指定的路径尝试读取升级文件并进行校验。
- 取出OTA包内的升级文件和列表
system.patch.dat
、system.new.dat
、system.transfer.list
、boot.img
、uboot.img
。metadata
用于校验版本。若update-binary
有,则取出,用于执行升级脚本定义的升级命令。updater-script
定义了用于执行升级的脚本。
- 根据OTA包内metadata定义的post-build和pre-build的匹配来确定当前OTA是否是本设备对应Android版本的合适差分包。确认版本号匹配后,才会进行校验。若版本号不匹配,则在校验进行之前就会退出安装。
OTA安装流程
/* OTA INSTALL
* 1. main system downloads OTA package to /cache/some-filename.zip
* 2. main system writes "--update_package=/cache/some-filename.zip"
* 3. main system reboots into recovery
* 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
* -- after this, rebooting will attempt to reinstall the update --
* 5. install_package() attempts to install the update
* NOTE: the package install must itself be restartable from any point
* 6. finish_recovery() erases BCB
* -- after this, rebooting will (try to) restart the main system --
* 7. ** if install failed **
* 7a. prompt_and_wait() shows an error icon and waits for the user
* 7b; the user reboots (pulling the battery, etc) into the main system
* 8. main() calls maybe_install_firmware_update()
* ** if the update contained radio/hboot firmware **:
* 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
* -- after this, rebooting will reformat cache & restart main system --
* 8b. m_i_f_u() writes firmware image into raw cache partition
* 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
* -- after this, rebooting will attempt to reinstall firmware --
* 8d. bootloader tries to flash firmware
* 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
* -- after this, rebooting will reformat cache & restart main system --
* 8f. erase_volume() reformats /cache
* 8g. finish_recovery() erases BCB
* -- after this, rebooting will (try to) restart the main system --
* 9. main() calls reboot() to boot main system
*/
Recovery执行升级
- metadata: 键值对存储目标版本和源版本的信息,匹配成功才会进行OTA包的写入。
- *.dat *.list: 按照block块diff得到的数据文件。
- update-binary、update-script: recovery会fork子进程,exec执行OTA包内的update-binary,而update-binary实际上是一个脚本执行器,它执行的脚本就是OTA包的安装指令,这些指令存储在update-script内。
- recovery挂载分区、准备参数、获取升级文件后,调用
install_package()
(实际上是really_install_package()
真正执行安装)进行OTA包的安装。该方法校验升级包后,通过try_update_binary()
执行安装。
static int
really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector& log_buffer, int retry_count)
{
// ...
// Map the update package into memory.
ui->Print("Opening update package...\n");
// ...
// Verify package.
if (!verify_package(map.addr, map.length)) {
log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
// Try to open the package.
ZipArchive zip;
int err = mzOpenZipArchive(map.addr, map.length, &zip);
if (err != 0) {
LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
sysReleaseMap(&map);
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 = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count);
ui->SetEnableReboot(true);
ui->Print("\n");
sysReleaseMap(&map);
return result;
}
try_update_binary()
会fork一个子进程并通过pipe进行通信。fork后的父进程会从pipe中读取子进程发出的指令,并执行对应的方法,如打印字符串到屏幕、重试更新、设置更新进度条和抹除设备数据等。pipe关闭后,父进程获取子进程的退出状态即更新包的安装结果。子进程会exec一个打包进入OTA包的updater。它是一个可执行bin,是执行OTA安装的关键进程。
// 如果OTA升级包内包含update-binary,就会将它解压出来作为安装执行器。即fork的子进程会运行这个bin,这个bin真正执行将OTA升级包写入系统。
static int
try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache,
std::vector& log_buffer, int retry_count)
{
read_source_target_build(zip, log_buffer);
// 这里是父进程准备与子进程通信的pipe
int pipefd[2];
pipe(pipefd);
std::vector args;
// 根据系统是否是AB升级,确定升级命令,实际就是从OTA包中取出update-binary并组建升级命令
// 升级命令既是调用子进程运行update-binary的指令
int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
mzCloseZipArchive(zip);
if (ret) {
close(pipefd[0]);
close(pipefd[1]);
return ret;
}
// 接下来通过注释阐述了父进程,即当前进程能够支持的函数
// 这些函数将由子进程(即将创建的update-binary的进程)通过pipe向父进程(本进程)以字符串的形式发出函数名称和参数,进而根据函数名称调用函数
// 基本常用的包括设置进度(显示到ui)、ui打印和清除数据
// 子进程将被创建以执行升级,而本进程(父进程)会循环等待pipe的数据,并解释后执行对应的函数,实现进程分离独立运行(父进程实现UI和其他工作,子进程负责安装)
// 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 program can write single-line commands:
//
// progress
// fill up the next part of of the progress bar
// over seconds. If is zero, use
// set_progress commands to manually control the
// progress of this segment of the bar.
//
// set_progress
// should be between 0.0 and 1.0; sets the
// progress bar within the segment defined by the most
// recent progress command.
//
// firmware <"hboot"|"radio">
// arrange to install the contents of in the
// given partition on reboot.
//
// (API v2: may start with "PACKAGE:" to
// indicate taking a file from the OTA package.)
//
// (API v3: this command no longer exists.)
//
// ui_print
// display on the screen.
//
// wipe_cache
// a wipe of cache will be performed following a successful
// installation.
//
// clear_display
// turn off the text display.
//
// enable_reboot
// packages can explicitly request that they want the user
// to be able to reboot during installation (useful for
// debugging packages that don't exit).
//
// - the name of the package zip file.
//
// - an optional argument "retry" if this update is a retry of a failed
// update attempt.
//
// 准备创建子进程,准备子进程参数,准备管道
// Convert the vector to a NULL-terminated char* array suitable for execv.
const char* chr_args[args.size() + 1];
chr_args[args.size()] = NULL;
for (size_t i = 0; i < args.size(); i++) {
chr_args[i] = args[i].c_str();
}
pid_t pid = fork();
if (pid == -1) {
close(pipefd[0]);
close(pipefd[1]);
LOGE("Failed to fork update binary: %s\n", strerror(errno));
return INSTALL_ERROR;
}
// 子进程,执行update-binary,跳转到updater.cpp
if (pid == 0) {
umask(022);
close(pipefd[0]);
execv(chr_args[0], const_cast(chr_args));
fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
_exit(-1);
}
close(pipefd[1]);
*wipe_cache = false;
bool retry_update = false;
// 父进程,在管道的一端进行读循环,直到子进程退出时关闭管道后,跳出循环
// 子进程(update-binary)会向管道写入函数名和参数,这些字符串形式的信息在接下来的while()中被解析,然后由父进程(本进程)执行对应的函数。
char buffer[1024];
FILE* from_child = fdopen(pipefd[0], "r");
while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
char* command = strtok(buffer, " \n");
if (command == NULL) {
continue;
} else if (strcmp(command, "progress") == 0) {
char* fraction_s = strtok(NULL, " \n");
char* seconds_s = strtok(NULL, " \n");
float fraction = strtof(fraction_s, NULL);
int seconds = strtol(seconds_s, NULL, 10);
ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
} else if (strcmp(command, "set_progress") == 0) {
char* fraction_s = strtok(NULL, " \n");
float fraction = strtof(fraction_s, NULL);
ui->SetProgress(fraction);
} else if (strcmp(command, "ui_print") == 0) {
char* str = strtok(NULL, "\n");
if (str) {
ui->PrintOnScreenOnly("%s", str);
} else {
ui->PrintOnScreenOnly("\n");
}
fflush(stdout);
} else if (strcmp(command, "wipe_cache") == 0) {
*wipe_cache = true;
} else if (strcmp(command, "clear_display") == 0) {
ui->SetBackground(RecoveryUI::NONE);
} else if (strcmp(command, "enable_reboot") == 0) {
// packages can explicitly request that they want the user
// to be able to reboot during installation (useful for
// debugging packages that don't exit).
ui->SetEnableReboot(true);
} else if (strcmp(command, "retry_update") == 0) {
retry_update = true;
} else if (strcmp(command, "log") == 0) {
// Save the logging request from updater and write to
// last_install later.
log_buffer.push_back(std::string(strtok(NULL, "\n")));
} else if (strcmp(command, "wipe_all") == 0){
printf("set bWipeAfterUpdate to true.\n");
bWipeAfterUpdate = true;
}else {
LOGE("unknown command [%s]\n", command);
}
}
fclose(from_child);
// 子进程结束,这里获取子进程的退出状态码
int status;
waitpid(pid, &status, 0);
if (retry_update) {
return INSTALL_RETRY;
}
// 分别获取子进程是否正常退出(不是因为崩溃或信号退出的),并获取退出状态码。若子进程正常退出,则这里已经完成了安装,接下来回到install.cpp完成收尾后重启。
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
return INSTALL_ERROR;
}
return INSTALL_SUCCESS;
}
- 通过子进程执行安装。
- 子进程是在
install.cpp
的try_binary_update
中fork后execv启动的。代码位于```updater.cpp`。
int main(int argc, char** argv) {
// ...
// 通过execv时传递的参数,获取管道fd
int fd = atoi(argv[2]);
FILE* cmd_pipe = fdopen(fd, "wb");
setlinebuf(cmd_pipe);
// 从OTA升级包中解压得到updater-script,该文件与本进程的bin(update-binary)在同一个目录下
// 这个script是某种特定格式的脚本,有本进程解析并执行,其内容就是升级的所有内容。包含升级文件的校验、升级流程的控制和升级步骤,全部都在这个文件内控制。
const char* package_filename = argv[3];
g_package_file = package_filename;
MemMapping map;
if (sysMapFile(package_filename, &map) != 0) {
printf("failed to map package %s\n", argv[3]);
return 3;
}
ZipArchive za;
int err;
err = mzOpenZipArchive(map.addr, map.length, &za);
if (err != 0) {
printf("failed to open package %s: %s\n",
argv[3], strerror(err));
return 3;
}
ota_io_init(&za);
const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
if (script_entry == NULL) {
printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename);
return 4;
}
char* script = reinterpret_cast(malloc(script_entry->uncompLen+1));
if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
printf("failed to read script from package\n");
return 5;
}
script[script_entry->uncompLen] = '\0';
// 注册处理函数,实际上就是将函数名字符串与对应的函数指针关联起来
// 从updater-script中读取到的执行升级任务的脚本,其实就是以下注册的函数名加上一些参数。即读出来的内容,根据对应的函数名调用对应的函数,就实现了解析执行安装脚本(updater-script)的功能。
RegisterBuiltins();
RegisterInstallFunctions();
RegisterBlockImageFunctions(); // 例,这里注册了升级system分区的常用的指令,使得update-binary(本进程)可以执行对应的system分区升级命令。
RegisterDeviceExtensions();
FinishRegistration();
// 解析脚本。这里调用的parse_string是updater-script所使用的一种特定格式语言的解析器,经过执行后会将结果保存到`Expr* root`。它是一个链表。这个链表实际上很简单,仅包含了解析出来的命令(字符串形式,在上面注册的Register*Func中找得到对应的函数)、对应的参数(传递给函数的参数)和链表成员指针(指向链表的下一个成员)
// parse_string按行解析脚本,也将每一行结果保存为一个链表成员。所以每个链表成员实际上都是一行updater-script的内容,也是一行命令,对应了一个函数。
Expr* root;
int error_count = 0;
int error = parse_string(script, &root, &error_count);
if (error != 0 || error_count > 0) {
printf("%d parse errors\n", error_count);
return 6;
}
struct selinux_opt seopts[] = {
{ SELABEL_OPT_PATH, "/file_contexts" }
};
sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
if (!sehandle) {
fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
}
// 完成了updater-script的解析(即通过parse_string将updater-script的指令转换成一个个函数调用指令,通过有序链表存储)
// 这里开始解析链表,将链表指示的函数调用,通过上面注册的函数(Register*Function)找到对应的函数,并执行。
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;
state.cookie = &updater_info;
state.script = script;
state.errmsg = NULL;
if (argc == 5) {
if (strcmp(argv[4], "retry") == 0) {
state.is_retry = true;
} else {
printf("unexpected argument: %s", argv[4]);
}
}
// 执行脚本指示的函数。其中state用于返回,提示调用结果。root即上文的Expr,里面包含了注册的函数指针,包含了从update-script解析出来的函数参数、函数名。
char* result = Evaluate(&state, root);
if (have_eio_error) {
fprintf(cmd_pipe, "retry_update\n");
}
// 脚本执行,错误处理
if (result == NULL) {
if (state.errmsg == NULL) {
printf("script aborted (no error message)\n");
fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
} else {
printf("script aborted: %s\n", state.errmsg);
char* line = strtok(state.errmsg, "\n");
while (line) {
// Parse the error code in abort message.
// Example: "E30: This package is for bullhead devices."
if (*line == 'E') {
if (sscanf(line, "E%u: ", &state.error_code) != 1) {
printf("Failed to parse error code: [%s]\n", line);
}
}
fprintf(cmd_pipe, "ui_print %s\n", line);
line = strtok(NULL, "\n");
}
fprintf(cmd_pipe, "ui_print\n");
}
if (state.error_code != kNoError) {
fprintf(cmd_pipe, "log error: %d\n", state.error_code);
// Cause code should provide additional information about the abort;
// report only when an error exists.
if (state.cause_code != kNoCause) {
fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
}
}
free(state.errmsg);
return 7;
} else {
fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result);
free(result);
}
if (updater_info.package_zip) {
mzCloseZipArchive(updater_info.package_zip);
}
sysReleaseMap(&map);
free(script);
return 0;
}
- 子进程解析脚本调用注册的函数完成处理后,即退出,由父进程完成收尾工作并重启。
多进程/线程架构
- recovery中可能会同时存在多个进程和线程
- 如updater就运行于独立的进程。
- updater执行对应的定义于updater-script的命令时,可能会创建线程。
- 如block_image_update对应的函数
PerformBlockImageUpdate
就会创建线程来写入block差分数据。
if (params.canwrite) {
params.nti.za = za;
params.nti.entry = new_entry;
pthread_mutex_init(¶ms.nti.mu, nullptr);
pthread_cond_init(¶ms.nti.cv, nullptr);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti);
if (error != 0) {
fprintf(stderr, "pthread_create failed: %s\n", strerror(error));
return StringValue(strdup(""));
}
}
update-script、updater-binary与system.patch.dat、system.transfer.list
- update-binary是OTA包内包含的执行升级指令的Bin,这个bin根据updater-script定义的一系列脚本来执行升级;
- 在较高版本的Android OTA中,recovery支持以block diff形式生成的差分包进行升级。当update-binary进行system分区的升级时,往往是根据updater-script中定义的
block_image_update
指令进行块差异升级。其中,update-script仅仅只是定义了执行该命令,而真正保存差异的(即即将写入system分区的内容)是system.patch.dat和system.transfer.list