recovery代碼分析(一)

以下分析代碼基於mtk 8167a的sdk,android版本是8.0.1。代碼的分析直接看以Timothy開頭的註釋。

main()函數

main()函數的位置是recovery.cpp。

int main(int argc, char **argv) {
	// Timothy:初始化日誌。因爲recovery裏面沒有logcat,所以會將關鍵信息打印在屏幕上,日誌寫到文件裏。
    // We don't have logcat yet under recovery; so we'll print error on screen and
    // log to stdout (which is redirected to recovery.log) as we used to do.
    android::base::InitLogging(argv, &UiLogger);

    // Take last pmsg contents and rewrite it to the current pmsg session.
    static const char filter[] = "recovery/";
    // Do we need to rotate?
    bool doRotate = false;

    __android_log_pmsg_file_read(
        LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
        logbasename, &doRotate);
    // Take action to refresh pmsg contents
    __android_log_pmsg_file_read(
        LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,
        logrotate, &doRotate);

	// Timothy:這部分是adb reboot side_load命令進入recovery後走的分支,目前不關注。
    // If this binary is started with the single argument "--adbd",
    // instead of being the normal recovery binary, it turns into kind
    // of a stripped-down version of adbd that only supports the
    // 'sideload' command.  Note this must be a real argument, not
    // anything in the command file or bootloader control block; the
    // only way recovery should be run with this argument is when it
    // starts a copy of itself from the apply_from_adb() function.
    if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
        minadbd_main();
        return 0;
    }

    time_t start = time(NULL);

	//Timothy:重定向標準輸出,TEMPORARY_LOG_FILE這個宏的定義是/tmp/recovery.log。
    // redirect_stdio should be called only in non-sideload mode. Otherwise
    // we may have two logger instances with different timestamps.
    redirect_stdio(TEMPORARY_LOG_FILE);

    printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));

	// Timothy:這一部分是掛載必要的分區。
    load_volume_table();
    has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
    has_nvdata = volume_for_mount_point(NVDATA_ROOT) != nullptr;

    mt_init_partition_type();

	// Timothy:這一部分是解析啓動recovery的參數,例如是否是ota升級、是否清除cache分區、
	// 是否清除data分區等等。
	// 另外還有個關鍵信息是本地化信息locale,這決定了界面上顯示的語言。
    std::vector<std::string> args = get_args(argc, argv);
    std::vector<char*> args_to_parse(args.size());
    std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),
                   [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });

    const char *update_package = NULL;
    bool should_wipe_data = false;
    bool should_prompt_and_wipe_data = false;
    bool should_wipe_cache = false;
    bool should_wipe_ab = false;
    size_t wipe_package_size = 0;
    bool show_text = false;
    bool sideload = false;
    bool sideload_auto_reboot = false;
    bool just_exit = false;
    bool shutdown_after = false;
    int retry_count = 0;
    bool security_update = false;

    int arg;
    int option_index;
    while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,
                              &option_index)) != -1) {
        switch (arg) {
        case 'n': android::base::ParseInt(optarg, &retry_count, 0); 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 'p': shutdown_after = true; break;
        case 'r': reason = optarg; break;
        case 'e': security_update = true; break;
        case 0: {
            std::string option = OPTIONS[option_index].name;
            if (option == "wipe_ab") {
                should_wipe_ab = true;
            } else if (option == "wipe_package_size") {
                android::base::ParseUint(optarg, &wipe_package_size);
            } else if (option == "prompt_and_wipe_data") {
                should_prompt_and_wipe_data = true;
            }
            break;
        }
        case '?':
            LOG(ERROR) << "Invalid command argument";
            continue;
        }
    }

    if (locale.empty()) {
        if (has_cache) {
            locale = load_locale_from_cache();
        }

        if (locale.empty()) {
            locale = DEFAULT_LOCALE;
        }
    }

    printf("locale is [%s]\n", locale.c_str());
    printf("stage is [%s]\n", stage.c_str());
    printf("reason is [%s]\n", reason);

	// Timothy:這部分是初始化顯示的界面,暫時不關注。
    Device* device = make_device();
    if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
        printf("Quiescent recovery mode.\n");
        ui = new StubRecoveryUI();
    } else {
        ui = device->GetUI();

        if (!ui->Init(locale)) {
            printf("Failed to initialize UI, use stub UI instead.\n");
            ui = new StubRecoveryUI();
        }
    }

    // Set background string to "installing security update" for security update,
    // otherwise set it to "installing system update".
    ui->SetSystemUpdateText(security_update);

    int st_cur, st_max;
    if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) {
        ui->SetStage(st_cur, st_max);
    }

    ui->SetBackground(RecoveryUI::NONE);
    if (show_text) ui->ShowText(true);

	// Timothy:這部分是跟selinux相關的,暫時不關注。
    sehandle = selinux_android_file_context_handle();
    selinux_android_set_sehandle(sehandle);
    if (!sehandle) {
        ui->Print("Warning: No file_contexts\n");
    }

	// Timothy:根據註釋,這個函數時在recovery啓動時調用。調用的時機是UI完成初始化,參數已經解析完畢,
	// 但是實際的升級尚未開始。	
    device->StartRecovery();

    printf("Command:");
    for (const auto& arg : args) {
        printf(" \"%s\"", arg.c_str());
    }
    printf("\n\n");

	// Timothy:這裏把系統裏所有的屬性都打印出來了。既然這裏能讀屬性,所以猜測在recovery裏面也能寫屬性。
	// 那麼recovery和正常啓動的Android應該能夠通過property來傳遞一些值。
    property_list(print_property, NULL);
    printf("\n");

    ui->Print("Supported API: %d\n", RECOVERY_API_VERSION);
    fprintf(stdout, "update_package = %s\n", update_package ? update_package : "NULL");

    int status = INSTALL_SUCCESS;

    if (update_package != NULL) {
        // It's not entirely true that we will modify the flash. But we want
        // to log the update attempt since update_package is non-NULL.
        modified_flash = true;

		// Timothy:這裏檢查電量,如果低於BATTERY_OK_PERCENTAGE就不升級,直接跳過。
		// BATTERY_OK_PERCENTAGE這個宏的定義是20
        if (!is_battery_ok()) {
            ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
                      BATTERY_OK_PERCENTAGE);
            // Log the error code to last_install when installation skips due to
            // low battery.
            log_failure_code(kLowBattery, update_package);
            status = INSTALL_SKIPPED;
        } else if (bootreason_in_blacklist()) {
            // Skip update-on-reboot when bootreason is kernel_panic or similar
            ui->Print("bootreason is in the blacklist; skip OTA installation\n");
            log_failure_code(kBootreasonInBlacklist, update_package);
            status = INSTALL_SKIPPED;
        } else {
			// Timothy:這裏執行實際的安裝升級包
            status = install_package(update_package, &should_wipe_cache,
                                     TEMPORARY_INSTALL_FILE, true, retry_count);
			// Timothy:如果升級成功,而且傳進來的command包含清除cache分區,就清除cache分區。
            if (status == INSTALL_SUCCESS && should_wipe_cache) {
                wipe_cache(false, device);
            }
			// Timothy:如果升級失敗,就在屏幕上打印升級失敗,如果重試次數沒到上線,將重試。
            if (status != INSTALL_SUCCESS) {
                ui->Print("Installation aborted.\n");
                // When I/O error happens, reboot and retry installation RETRY_LIMIT
                // times before we abandon this OTA update.
                if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) {
                    copy_logs();
                    set_retry_bootloader_message(retry_count, argc, argv);
                    // Print retry count on screen.
                    ui->Print("Retry attempt %d\n", retry_count);

                    // Reboot and retry the update
                    if (!reboot("reboot,recovery")) {
                        ui->Print("Reboot failed\n");
                    } else {
                        while (true) {
                            pause();
                        }
                    }
                }
                // 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);
                }
            }
        }
	// Timothy:這後面就是除了升級以外的其他命令了。例如恢復出廠設置之類的。
    } else if (should_wipe_data) {
        if (!wipe_data(device)) {
            status = INSTALL_ERROR;
        }
    } else if (should_prompt_and_wipe_data) {
        ui->ShowText(true);
        ui->SetBackground(RecoveryUI::ERROR);
        if (!prompt_and_wipe_data(device)) {
            status = INSTALL_ERROR;
        }
        ui->ShowText(false);
    } else if (should_wipe_cache) {
        if (!wipe_cache(false, device)) {
            status = INSTALL_ERROR;
        }
    } else if (should_wipe_ab) {
        if (!wipe_ab_device(wipe_package_size)) {
            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);
        }
        status = apply_from_adb(&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) {
      // If this is an eng or userdebug build, automatically turn on the text display if no command
      // is specified. Note that this should be called before setting the background to avoid
      // flickering the background image.
      if (is_ro_debuggable()) {
        ui->ShowText(true);
      }
      status = INSTALL_NONE;  // No command specified
	  ui->ShowText(true);
      ui->SetBackground(RecoveryUI::NO_COMMAND);
    }

	// Timothy:在這個函數裏面,如果升級成功,將刪除cache分區下的ota包。然後裏面有個mtk加的操作,
	// 調用一個函數將結果寫入data分區下的一個文件。但是這個函數實際上並沒有寫,很費解。
    mt_main_write_result(status, update_package);
    if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
        ui->SetBackground(RecoveryUI::ERROR);
		ui->ShowText(true);
        if (!ui->IsTextVisible()) {
            sleep(5);
        }
    }

	// Timothy:這裏的註釋解釋了一個我們曾經遇到的問題。在ota升級的過程中,如果按了音量鍵,將會顯示
	// 一個打印了升級過程的文字界面,而不是默認的進度條界面。在這個界面出現之後,升級成功將不會自動
	// 重啓,而是等待用戶的操作。也就是註釋中的第一種情況。
    Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;
    // 1. If the recovery menu is visible, prompt and wait for commands.
    // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into
    //    recovery to sideload a package.)
    // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device
    //    without waiting.
    // 4. In all other cases, reboot the device. Therefore, normal users will observe the device
    //    reboot after it shows the "error" screen for 5s.
    if ((status == INSTALL_NONE && !sideload_auto_reboot) || ui->IsTextVisible()) {
        Device::BuiltinAction temp = prompt_and_wait(device, status);
        if (temp != Device::NO_ACTION) {
            after = temp;
        }
    }

	// Timothy:這個函數的作用是清除cache分區下的command文件,準備重啓,
	// 並將日誌文件拷貝到cache/recovery下面。
    // Save logs and clean up before rebooting or shutting down.
    finish_recovery();

	// Timothy:我查了一下,ANDROID_RB_PROPERTY這個宏的定義是sys.powerctl,
	// 用這個屬性可以進行關機、重啓等操作,在正常的adb shell裏面也可以用。
	// property_set(ANDROID_RB_PROPERTY, "shutdown,");
	// property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
	// property_set(ANDROID_RB_PROPERTY, "reboot,");
	// property_set(ANDROID_RB_PROPERTY, "reboot,edl");
    switch (after) {
        case Device::SHUTDOWN:
            ui->Print("Shutting down...\n");
            android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
            break;

        case Device::REBOOT_BOOTLOADER:
            ui->Print("Rebooting to bootloader...\n");
            android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
            break;

        default:
            ui->Print("Rebooting...\n");
            reboot("reboot,");
            break;
    }
    while (true) {
        pause();
    }
    // Should be unreachable.
    return EXIT_SUCCESS;
}

install_package()函數

install_package()函數的位置是install.cpp。

int install_package(const std::string &path, bool *wipe_cache, const std::string &install_file,
                    bool needs_mount, int retry_count) {
    // Timothy:檢查參數是否爲空
    CHECK(!path.empty());
    CHECK(!install_file.empty());
    CHECK(wipe_cache != nullptr);

    modified_flash = true;
    // Timothy:記錄升級的開始時間,後面要計算升級所用時長。
    auto start = std::chrono::system_clock::now();

    // Timothy:記錄開始升級前的溫度。
    int start_temperature = GetMaxValueFromThermalZone();
    int max_temperature = start_temperature;

    int result;
    std::vector<std::string> log_buffer;
    if (setup_install_mounts() != 0) { // Timothy:檢查tmp分區和cache分區是否掛載
        LOG(ERROR) << "failed to set up expected mounts for install; aborting";
        result = INSTALL_ERROR;
    } else {
        // Timothy:這裏進行實際的安裝
        result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count,
                                        &max_temperature);
    }

    // Measure the time spent to apply OTA update in seconds.
    std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
    int time_total = static_cast<int>(duration.count());

    // Timothy:這部分跟加密分區有關,暫時不關注。
    bool has_cache = volume_for_mount_point("/cache") != nullptr;
    // Skip logging the uncrypt_status on devices without /cache.
    if (has_cache) {
        static constexpr const char *UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
        if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
            LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS;
        } else {
            std::string uncrypt_status;
            if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
                PLOG(WARNING) << "failed to read uncrypt status";
            } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
                LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status;
            } else {
                log_buffer.push_back(android::base::Trim(uncrypt_status));
            }
        }
    }

	// Timothy:這部分打印了一些日誌。
    // The first two lines need to be the package name and install result.
    std::vector<std::string> log_header = {
        path,
        result == INSTALL_SUCCESS ? "1" : "0",
        "time_total: " + std::to_string(time_total),
        "retry: " + std::to_string(retry_count),
    };

    int end_temperature = GetMaxValueFromThermalZone();
    max_temperature = std::max(end_temperature, max_temperature);
    if (start_temperature > 0) {
        log_buffer.push_back("temperature_start: " + std::to_string(start_temperature));
    }
    if (end_temperature > 0) {
        log_buffer.push_back("temperature_end: " + std::to_string(end_temperature));
    }
    if (max_temperature > 0) {
        log_buffer.push_back("temperature_max: " + std::to_string(max_temperature));
    }

    std::string log_content =
        android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n";
    if (!android::base::WriteStringToFile(log_content, install_file)) {
        PLOG(ERROR) << "failed to write " << install_file;
    }

    // Write a copy into last_log.
    LOG(INFO) << log_content;

    return result;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章