Android 之 Recovery1

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值