天天看點

Recovery代碼分析之一

        在android系統的手機啟動時,按下 (音量下+power) 組合鍵(大多數如此,也有例外)可進入recovery模式。此recovery模式一個重要的功能便是進行系統更新,這是OTA功能實作的基礎和關鍵。由于前段時間一直在進行OTA項目的開發,是以将recovery模式下代碼分析整理出來,以備不時之須。

        recovery模式的代碼存在于源碼目錄下的bootable目錄中,主函數位于./bootable/recovery/recovery.c檔案中。我們将主函數的代碼複制如下,并對其中核心代碼進行分析。

int
main(int argc, char **argv) {
    time_t start = time(NULL);

    // If these fail, there's not really anywhere to complain...
    freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
    freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
    printf("Starting recovery on %s", ctime(&start));

    device_ui_init(&ui_parameters);
    ui_init();
    ui_set_background(BACKGROUND_ICON_INSTALLING);
    load_volume_table();    //加載系統分區表
    get_args(&argc, &argv);	    //擷取參數資訊,包括需要執行的動作(更新、清除/data、清除cache等)及更新包路徑

    int previous_runs = 0;
    const char *send_intent = NULL;    //通過finish_recovery函數傳遞到main system下的資訊
    const char *update_package = NULL;    //更新包路徑
    int wipe_data = 0, wipe_cache = 0;

    int arg;
    while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
        switch (arg) {
        case 'p': previous_runs = atoi(optarg); break;
        case 's': send_intent = optarg; break;
        case 'u': update_package = optarg; break;    //解析獲得更新包路徑
        case 'w': wipe_data = wipe_cache = 1; break;
        case 'c': wipe_cache = 1; break;
        case 't': ui_show_text(1); break;
        case '?':
            LOGE("Invalid command argument\n");
            continue;
        }
    }

#ifdef HAVE_SELINUX
    struct selinux_opt seopts[] = {
      { SELABEL_OPT_PATH, "/file_contexts" }
    };

    sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);

    if (!sehandle) {
        fprintf(stderr, "Warning: No file_contexts\n");
        ui_print("Warning:  No file_contexts\n");
    }
#endif

    device_recovery_start();

    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 = 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");

    int status = INSTALL_SUCCESS;

    if (update_package != NULL) {
        status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);
        if (status == INSTALL_SUCCESS && wipe_cache) {
            if (erase_volume("/cache")) {
                LOGE("Cache wipe (requested by package) failed.");
            }
        }
        if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n");
    } else if (wipe_data) {
        if (device_wipe_data()) status = INSTALL_ERROR;
        if (erase_volume("/data")) status = INSTALL_ERROR;
        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
        if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n");
    } else if (wipe_cache) {
        if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
        if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed.\n");
    } else {
        status = INSTALL_ERROR;  // No command specified
    }

    if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
    if (status != INSTALL_SUCCESS || ui_text_visible()) {
        prompt_and_wait();
    }

    // Otherwise, get ready to boot the main system...
    finish_recovery(send_intent);
    ui_print("Rebooting...\n");
    android_reboot(ANDROID_RB_RESTART, 0, 0);
    return EXIT_SUCCESS;
}
           

代碼段1 ./bootable/recovery/recovery.c檔案中的main函數,即系統進入到recovery模式執行的主流程

        代碼分析如下:

代碼06-08:打開臨時日志檔案TEMPORARY_LOG_FILE(定義在recovery.c的開始部分),并寫入資訊。注意:此檔案裡的資訊最終會被寫入到日志檔案LAST_LOG_FILE

                       中,LAST_LOG_FILE的定義如下:static const char *LAST_LOG_FILE = "/cache/recovery/last_log";這就意味着可以獲得系統更新時詳細的LOG資訊,這對于

                      OTA項目的開發具有相當重要的作用;

代碼10-12:UI相關的操作,未研究,略去;

代碼      13:加載recovery模式下系統分區資訊,由于需要對某些分區進行讀寫操作,故需此步。此分區資訊以檔案形式存在,路徑為:./device/<生産廠商>/<産品型号>/

                       recovery.fstab;比如三星crespo4g系列此檔案在源碼下的全路徑為:./device/samsung/crespo4g/recovery.fstab,其内容如下所示:

# mount point	fstype		device

/sdcard		vfat		/dev/block/platform/s3c-sdhci.0/by-name/media
/system		ext4		/dev/block/platform/s3c-sdhci.0/by-name/system
/cache		yaffs2		cache
/data		ext4		/dev/block/platform/s3c-sdhci.0/by-name/userdata
/misc 		mtd 		misc
/boot		mtd		boot
/recovery	mtd		recovery
/bootloader	mtd		bootloader
/radio		mtd		radio
           

代碼段2 recovery.fstab示例

代碼      14:調用get_args()擷取由正常模式下傳入到recovery模式下的參數,這些參數包括執行動作(系統更新、清空data分區、清空cache分區等)及更新包路徑,擷取來源為

                       BCB(結構體bootloader_message)或COMMAND_FILE(/cache/recovery/command),詳細資訊将在後文介紹;

代碼21-34:解析get_args所獲得的參數,如果是在進行OTA操作,我們将通過第26行擷取更新包的路徑update_package;

代碼57-93中,系統将根據不同的指令進行不同的操作,包括更新、清除/data分區、清除/cache分區等。

代碼57-70:對update_package的預處理,本人所遇到的情況是:在main system傳入的路徑為/mnt/sdcard/update.zip,而在recovery下需将其轉換為/sdcard/update.zip,相

                      關的操作便在此處進行(當然本文使用的為android源碼,并未加入自己改動的代碼);

代碼78-85:更新操作。其實核心代碼隻有一句,第79行調用install_package函數将更新包中的資料寫入到系統相應分區中,我們将在後文中詳細介紹此函數;

代碼86-90:清空/data分區(貌似就是恢複出廠設定吧?我沒詳細研究,不敢妄言);

代碼91-93:清空/cache分區;

代碼99-101:當更新失敗或者我們是手動進入recovery模式時,将調用prompt_and_wait(),顯示recovery模式下的互動界面,我們在後文中再詳細介紹;

代碼   104: 調用finish_recovery函數清空BCB塊(防止重複進入recovery模式)、将send_intent寫入檔案(傳遞到main system)、将日志寫入到LAST_LOG_FILE中等;

代碼   106: 系統重新開機。

        以上即系統進入recovery模式下的執行流程,其中涉及到的關鍵步驟:如何擷取參數(get_args()),系統更新(install_package())及互動界面的顯示與操作(prompt_and_w-

ait())等将在下一篇文章中進行分析。