• Recovery启动流程--recovery.cpp分析


    这篇文章主要通过分析高通recovery目录下的recovery.cpp源码,对recovery启动流程有一个宏观的了解。

    当开机以后,在lk阶段,如果是recovery,会设置boot_into_recovery=1,然后读取recovery.img镜像,把recovery.img的地址和ramdisk等信息作为参数启动kernel,从而进入recovery模式,下面进行简单的分析。

    为什么要分析recovery.cpp这个文件?

    下面的代码位于bootable/recovery/etc/init.rc,由此可知,进入recovery模式后会执行sbin /recovery,此文件是bootable/recovery/recovery.cpp生成(可查看对应目录的Android.mk查看),所以recovery.cpp是recovery模式的入口。

    service recovery /sbin/recovery
        seclabel u:r:recovery:s0
    

    1. 前期准备:

    首先列出recovery流程的几个重要点,接着会详细分析

    1. 加载recovery.fstab分区表
    2. 解析传入的参数
    3. recovery界面相关的设置
    4. 执行命令
    5. 如果没有命令,等待用户输入
    6. 结束recovery
    bootable/recovery/recovery.cpp
    
    
    
    int main(int argc, char **argv) {
        // 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);
    
        // 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.
        //如果二进制文件使用单个参数"--adbd"启动
        //而不是正常的recovery启动(不带参数即为正常启动)
        //它变成精简版命令时只支持sideload命令。它必须是一个正确可用的参数
        //不在/cache/recovery/command中,也不受B2B控制
        //是apply_from_adb()的副本
    
    
        if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
            adb_server_main(0, DEFAULT_ADB_PORT, -1);
            return 0;
        }
    
        time_t start = time(NULL);
    
        // 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));
    
        load_volume_table();
        
        //从上面建立的分区表信息中读取是否有cache分区,因为log等重要信息都存在cache分区里
    
        has_cache = volume_for_path(CACHE_ROOT) != nullptr;
        //从传入的参数或/cache/recovery/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 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 status = INSTALL_SUCCESS;
        bool mount_required = true;
    
        int arg;
        int option_index;
        
        
        //while循环解析command或者传入的参数,并把对应的功能设置为true或给相应的变量赋值
        while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) {
            switch (arg) {
            case 'i': send_intent = optarg; break;
            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 'g': {
                if (stage == NULL || *stage == '') {
                    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 'e': security_update = true; break;
            case 'y':
    			security_mode = atoi(optarg);
    			printf("security_mode is [%d]*****
    ", security_mode);
    			break;
            case 0: {
                if (strcmp(OPTIONS[option_index].name, "wipe_ab") == 0) {
                    should_wipe_ab = true;
                    break;
                } else if (strcmp(OPTIONS[option_index].name, "wipe_package_size") == 0) {
                    android::base::ParseUint(optarg, &wipe_package_size);
                    break;
                }
                break;
            }
            case '?':
                LOGE("Invalid command argument
    ");
                continue;
            }
        }
    
        if (locale == nullptr && has_cache) {
            load_locale_from_cache();
        }
        printf("locale is [%s]
    ", locale);
        printf("stage is [%s]
    ", stage);
        printf("reason is [%s]
    ", reason);
    
        Device* device = make_device();
        ui = device->GetUI();
        gCurrentUI = ui;
    
        ui->SetLocale(locale);
        ui->Init();
        // 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 != 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
    ");
        }
    
        device->StartRecovery();
    
        printf("Command:");
        for (arg = 0; arg < argc; arg++) {
            printf(" "%s"", argv[arg]);
        }
        printf("
    ");
    
        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);
                if (modified_path) {
                    strlcpy(modified_path, "/cache/", len);
                    strlcat(modified_path, update_package+6, len);
                    printf("(replacing path "%s" with "%s")
    ",
                           update_package, modified_path);
                    update_package = modified_path;
                }
                else
                    printf("modified_path allocation failed
    ");
            }
            if (!strncmp("/sdcard", update_package, 7)) {
                //If this is a UFS device lets mount the sdcard ourselves.Depending
                //on if the device is UFS or EMMC based the path to the sdcard
                //device changes so we cannot rely on the block dev path from
                //recovery.fstab
                if (is_ufs_dev()) {
                        if(do_sdcard_mount_for_ufs() != 0) {
                                status = INSTALL_ERROR;
                                goto error;
                        }
                        if (ensure_path_mounted("/cache") != 0 || ensure_path_mounted("/tmp") != 0) {
                                ui->Print("
    Failed to mount tmp/cache partition
    ");
                                status = INSTALL_ERROR;
                                goto error;
                        }
                        mount_required = false;
                } else {
                        ui->Print("Update via sdcard on EMMC dev. Using path from fstab
    ");
                }
            }
        }
        printf("
    ");
        property_list(print_property, NULL);
        property_get("ro.build.display.id", recovery_version, "");
        printf("
    ");
    
        /*if(check_identification_code() < 0){
    		identification_code = -1;
    		ui->Print("check_identification_code failed.
    ");
        }*/
        
        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;
    
            if (!is_battery_ok()) {
                ui->Print("battery capacity is not enough for installing package, needed is %d%%
    ",
                          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
    ");
                log_failure_code(kBootreasonInBlacklist, update_package);
                status = INSTALL_SKIPPED;
            } else {
                status = install_package(update_package, &should_wipe_cache,
                                         TEMPORARY_INSTALL_FILE, mount_required, retry_count);
                if (status == INSTALL_SUCCESS) {
                    ota_completed = true;
                }
                if (status == INSTALL_SUCCESS && should_wipe_cache) {
                    wipe_cache(false, device);
                }
                if (status != INSTALL_SUCCESS) {
                    ui->Print("Installation aborted.
    ");
                    // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
                    // times before we abandon this OTA update.
                    if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
                        copy_logs();
                        set_retry_bootloader_message(retry_count, argc, argv);
                        // Print retry count on screen.
                        ui->Print("Retry attempt %d
    ", retry_count);
    
                        // Reboot and retry the update
                        int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery");
                        if (ret < 0) {
                            ui->Print("Reboot failed
    ");
                        } 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);
                    }
                }
            }
        } else if (should_wipe_data) {
            if (!wipe_data(false, device)) {
                status = INSTALL_ERROR;
            }
        } 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(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
            if (status == INSTALL_SUCCESS) {
                ota_completed = true;
            }
            if (status == INSTALL_SUCCESS && should_wipe_cache) {
                if (!wipe_cache(false, device)) {
                    status = INSTALL_ERROR;
                }
            }
            ui->Print("
    Install from ADB complete (status: %d).
    ", status);
            if (sideload_auto_reboot) {
                ui->Print("Rebooting automatically.
    ");
            }
        } 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);
            }
        }
    error:
        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 && status != INSTALL_SKIPPED && !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...
    ");
                property_set(ANDROID_RB_PROPERTY, "shutdown,");
                break;
    
            case Device::REBOOT_BOOTLOADER:
                ui->Print("Rebooting to bootloader...
    ");
                property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
                break;
    
            default:
                ui->Print("Rebooting...
    ");
                property_set(ANDROID_RB_PROPERTY, "reboot,");
                break;
        }
        while (true) {
          pause();
        }
        // Should be unreachable.
        return EXIT_SUCCESS;
    }
    
    

    首先:

    1.1 启动adb进程

    启动adbd进程,为了使用adb sideload命令

    if (argc == 2 && strcmp(argv[1], "--adbd") == 0) 
    {
        adb_server_main(0, DEFAULT_ADB_PORT, -1);
            return 0;
    }
    

    1.2 重定向到recovery.log

    重定向标准输出和标准出错到/tmp/recovery.log 这个文件里

    redirect_stdio(TEMPORARY_LOG_FILE);
    

    1.3 装载分区表

    做完这些步骤以后,会初始化并装载recovery的分区表recovery.fstab

    void load_volume_table()
    {
        int i;
        int ret;
    
        fstab = fs_mgr_read_fstab("/etc/recovery.fstab");
        if (!fstab) {
            LOGE("failed to read /etc/recovery.fstab
    ");
            return;
        }
        //将对应的信息加入到一条链表中
        ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
        
        //如果load到的分区表为空,后面做释放操作
        if (ret < 0 ) {
            LOGE("failed to add /tmp entry to fstab
    ");
            fs_mgr_free_fstab(fstab);
            fstab = NULL;
            return;
        }
    
        printf("recovery filesystem table
    ");
        printf("=========================
    ");
        
        
        //到这一步,打印分区表信息,这类信息在
    	//recovery启动的时候的log可以看到
    	//分别是以下
    	//编号|   挂载节点|  文件系统类型|  块设备|   长度
        for (i = 0; i < fstab->num_entries; ++i) {
            Volume* v = &fstab->recs[i];
            printf("  %d %s %s %s %lld
    ", i, v->mount_point, v->fs_type,
                   v->blk_device, v->length);
        }
        printf("
    ");
    }
    

    这里主要看如何装载分区表的流程,先来看看recovery.fstab

    
    /dev/block/bootdevice/by-name/system       /system         ext4    ro,barrier=1                                                    wait
    /dev/block/bootdevice/by-name/cache        /cache          ext4    noatime,nosuid,nodev,barrier=1,data=ordered                     wait,check
    /dev/block/bootdevice/by-name/userdata     /data           ext4    noatime,nosuid,nodev,barrier=1,data=ordered,noauto_da_alloc     wait,check,length=-16384
    /dev/block/mmcblk1p1                       /sdcard         vfat    nosuid,nodev                                                    wait
    /dev/block/sda1                            /usbotg         vfat    nosuid,nodev                                                    wait
    /dev/block/bootdevice/by-name/boot         /boot           emmc    defaults                                                        defaults
    /dev/block/bootdevice/by-name/recovery     /recovery       emmc    defaults                                                        defaults
    /dev/block/bootdevice/by-name/misc         /misc           emmc    defaults                                                        defaults
    

    挂载完相应的分区以后,就需要获取命令参数,因为只有挂载了对应的分区,才能访问到前面要写入command的这个文件,这样我们才能正确的打开文件,如果分区都没找到,那么当然就找不到分区上的文件,上面这个步骤是至关重要的。

    //从上面建立的分区表信息中读取是否有cache分区,因为log等重要信息都存在cache分区里
    
    has_cache = volume_for_path(CACHE_ROOT) != nullptr;
    

    1.4 获取相应参数:

    从传入的参数或/cache/recovery/command文件中得到相应的命令

    get_args(&argc, &argv);             //从传入的参数或/cache/recovery/command文件中得到相应的命令
    

    while循环解析command或者传入的参数,并把对应的功能设置为true或给相应的变量赋值

    获取到对应的命令,就会执行对应的标志,后面会根据标志来执行对应的操作。

    while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {         //while循环解析command或者传入的参数,并把对应的功能设置为true或给相应的变量赋值
            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 == '') {
                    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
    ");
                continue;
            }
        }
    

    get_args()函数的主要作用是建立recovery的启动参数,如果系统启动recovery时已经传递了启动参数,那么这个函数只是把启动参数的内容复制到函数的参数boot对象中,否则函数会首先从/misc分区中获取命令字符串来构建启动参数。如果/misc分区下没有内容,则尝试打开/cache/recovery/command文件并读取文件的内容来建立启动参数。从这个函数我们可以看到,更新系统最简单的方式是把更新命令写到/cache/recovery/command文件中。get_args()函数是通过get_bootloader_message()函数来读取/misc分区的数据的

    get_args()函数是通过read_bootloader_message()函数来读取/misc分区的数据的,read_bootloader_message()函数的代码如下所示:

    read_bootloader_message  -->
        read_misc_partition  -->
            get_misc_blk_device
    
    static std::string get_misc_blk_device(std::string* err) {
      struct fstab* fstab = read_fstab(err);
      if (fstab == nullptr) {
        return "";
      }
      fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
      if (record == nullptr) {
        *err = "failed to find /misc partition";
        return "";
      }
      return record->blk_device;
    }
    

    从read_bootloader_message()函数的代码可以看到,它打开/misc分区来读取数据;

    get_args()函数的结尾调用了set_bootloader_message()函数,函数的作用是把启动参数的信息又保存到了/misc分区中。这样做的目的是防止升级过程中发生崩溃,这样重启后仍然可以从/misc分区中读取更新的命令,继续进行更新操作。这也是为什么get_args()函数要从几个地方读取启动参数的原因。

    1.5 load_locale_from_cache()函数

    load_locale_from_cache不展开说了,大致过程就是从之前解析分区表得到的fstab中查询/cache/recovery/last_locale文件是否存在,如果存在就读取里面的值

    /cache/recovery/last_locale关系到中文显示或者英文显示

    1.6 设置UI模型

    UI模型详情参考这篇文章:

    Android Recovery 源码UI 定制

    
    //创建设备
    Device* device = make_device();
    //获取UI
    ui = device->GetUI();
    //设置当前的UI
    gCurrentUI = ui;
    //设置UI的语言信息
    ui->SetLocale(locale);
    //UI初始化
    ui->Init();
    //这里会调用SetSystemUpdateText 方法把显示哪种文字的选择存在installing_text中,后面解析具体命令的时候会调用GetCurrentText来显示
    ui->SetSystemUpdateText(security_update);
    
    //设置界面上是否能够显示字符,使能ui->print函数开关
    if (show_text) ui->ShowText(true);
    //设置selinux权限,一般我会把selinux 给disabled
    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
    ");
    }
    
    if (!sehandle) {
        ui->Print("Warning: No file_contexts
    ");
    }
    
    //虚函数,没有做什么流程
    device->StartRecovery();
    
    printf("Command:");
    for (arg = 0; arg < argc; arg++) {
        printf(" "%s"", argv[arg]);
    }
    printf("
    ");
    
    

    2. 重要环节-升级

    2.1 Recovery界面升级

    
    //如果update_package(也就是要升级的OTA包)不为空的情况下
    //这里要对升级包的路径做一下路径转换,这里可以自由定制自己升级包的路径
    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".
            //这里就是做转换的方法
    	    //先比较传进来的recovery参数的前6个byte是否是CACHE
    	    //如果是将其路径转化为/cache/CACHE: ......
            if (strncmp(update_package, "CACHE:", 6) == 0) {
                int len = strlen(update_package) + 10;
                char* modified_path = (char*)malloc(len);
                if (modified_path) {
                    strlcpy(modified_path, "/cache/", len);
                    strlcat(modified_path, update_package+6, len);
                    printf("(replacing path "%s" with "%s")
    ",
                           update_package, modified_path);
                           
                    //这个update_package就是转换后的路径
                    update_package = modified_path;
                }
                else
                    printf("modified_path allocation failed
    ");
            }
            
            //这里修改为我们自己的/sdcard路径
            if (!strncmp("/sdcard", update_package, 7)) {
                //If this is a UFS device lets mount the sdcard ourselves.Depending
                //on if the device is UFS or EMMC based the path to the sdcard
                //device changes so we cannot rely on the block dev path from
                //recovery.fstab
                if (is_ufs_dev()) {
                        if(do_sdcard_mount_for_ufs() != 0) {
                                status = INSTALL_ERROR;
                                goto error;
                        }
                        if (ensure_path_mounted("/cache") != 0 || ensure_path_mounted("/tmp") != 0) {
                                ui->Print("
    Failed to mount tmp/cache partition
    ");
                                status = INSTALL_ERROR;
                                goto error;
                        }
                        mount_required = false;
                } else {
                        ui->Print("Update via sdcard on EMMC dev. Using path from fstab
    ");
                }
            }
        }
        
        printf("
    ");
        property_list(print_property, NULL);
        //获取属性,这里应该是从一个文件中找到ro.build.display.id
    	//获取recovery的版本信息
        property_get("ro.build.display.id", recovery_version, "");
        printf("
    ");
        
        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;
    
            if (!is_battery_ok()) {
                ui->Print("battery capacity is not enough for installing package, needed is %d%%
    ",
                          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
    ");
                log_failure_code(kBootreasonInBlacklist, update_package);
                status = INSTALL_SKIPPED;
            } else {
                status = install_package(update_package, &should_wipe_cache,
                                         TEMPORARY_INSTALL_FILE, mount_required, retry_count);
                if (status == INSTALL_SUCCESS) {
                    ota_completed = true;
                }
                if (status == INSTALL_SUCCESS && should_wipe_cache) {
                    wipe_cache(false, device);
                }
                if (status != INSTALL_SUCCESS) {
                    ui->Print("Installation aborted.
    ");
                    // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
                    // times before we abandon this OTA update.
                    if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
                        copy_logs();
                        set_retry_bootloader_message(retry_count, argc, argv);
                        // Print retry count on screen.
                        ui->Print("Retry attempt %d
    ", retry_count);
    
                        // Reboot and retry the update
                        int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery");
                        if (ret < 0) {
                            ui->Print("Reboot failed
    ");
                        } 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);
                    }
                }
            }
        }
        
    

    2.2 install_package函数

    我们来分析一波这个install_package函数:

    
    int
    install_package(const char* path, bool* wipe_cache, const char* install_file,
                    bool needs_mount, int retry_count)
    {
        modified_flash = true;
        auto start = std::chrono::system_clock::now();
    
        int result = 0;
        std::vector<std::string> log_buffer;
    
        timeout_exit_stop();
        
        if (needs_mount == true)
                result = setup_install_mounts();//确保/tmp和/cache分区已经mount 
        if (result != 0) {
            LOGE("failed to set up expected mounts for install; aborting
    ");
            result = INSTALL_ERROR;
        } else {
            //一般来到这里
            result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count);
        }
    
        // 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());
    
        if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
            LOGW("Can't mount %s
    ", UNCRYPT_STATUS);
        } else {
            std::string uncrypt_status;
            if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
                LOGW("failed to read uncrypt status: %s
    ", strerror(errno));
            } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
                LOGW("corrupted uncrypt_status: %s: %s
    ", uncrypt_status.c_str(), strerror(errno));
            } else {
                log_buffer.push_back(android::base::Trim(uncrypt_status));
            }
        }
    
        // 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),
        };
        std::string log_content = android::base::Join(log_header, "
    ") + "
    " +
                android::base::Join(log_buffer, "
    ");
        if (!android::base::WriteStringToFile(log_content, install_file)) {
            LOGE("failed to write %s: %s
    ", install_file, strerror(errno));
        }
    
        // Write a copy into last_log.
        LOGI("%s
    ", log_content.c_str());
            
        timeout_exit_start();
        
        return result;
    }
    
    

    我们来到really_install_package函数中:

    static int
    really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
                           std::vector<std::string>& log_buffer, int retry_count)
    {
        ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
        ui->Print("Finding update package...
    ");
        // Give verification half the progress bar...
        ui->SetProgressType(RecoveryUI::DETERMINATE);
        ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
        LOGI("Update location: %s
    ", path);
    
        // Map the update package into memory.
        ui->Print("Opening update package...
    ");
    
        if (path && needs_mount) {  //确保更新包所在的路径已经moun
            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
    ");
            return INSTALL_CORRUPT;
        }
    
        // Verify package.
        if (!verify_package(map.addr, map.length)) {
            log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
            sysReleaseMap(&map);
            return INSTALL_CORRUPT;
        }
    
        // Try to open the package.
        ZipArchive zip;
        int err = mzOpenZipArchive(map.addr, map.length, &zip);
        if (err != 0) {
            LOGE("Can't open %s
    (%s)
    ", path, err != -1 ? strerror(err) : "bad");
            log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
    
            sysReleaseMap(&map);
            return INSTALL_CORRUPT;
        }
    
        // Verify and install the contents of the package.
        ui->Print("Installing update...
    ");
        if (retry_count > 0) {
            ui->Print("Retry attempt: %d
    ", retry_count);
        }
        ui->SetEnableReboot(false);
        int result = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count);    //开始安装
        ui->SetEnableReboot(true);
        ui->Print("
    ");
    
        sysReleaseMap(&map);
    
    #ifdef USE_MDTP
        /* If MDTP update failed, return an error such that recovery will not finish. */
        if (result == INSTALL_SUCCESS) {
            if (!mdtp_update()) {
                ui->Print("Unable to verify integrity of /system for MDTP, update aborted.
    ");
                return INSTALL_ERROR;
            }
            ui->Print("Successfully verified integrity of /system for MDTP.
    ");
        }
    #endif /* USE_MDTP */
    
        return result;
    }
    
    

    注释如上;

    函数really_install_package会对升级包进行一系列的校验,通过校验后,调用try_update_binary函数完成升级。因此,try_update_binary()才是真正升级的地方。如下:

    2.3 真正升级的try_update_binary()函数

    try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
        const ZipEntry* binary_entry =                                     //在升级包中查找是否存在META-INF/com/google/android/update-binary文件
                mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
        if (binary_entry == NULL) {
            mzCloseZipArchive(zip);
            return INSTALL_CORRUPT;
        }
    
        const char* binary = "/tmp/update_binary";      //在tmp中创建临时文件夹,权限755
        unlink(binary);
        int fd = creat(binary, 0755);
        if (fd < 0) {
            mzCloseZipArchive(zip);
            LOGE("Can't make %s
    ", binary);
            return INSTALL_ERROR;
        }
        bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);     //把update.zip升级包解压到/tmp/update_binary文件夹中
        sync();
        close(fd);
        mzCloseZipArchive(zip);
    
        if (!ok) {
            LOGE("Can't copy %s
    ", 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;                                                     //[0]存放字符串 "/tmp/update_binary" ,也就是升级包解压的目的地址
        args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk    //[1]存放RECOVERY_API_VERSION,在Android.mk中定义,我的值为3  RECOVERY_API_VERSION := 3
        char* temp = (char*)malloc(10);
        sprintf(temp, "%d", pipefd[1]);
        args[2] = temp;
        args[3] = (char*)path;                                                //[3]存放update.zip路径
        args[4] = NULL;
    
        pid_t pid = fork();                                                   //创建一个新进程,为子进程
        if (pid == 0) {       //进程创建成功,执行META-INF/com/google/android/update-binary脚本,给脚本传入参数args
            umask(022);
            close(pipefd[0]);
            execv(binary, (char* const*)args);
            fprintf(stdout, "E:Can't run %s (%s)
    ", 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) {                    //父进程通过管道pipe读取子进程的值,使用strtok分割函数把子进程传过来的参数进行解析,执行相应的ui修改
            char* command = strtok(buffer, " 
    "); 
            if (command == NULL) {
                continue;
            } else if (strcmp(command, "progress") == 0) {
                char* fraction_s = strtok(NULL, " 
    ");
                char* seconds_s = strtok(NULL, " 
    ");
    
                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, " 
    ");
                float fraction = strtof(fraction_s, NULL);
                ui->SetProgress(fraction);
            } else if (strcmp(command, "ui_print") == 0) {
                char* str = strtok(NULL, "
    ");
                if (str) {
                    ui->Print("%s", str);
                } else {
                    ui->Print("
    ");
                }
                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]
    ", command);
            }
        }
        fclose(from_child);
    
        int status;
        waitpid(pid, &status, 0);
        if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
            LOGE("Error in %s
    (Status %d)
    ", path, WEXITSTATUS(status));
            return INSTALL_ERROR;
        }
    
        return INSTALL_SUCCESS;
    }
    

    try_update_binary流程:

    1. 查找META-INF/com/google/android/update-binary二进制脚本

    2. 解压update.zip包到/tmp/update_binary

    3. 创建子进程,执行update-binary二进制安装脚本,并通过管道与父进程通信,父进程更新ui界面。

    再看看之后的操作:

    if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
                        copy_logs();
                        set_retry_bootloader_message(retry_count, argc, argv);
                        // Print retry count on screen.
                        ui->Print("Retry attempt %d
    ", retry_count);
    
                        // Reboot and retry the update
                        int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery");
                        if (ret < 0) {
                            ui->Print("Reboot failed
    ");
                        } 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);
                    }
    

    再看看之后的操作:

    else if (should_wipe_data) {     //只清除用户数据
        if (!wipe_data(false, device)) {
            status = INSTALL_ERROR;
        }
    } else if (should_wipe_cache) {    //只清除缓存
        if (!wipe_cache(false, device)) {
            status = INSTALL_ERROR;
        }
    }
    else if (sideload) {//执行adb reboot 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(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
        if (status == INSTALL_SUCCESS) {
            ota_completed = true;
        }
        if (status == INSTALL_SUCCESS && should_wipe_cache) {
            if (!wipe_cache(false, device)) {
                status = INSTALL_ERROR;
            }
        }
        ui->Print("
    Install from ADB complete (status: %d).
    ", status);
        if (sideload_auto_reboot) {
            ui->Print("Rebooting automatically.
    ");
        }
    } 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);
      }
    

    2.4 死循环prompt_and_wait

    if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {   //安装失败,复制log信息到/cache/recovery/。如果进行了wipe_data/wipe_cache/apply_from_sdcard(也就是修改了flash),
    //直接return结束recovery,否则现实error背景图片
        copy_logs();
        ui->SetBackground(RecoveryUI::ERROR);
    }
    
    Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; 
    if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) {       //status在just_exit中已经变为none,会执行此if语句
    #ifdef SUPPORT_UTF8_MULTILINGUAL
        ml_select(device);
    #endif
        Device::BuiltinAction temp = prompt_and_wait(device, status);       //prompt_and_wait()函数是个死循环 开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如Reboot system等
        if (temp != Device::NO_ACTION) {
            after = temp;
        }
    }
    

    这里是根据你的按键去选择判断进入哪一个函数里面,进入那一段的升级里面;

    。。。。
                case Device::NO_ACTION:
                    break;
    
                case Device::REBOOT:
                case Device::SHUTDOWN:
                case Device::REBOOT_BOOTLOADER:
                    return chosen_action;
    
                case Device::WIPE_DATA:
                    timeout_exit_stop();
                    //wipe_data(ui->IsTextVisible(), device);
                    nexgo_wipe_data(ui->IsTextVisible(), device);
                    timeout_exit_start();
                    if (!ui->IsTextVisible()) return Device::NO_ACTION;
                    break;
    
                case Device::WIPE_CACHE:
                    timeout_exit_stop();
                    ui->Print("
    -- Wiping cache...
    ");
                    wipe_cache(ui->IsTextVisible(), device);
                    ui->Print("Cache wipe complete.
    ");
                    timeout_exit_start();
                    if (!ui->IsTextVisible()) return Device::NO_ACTION;
                    break;
    
                case Device::APPLY_ADB_SIDELOAD:
                case Device::APPLY_SDCARD:
                case Device::APPLY_USB: 
                    {
                        #if 0
                        bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
                        if (adb) {
                            status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
                        } else {
                            status = apply_from_sdcard(device, &should_wipe_cache);
                        }
                        #else
                        char *apply_names;
                        if (chosen_action == Device::APPLY_ADB_SIDELOAD) {
                            apply_names = "ADB";
                            status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
                        } 
                        else if(chosen_action == Device::APPLY_SDCARD){
                            apply_names = "SD card";
                            status = apply_from_sdcard(device, &should_wipe_cache);
                        }
                        else if(chosen_action == Device::APPLY_USB){
                            apply_names = "USB OTG";
                            status = apply_from_usbotg(device, &should_wipe_cache);
                        }
                        #endif
    
                        if (status == INSTALL_SUCCESS) {
                            ota_completed = true;
                        }
                        if (status == INSTALL_SUCCESS && should_wipe_cache) {
                            if (!wipe_cache(false, device)) {
                                status = INSTALL_ERROR;
                            }
                        }
    
                        if (status != INSTALL_SUCCESS) {
                            ui->SetBackground(RecoveryUI::ERROR);
                            ui->Print("Installation aborted.
    ");
                            copy_logs();
                        } else if (!ui->IsTextVisible()) {
                            return Device::NO_ACTION;  // reboot if logs aren't visible
                        } else {
                            ui->Print("
    Install from %s complete.
    ",apply_names);
                        }
                    }
                    break;
                /*case Device::APPLY_U_DISK:	
                	  {
    
                        status = apply_from_u_disk(device, &should_wipe_cache);
                        
    
                        if (status == INSTALL_SUCCESS) {
                            ota_completed = true;
                        }
                        if (status == INSTALL_SUCCESS && should_wipe_cache) {
                            if (!wipe_cache(false, device)) {
                                status = INSTALL_ERROR;
                            }
                        }
    
                        if (status != INSTALL_SUCCESS) {
                            ui->SetBackground(RecoveryUI::ERROR);
                            ui->Print("Installation aborted.
    ");
                            copy_logs();
                        } else if (!ui->IsTextVisible()) {
                            return Device::NO_ACTION;  // reboot if logs aren't visible
                        } else {
                            ui->Print("
    Install from U Disk complete.
    ");
                        }
                    }
                    break*/
                case Device::VIEW_RECOVERY_LOGS:
                    timeout_exit_stop();
                    choose_recovery_file(device);
                    timeout_exit_start();
                    break;
                #if 0
                case Device::RUN_GRAPHICS_TEST:
                    run_graphics_test(device);
                    break;
                //#endif
                case Device::MOUNT_SYSTEM:
                    {
    #ifdef USE_MDTP
                        if (is_mdtp_activated()) {
                            ui->Print("Mounting /system forbidden by MDTP.
    ");
                        }
                        else
    #endif
                        {
                            char system_root_image[PROPERTY_VALUE_MAX];
                            property_get("ro.build.system_root_image", system_root_image, "");
        
                            // For a system image built with the root directory (i.e.
                            // system_root_image == "true"), we mount it to /system_root, and symlink /system
                            // to /system_root/system to make adb shell work (the symlink is created through
                            // the build system).
                            // Bug: 22855115
                            if (strcmp(system_root_image, "true") == 0) {
                                if (ensure_path_mounted_at("/", "/system_root") != -1) {
                                    ui->Print("Mounted /system.
    ");
                                }
                            } else {
                                if (ensure_path_mounted("/system") != -1) {
                                    ui->Print("Mounted /system.
    ");
                                }
                            }
                        }
                        break;
    
                    }
            #endif
                case Device::SECURE_UNLOCK:
        		        {
            			    unlock_device();
                           	if (!ui->IsTextVisible()) return Device::NO_ACTION;
                           	security_mode = 0;
                           	error_code = 0;
            			    break;
        		        }
        		case Device::DOWNLOAD_SECURE_INFO:
            		{
            			printf("security_info_download=====
    ");
            			int ret = security_info_download();
            			if(ret == 0)
            			{
            				ui->Print("
    -- Wiping data...
    ");
        			    	device->PreWipeData();
        			    	erase_volume("/data");
        			    	erase_volume("/cache");
        			    	ui->Print("Data wipe complete.
    ");
            				need_wipe = 0;
            				return  Device::REBOOT;
            			}
            			if (!ui->IsTextVisible()) return Device::NO_ACTION;
            			security_mode = 0;
            			error_code = 0;
            			break;
            		}
        		case Device::DOWNLOAD_HWC_INFO: 
            		{
            			printf("hwc ---download
    ");
            			int ret = security_hwc_download();
            			if(ret == 0)
            			{
            				 return Device::REBOOT;
            				 break;
            			}
            			if (!ui->IsTextVisible()) 
            			    return Device::NO_ACTION;
            			security_mode = 0;
            			error_code = 0;
            			break;
            		}
        		case Device::APPLY_OTACONFIG_EXT: 
                    apply_from_config(device,SDCARD_ROOT);
                    break;
                
             case Device::APPLY_OTACONFIG_USB_PATH: 
                    /*int chonsen = factory_jump_usbpath(factory_jump_usbpath_flag,device);
                    factory_jump_usbpath_flag = 0;
                    if(chonsen == 0)
                    {
                        apply_from_path(device,USBOTG_ROOT);
                    }
                    else if(chonsen == 2)//进入download hwc
                    {
                        factory_chosen_item = 1;
                    }
                    break; */
                    {
                    int ret = apply_from_path(device,USBOTG_ROOT);
                    if((ret != 0) && (factory_jump_usbpath_flag == 1)){
                        factory_chosen_item = 1;
                    }
                    factory_jump_usbpath_flag = 0;
                    break;
                    }
    		case Device::APPLY_OTACONFIG_USB: 
    		    {
                   dictionary      *       ini ;
                   const char      * otafilename;
                   char otapathname[128];
                   int ret = 0,i;
                   char inipathname[256] = {0};
                   char section[20] =      {0};
                   char *prefixPath;
                   int status = 0;		
    			   APPLY_OTACONFIG_FLAG = 1;
    		        ui->Print("
    -- Install from %s ...
    ", USBOTG_ROOT);
                    ensure_path_mounted(USBOTG_ROOT);
                    char* path = browse_directory(USBOTG_ROOT, device);
                    if (path == NULL) {
                        ui->Print("
    -- No package file selected.
    ", path);
                        ensure_path_unmounted(USBOTG_ROOT);
                        break;
                    }
    
                    ui->Print("
    -- Install %s ...
    ", path);
    				set_sdcard_update_bootloader_message();
    				if(strncasecmp(&path[strlen(path)-4], ".ini", 4) == 0)
    				{
    				   snprintf(inipathname,256,"%s",path);
    				   ini = iniparser_load(inipathname);
    				   if (ini==NULL) {
    						   fprintf(stderr, "cannot parse file: %s
    ", inipathname);
    						   break;
    				   }
    				   prefixPath = strrchr(path, '/'); //<BB><F1>?ini?<U+05FA>??<BE><B6>path
    				   *prefixPath = '';
    				   ensure_path_unmounted(USBOTG_ROOT);
    				   for(i=1; i<10; i++)
    				   {
    						ensure_path_mounted(USBOTG_ROOT);
    						snprintf(section,20,"Path:OTApackage%d", i);
    						otafilename = iniparser_getstring(ini, section, NULL);  //<BB><F1>?ini<CE>?<FE><C3><FB>
    
    						if (otafilename)
    							   printf("Path:   [%s]
    ",otafilename );
    						else
    							   printf("Path%d:   [UNDEF]
    ", i);
    
    						if (!otafilename)
    							   continue;
    
    						strcpy(otapathname, path);
    						strcat(otapathname, "/");
    						strcat(otapathname, otafilename);  
    						ui->Print("
    -- otapathname = %s
    ", otapathname);
    						//void* token = start_usbotg_fuse(otapathname);
    						//status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache,
    						//finish_sdcard_fuse(token);ensure_path_unmounted(USBOTG_ROOT);
    						status = install_package_usb(otapathname, &should_wipe_cache);
    						if (status != INSTALL_SUCCESS) 
    							break;
    				   }
    				   iniparser_freedict(ini);
    				}
    				else
    				{
    
                        status = install_package_usb(path, &should_wipe_cache);
    				}
    				ensure_path_unmounted(USBOTG_ROOT);
    
                    if (status == INSTALL_SUCCESS && should_wipe_cache) {
                        ui->Print("
    -- Wiping cache (at package request)...
    ");
                        if (erase_volume("/cache")) {
                            ui->Print("Cache wipe failed.
    ");
                        } else {
                            ui->Print("Cache wipe complete.
    ");
                        }
                    }
    
                    if (status >= 0) {
                        if (status != INSTALL_SUCCESS) {
                            ui->SetBackground(RecoveryUI::ERROR);
                            ui->Print("Installation aborted.
    ");
                        } else if (!ui->IsTextVisible()) {
                            return Device::NO_ACTION;  // reboot if logs aren't visible
                        } else {
                            ui->Print("
    Install from sdcard complete.
    ");
                        }
                    }
                    break;
                }
                break;     
            }
    
  • 相关阅读:
    hdu2328 Corporate Identity
    hdu1238 Substrings
    hdu4300 Clairewd’s message
    hdu3336 Count the string
    hdu2597 Simpsons’ Hidden Talents
    poj3080 Blue Jeans
    poj2752 Seek the Name, Seek the Fame
    poj2406 Power Strings
    hust1010 The Minimum Length
    hdu1358 Period
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/11570830.html
Copyright © 2020-2023  润新知