本节继续分析一下升级过程。Android中sideload的安装方式比较少见,并且是比较复杂的,我们通过分析这种升级方式来了解Recovery的升级过程。
1、sideload安装方式
在上一节prompt_and_wait函数中处理菜单命令时,如果选择的命令是APPLY_ADB_SIDELOAD,将启动ADBD,让用户通过adb连接来执行sideload命令上传,更新文件到/tmp/update.zip,然后再执行更新操作。在prompt_and_wait函数中的处理如下:
case Device::APPLY_ADB_SIDELOAD:
status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);//入口函数,/tmp/last_install
if (status >= 0) {
if (status != INSTALL_SUCCESS) {
ui->SetBackground(RecoveryUI::ERROR);
ui->Print("Installation aborted.\n");
copy_logs();
} else if (!ui->IsTextVisible()) {
return Device::NO_ACTION; // reboot if logs aren't visible
} else {
ui->Print("\nInstall from ADB complete.\n");
}
}
break;
sideload安装方式的入口是apply_from_adb()函数,位于bootable/recovery/Adb_install.cpp中,代码如下:
int
apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) {
ui = ui_;
stop_adbd();//先停止ADBD
set_usb_driver(true);//启动USB连接
//打印使用sideload命令的提示
ui->Print("\n\nNow send the package you want to apply\n"
"to the device with \"adb sideload \"...\n");
pid_t child;
if ((child = fork()) == 0) {//fork一个子进程,然后在子进程中执行recovery
execl("/sbin/recovery", "recovery", "--adbd", NULL);
_exit(-1);
}
// FUSE_SIDELOAD_HOST_PATHNAME will start to exist once the host
// connects and starts serving a package. Poll for its
// appearance. (Note that inotify doesn't work with FUSE.)
int result;
int status;
bool waited = false;
struct stat st;
for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
if (waitpid(child, &status, WNOHANG) != 0) {//等待子进程ADBD结束
result = INSTALL_ERROR;
waited = true;
break;
}
if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {//获取/sideload/package.zip文件信息,并保存在stat结构体中
if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) {//文件不存在
sleep(1);
continue;
} else {//超时
ui->Print("\nTimed out waiting for package.\n\n", strerror(errno));
result = INSTALL_ERROR;
kill(child, SIGKILL);
break;
}
}
result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false);//文件存在,调用install_package函数开始安装
break;
}
if (!waited) {
// Calling stat() on this magic filename signals the minadbd
// subprocess to shut down.
stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
// TODO(dougz): there should be a way to cancel waiting for a
// package (by pushing some button combo on the device). For now
// you just have to 'adb sideload' a file that's not a valid
// package, like "/dev/null".
waitpid(child, &status, 0);
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
if (WEXITSTATUS(status) == 3) {
ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");
} else if (!WIFSIGNALED(status)) {
ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));
}
}
set_usb_driver(false);//关闭USB连接
maybe_restart_adbd();
return result;
}
apply_from_adb()函数会创建一个子进程,这个子进程执行recovery文件,但是启动的参数是--adbd,在前面Recovery的main函数中介绍过,如果启动参数带有adbd,将会调用adb_main函数,这时候recovery实际上变成了一个简单的ADBD的守护进程。这个简单的ADBD的主要作用就是接收从用户上传的更新包文件,然后放到/tmp/目录下,并改名为update.zip,得到文件后ADBD就会结束。recovery模块启动子进程后,会调用waitpid等待它结束,然后才继续执行,如果recovery在temp目录下发现了update.zip文件,则调用install_package函数来执行安装。
2、升级的入口函数
系统升级的入口为install_package()函数,代码位于/bootable/recovery/Install.cpp中,如下:
int
install_package(const char* path, int* wipe_cache, const char* install_file,
bool needs_mount)
{
FILE* install_log = fopen_path(install_file, "w");
if (install_log) {//检查文件/tmp/last_install是否存在
fputs(path, install_log);//将新的文件路径/sideload/package.zip记录到/tmp/last_install文件中
fputc('\n', install_log);
} else {
LOGE("failed to open last_install: %s\n", strerror(errno));
}
int result;
if (setup_install_mounts() != 0) {//确保/tmp和/cache分区已经mount
LOGE("failed to set up expected mounts for install; aborting\n");
result = INSTALL_ERROR;
} else {
result = really_install_package(path, wipe_cache, needs_mount);//继续执行安装
}
if (install_log) {
fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);//记录更新是否成功
fputc('\n', install_log);
fclose(install_log);
}
return result;
}
install_package函数首先调用setup_install_mounts函数来mount安装过程中需要使用的目录,因为这些目录可能还没有装载,或者中间又被卸载了。setup_install_mounts函数位于/bootable/recovery/Roots.cpp中,如下:
int setup_install_mounts() {
if (fstab == NULL) {
LOGE("can't set up install mounts: no fstab loaded\n");
return -1;
}
for (int i = 0; i < fstab->num_entries; ++i) {
Volume* v = fstab->recs + i;
if (strcmp(v->mount_point, "/tmp") == 0 ||
strcmp(v->mount_point, "/cache") == 0) {
if (ensure_path_mounted(v->mount_point) != 0) {//只mount /tmp和/cache分区
LOGE("failed to mount %s\n", v->mount_point);
return -1;
}
} else {
if (ensure_path_unmounted(v->mount_point) != 0) {//unmount其他分区
LOGE("failed to unmount %s\n", v->mount_point);
return -1;
}
}
}
return 0;
}
setup_install_mounts函数主要目标是确保/tmp和/cache分区加载成功,同时将其他分区卸载。之后install_package调用really_install_package()继续安装。
static int
really_install_package(const char *path, int* wipe_cache, bool needs_mount)
{
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);
LOGI("Update location: %s\n", path);
// Map the update package into memory.
ui->Print("Opening update package...\n");
if (path && needs_mount) {//确保更新包所在的路径已经mount
if (path[0] == '@') {
ensure_path_mounted(path+1);
} else {
ensure_path_mounted(path);
}
}
MemMapping map;
if (sysMapFile(path, &map) != 0) {
LOGE("failed to map file\n");
return INSTALL_CORRUPT;
}
int numKeys;//从/res/keys文件中装载设备的签名文件
Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
if (loadedKeys == NULL) {//找不到系统文件
LOGE("Failed to load keys\n");
return INSTALL_CORRUPT;
}
LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
ui->Print("Verifying update package...\n");
int err;//校验更新包的签名
err = verify_file(map.addr, map.length, loadedKeys, numKeys);
free(loadedKeys);
LOGI("verify_file returned %d\n", err);
if (err != VERIFY_SUCCESS) {//校验失败,退出
LOGE("signature verification failed\n");
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
/* Try to open the package.
*/
ZipArchive zip;
err = mzOpenZipArchive(map.addr, map.length, &zip);//打开文件
if (err != 0) {
LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
sysReleaseMap(&map);
return INSTALL_CORRUPT;
}
/* Verify and install the contents of the package.
*/
ui->Print("Installing update...\n");
ui->SetEnableReboot(false);
int result = try_update_binary(path, &zip, wipe_cache);//开始安装
ui->SetEnableReboot(true);
ui->Print("\n");
sysReleaseMap(&map);
return result;
}
really_install_package函数最主要的工作就是校验更新包的签名。之后调用try_update_binary继续安装过程,代码如下
// If the package contains an update binary, extract it and run it.
static int
try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
const ZipEntry* binary_entry =
mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);//在zip格式的更新包文件中得到文件META-INF/com/google/android/update-binary的entry
if (binary_entry == NULL) {
mzCloseZipArchive(zip);
return INSTALL_CORRUPT;//安装包中找不到update_binary文件
}
const char* binary = "/tmp/update_binary";
unlink(binary);
int fd = creat(binary, 0755);//创建空文件/tmp/update_binary
if (fd < 0) {
mzCloseZipArchive(zip);
LOGE("Can't make %s\n", binary);
return INSTALL_ERROR;//创建文件失败,返回
}
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);//把zip更新包中的update_binary文件保存到/tmp/update_binary文件中
close(fd);
mzCloseZipArchive(zip);
if (!ok) {
LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
return INSTALL_ERROR;//解压缩失败,返回
}
int pipefd[2];
pipe(pipefd);//创建管道
const char** args = (const char**)malloc(sizeof(char*) * 5);//准备参数
args[0] = binary;
args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk
char* temp = (char*)malloc(10);
sprintf(temp, "%d", pipefd[1]);
args[2] = temp;
args[3] = (char*)path;
args[4] = NULL;
pid_t pid = fork();
if (pid == 0) {
umask(022);
close(pipefd[0]);
execv(binary, (char* const*)args);//在子进程中执行/tmp/update_binary文件
fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
_exit(-1);
}
close(pipefd[1]);
*wipe_cache = 0;
//父进程通过管道接收子进程的命令,主要的命令就是用来更新UI,显示更新进度和提示信息
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) {//更新UI提示
char* str = strtok(NULL, "\n");
if (str) {
ui->Print("%s", str);
} else {
ui->Print("\n");
}
fflush(stdout);
} else if (strcmp(command, "wipe_cache") == 0) {
*wipe_cache = 1;
} 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 {
LOGE("unknown command [%s]\n", command);
}
}
fclose(from_child);
int status;
waitpid(pid, &status, 0);//等待子进程执行完毕
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
return INSTALL_ERROR;
}
return INSTALL_SUCCESS;
}
从try_update_binary函数可以看到,最后的文件更新过程其实还不是在recovery进程中完成的。try_update_binary()函数把更新包中的META_INF/com/google/android/update-binary文件解压缩出来放到目录/tmp中,然后创建子进程来运行update-binary可执行文件,update-binary将根据更新包中的update-script脚本来更新系统文件。在子进程执行更新过程中,recovery进程通过管道和子进程保持联系,子进程把更新的进度通过管道传递给recovery,由recovery显示出来。
3、update-binary模块
update-binary才是最后执行系统升级的模块。Android中update-binary模块的源文件位于bootable/recovery/updater中,这个目录下的Android.mk中指定的模块名是updater,update-binary是在制作更新包时指定的一个新的文件名。
update-binary模块的执行流程,代码位于/bootable/recovery/updater/Updater.cpp,如下:
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, NULL);
setbuf(stderr, NULL);
if (argc != 4) {//检查参数
printf("unexpected number of arguments (%d)\n", argc);
return 1;
}
char* version = argv[1];
if ((version[0] != '1' && version[0] != '2' && version[0] != '3') ||
version[1] != '\0') {//检查版本,只支持1、2、3
// We support version 1, 2, or 3.
printf("wrong updater binary API; expected 1, 2, or 3; "
"got %s\n",
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 (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);//打开升级包,并将相关的信息拷贝到一个临时的ZipArchinve变量中
if (err != 0) {
printf("failed to open package %s: %s\n",
argv[3], strerror(err));
return 3;
}
const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);//META-INF/com/google/android/updater-script
if (script_entry == NULL) {
printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename);
return 4;
}
char* script = 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';
// Configure edify's functions.//注册处理命令的函数(识别脚本中命令的函数)
RegisterBuiltins();//注册程序中控制流程的语句,如ifelse,assert等
RegisterInstallFunctions();//实际安装过程中安装所需的功能函数
RegisterBlockImageFunctions();
RegisterDeviceExtensions();//与设备相关的额外添加项。
FinishRegistration();//结束注册
// Parse the script.//解析脚本文件
Expr* root;
int error_count = 0;
int error = parse_string(script, &root, &error_count);//解析脚本文件,解析的结果保存到root中
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");
}
// 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;
state.cookie = &updater_info;
state.script = script;
state.errmsg = NULL;
char* result = Evaluate(&state, root);//核心函数是Evaluate(),它会调用其他的callback函数,而这些callback函数又会去调用Evaluate去解析不同的脚本片段,从而实现一个简单的脚本解释器。
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) {
fprintf(cmd_pipe, "ui_print %s\n", line);
line = strtok(NULL, "\n");
}
fprintf(cmd_pipe, "ui_print\n");
}
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;
}
main函数的流程如下:
char* Evaluate(State* state, Expr* expr) {
Value* v = expr->fn(expr->name, state, expr->argc, expr->argv);
if (v == NULL) return NULL;
if (v->type != VAL_STRING) {
ErrorAbort(state, "expecting string, got value type %d", v->type);
FreeValue(v);
return NULL;
}
char* result = v->data;
free(v);
return result;
}
Evaluate()函数主要是调用了expr的fn()函数,参数expr的类型是Expr,定义如下:
struct Expr {
Function fn;
char* name;
int argc;
Expr** argv;
int start, end;
};
从Expr的定义中可以看到它有一个字段argv,这个字段是Expr指针的指针类型,它实际上会指向一个Expr指针的数组对象,表示Expr对象的所有下一级对象。通过这个字段,脚本解析后得到的所有命令都串接在一起,而且命令的执行函数还会调用Ecaluate()来继续执行argv中的Expr对象,因此,虽然Evaluate()中只调用了root对象的fn()函数,但是实际上会执行脚本中的所有命令。