1.Recovery 源码路径:
bootable/recovery
生成镜像:recovery.img {kernel, dtb, recvoery-ramdisk}
recovery.img中 kernel 和 dtb 跟boot.img中一致的,recovey-ramdisk和boot.img中ramdisk不完全一样。
2.recovery启动和工作流程概述
在Android系统中执行 reboot recovery命令可以重启进入到 recovery状态。uboot引导Android主系统和引导recovery系统是一样的原理,只是引导分区不一样而已。recovery系统引导起来后init会执行init.rc脚本,启动一个名字为 recovery 的服务程序。如下 init.rc摘抄:
service recovery /sbin/recovery
seclabel u:r:recovery:s0
recovery服务启动后,会解析输入的命令行参数,如果命令行没有输入参数如上图中就是没有给任何参数,recovery就会解析 /cache/recovery/comand 文件中的命令,并把解析的命令写入BCB(boot command block, msic分区)中。解析出更新包之后,找到更新包中的更新程序和更新脚本,开启子线程执行更新程序,更新动作开始,子进程向主进程报告更新进度和结果,主进程负责显示更新画面和更新进度条。
3.Recovery源码路径下的Android.mk
LOCAL_PATH := $(call my-dir)
##### 1
include $(CLEAR_VARS)
LOCAL_SRC_FILES := fuse_sideload.c
LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter
LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE
# 模块名称
LOCAL_MODULE := libfusesideload
# 模块依赖的静态库
LOCAL_STATIC_LIBRARIES := libcutils libc libmincrypt
# 此模块定义的目标为 静态库
include $(BUILD_STATIC_LIBRARY)
##### 2
include $(CLEAR_VARS)
LOCAL_SRC_FILES := ...... # 此处省略了一些源文件名
LOCAL_MODULE := recovery
LOCAL_FORCE_STATIC_EXECUTABLE := true
ifeq ($(HOST_OS), linux)
LOCAL_REQUIRED_MODULES := mkfs.f2fs
endif
RECOVERY_API_VERSION := 3
RECOVERY_FSTAB_VERSION := 3
LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_C_INCLUDES := ...... # 此处省略了头文件路径
LOCAL_STATIC_LIBRARIES := ...... # 此处省略了依赖的库文件名
ifeq ($(TARGET_USERIMAGES_USE_EXT4), true)
LOCAL_CFLAGS += -DUSE_EXT4
LOCAL_C_INCLUDES += system/extras/ext4_utils
LOCAL_STATIC_LIBRARIES += libext4_utils_static libz
endif
# 生成目标的安装的路径
LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
LOCAL_CFLAGS += -DUSE_UBIFS
LOCAL_C_INCLUDES += system/vold
LOCAL_SRC_FILES += ubi.cpp
LOCAL_REQUIRED_MODULES := ubiupdatevol
ifeq ( $(HAVE_SELINUX), true)
LOCAL_C_INCLUDES += external/libselinux/include
LOCAL_STATIC_LIBRARIES += libselinux
LOCAL_CFLAGS += -DHAVE_SELINUX
endif
LOCAL_MODULE_TAGS := tag
ifeq ($(TARGET_RECOVERY_UI_LIB),)
LOCAL_SRC_FILES += default_device.cpp
else
LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB)
endif
include $(BUILD_EXECUTABLE)
#####
include $(CLEAR_VARS)
LOCAL_MODULE := libverifier
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := asn1_decoder.cpp
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := verifier_test
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_MODULE_TAGS := tests
LOCAL_CFLAGS := -Wno-unused-parameter
LOCAL_SRC_FILES := verifier_test.cpp \
asn1_decoder.cpp \
verifier.cpp \
ui.cpp
LOCAL_STATIC_LIBRARIES := libmincrypt \
libminui \
libminzip \
libcutils \
libstdc++ \
libc
include $(BUILD_EXECUTABLE)
# 包含子目录下的mk文件,类似于linux的包含子目录的Makefile
include $(LOCAL_PATH)/minui/Android.mk \
$(LOCAL_PATH)/minzio/Android.mk \
$(LOCAL_PATH)/minadbd/Android.mk \
$(LOCAL_PATH)/mtdutils/Android.mk \
$(LOCAL_PATH)/tests/Android.mk \
$(LOCAL_PATH)/tools/Android.mk \
$(LOCAL_PATH)/edify/Android.mk \
$(LOCAL_PATH)/uncrypt/Android.mk \
$(LOCAL_PATH)/updater/Android.mk \
$(LOCAL_PATH)/applypatch/Android.mk
Android.mk文件中生成的目标有,1、静态库libfuseideload.a 2、可执行程序recovery 3、静态库libverifier.a 4、可执行程序verifier_test。其中,1和2为recovery主体程序,3和4为测试程序。可执行程序recovery的入口在recovery.cpp ==> main()
4.Recovery源码入口 recovery.cpp
int main(int argc, char **argv) {
time_t start = time(NULL);
/* 重定向输出到日志文件 */
redirect_stdio( TEMPORARY_LOG_FILE );
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
adb_main(0, DEFAULT_ADB_PORT);
return 0;
}
printf("Starting recovery (Pid %d) on %s", getpid(), ctime(&start));
load_volume_table();
/* 解析参数,如果命令行有参数就解析命令行参数,如果没有就解析command文件 */
get_args(&argc, &argv);
const char *send_intent = NULL;
const char *update_package = NULL;
bool should_wipe_data = false;
bool should_wipe_cache = false;
bool show_text = false;
bool sideload = false;
bool sideload_auto_reboot = false;
bool just_exit = false;
bool shutdown_after = false;
int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
case 'i': send_intent = optarg; 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 'g': {
if (stage == NULL || *stage == '\0') {
char buffer[20] = "1/";
strncat(buffer, optarg, sizeof(buffer)-3);
stage = strdup(buffer);
}
break;
}
case 'p': shutdown_after = true; break;
case 'r': reason = optarg; break;
case '?':
LOGE("Invalid command argument\n");
continue;
}
}
if (locale == NULL) {
load_locale_from_cache();
}
printf("locale is [%s]\n", locale);
printf("stage is [%s]\n", stage);
printf("reason is [%s]\n", reason);
Device* device = make_device();
ui = device->GetUI();
gCurrentUI = ui;
ui->SetLocale(locale);
ui->Init();
int st_cur, st_max;
if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) {
ui->SetStage(st_cur, st_max);
}
ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);
struct selinux_opt seopts[] = {
{ SELABEL_OPT_PATH, "/file_contexts" }
};
sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
device->StartRecovery();
printf("Command:");
for (arg = 0; arg < argc; arg++) {
printf(" \"%s\"", argv[arg]);
}
printf("\n");
if (update_package) {
// For backwards compatibility on the cache partition only, if
// we're given an old 'root' path "CACHE:foo", change it to
// "/cache/foo".
if (strncmp(update_package, "CACHE:", 6) == 0) {
int len = strlen(update_package) + 10;
char* modified_path = (char*)malloc(len);
strlcpy(modified_path, "/cache/", len);
strlcat(modified_path, update_package+6, len);
printf("(replacing path \"%s\" with \"%s\")\n",
update_package, modified_path);
update_package = modified_path;
}
}
printf("\n");
property_list(print_property, NULL);
printf("\n");
ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);
int status = INSTALL_SUCCESS;
if (update_package != NULL) {
/* 如果更新包存在的话 就安装更新包 */
status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
// 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 (should_wipe_data) {
/* 擦除data分区 */
if (!wipe_data(false, device)) {
status = INSTALL_ERROR;
}
} else if (should_wipe_cache) {
/* 擦除cache分区 */
if (!wipe_cache(false, device)) {
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);
}
/* 从adb获取更新 */
status = apply_from_adb(ui, &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) {
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);
// http://b/17489952
// If this is an eng or userdebug build, automatically turn on the
// text display if no command is specified.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {
copy_logs();
ui->SetBackground(RecoveryUI::ERROR);
}
Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) {
/* 如果更新失败 会进入手动交互界面 并等待用户输入 */
Device::BuiltinAction temp = prompt_and_wait(device, status);
if (temp != Device::NO_ACTION) {
after = temp;
}
}
// Save logs and clean up before rebooting or shutting down.
finish_recovery(send_intent);
switch (after) {
case Device::SHUTDOWN:
ui->Print("Shutting down...\n");
property_set(ANDROID_RB_PROPERTY, "shutdown,");
break;
case Device::REBOOT_BOOTLOADER:
ui->Print("Rebooting to bootloader...\n");
property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
break;
default:
ui->Print("Rebooting...\n");
property_set(ANDROID_RB_PROPERTY, "reboot,");
break;
}
sleep(5); // should reboot before this finishes
return EXIT_SUCCESS
}
5.重点分析 install_package
1、install_package函数源码路径:bootable/recovery/install.cpp
int
install_package(const char* path, bool* wipe_cache, const char* install_file,
bool needs_mount)
{
modified_flash = true;
/* 打开日志文件 */
FILE* install_log = fopen_path(install_file, "w");
if (install_log) {
fputs(path, install_log);
fputc('\n', install_log);
} else {
LOGE("failed to open last_install: %s\n", strerror(errno));
}
int result;
/* 保证fstab中 /cache分区 或 /tmp已经挂载,并且其他分区被卸载 */
if (setup_install_mounts() != 0) {
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;
}
2、really_install_package函数源码路径:bootable/recovery/install.cpp
really_install_package的工作是校验安装包文件和签名文件。然后调用try_update_binary函数。
static int
really_install_package(const char *path, bool* 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) {
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;
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;
}
3、try_update_binary函数源码路径:bootable/recovery/install.cpp
try_update_binary函数的工作是在安装压缩包找到META-INF/com/google/android/update-binary文件,解压该文件,并创建一个子进程执行update-binary, 子进程和父进程通过管道通信,获取更新的动态和结果。这个可执行文件会按照脚本 META-INF/com/google/android/updater-script 中的命令进行安装更新。
static int
try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
const ZipEntry* binary_entry =
mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
if (binary_entry == NULL) {
mzCloseZipArchive(zip);
return INSTALL_CORRUPT;
}
const char* binary = "/tmp/update_binary";
unlink(binary);
int fd = creat(binary, 0755);
if (fd < 0) {
mzCloseZipArchive(zip);
LOGE("Can't make %s\n", binary);
return INSTALL_ERROR;
}
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
close(fd);
mzCloseZipArchive(zip);
if (!ok) {
LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
return INSTALL_ERROR;
}
int pipefd[2];
pipe(pipefd);
// 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 <frac> <secs>
// fill up the next <frac> part of of the progress bar
// over <secs> seconds. If <secs> is zero, use
// set_progress commands to manually control the
// progress of this segment of the bar.
//
// set_progress <frac>
// <frac> 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"> <filename>
// arrange to install the contents of <filename> in the
// given partition on reboot.
//
// (API v2: <filename> may start with "PACKAGE:" to
// indicate taking a file from the OTA package.)
//
// (API v3: this command no longer exists.)
//
// ui_print <string>
// display <string> 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.
//
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;
/* fork出子进程,子进程负责执行安装动作并把状态和结果告诉父进程 */
pid_t pid = fork();
if (pid == 0) {
umask(022);
close(pipefd[0]);
execv(binary, (char* const*)args);
fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
_exit(-1);
}
close(pipefd[1]);
*wipe_cache = false;
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->Print("%s", str);
} else {
ui->Print("\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 {
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;
}