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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值