recovery代码分析之二

1.函数get_args()

        首先考虑如下情形:在main system下,我们通过OTA客户端(自己编写)选择升级包update.zip(假设存放于/mnt/sdcard中)后进入recovery模式下进行升级。在这一过程中,需要解决两个问题,第一:如果使机器重启并自动进入recovery模式;第二:如何将升级包的路径/mnt/sdcard/update.zip传入到recovery模式,使其能查找到升级包。这涉及到android系统的启动流程,如下图所示:

图1 系统启动流程

        机器启动时,首先检测是否有组合键按下,如检测到(音量下+power)组合键,则进入recovery;否则检测系统的/misc分区,根据此分区存储的命令选择不同的模式。

/misc分区下存储着结构体bootloader_message,称之为BCB块,其定义如下:

struct bootloader_message{

      char command[32];    //存放不同的启动命令

      char status[32];     //存放执行结果

      char recovery[1024];   //存放/cache/recovery/command中的命令

 };

        结构体成员command[32]中存放着不同的启动命令:

  •         boot-recovery:系统会进入Recovery模式
  •         update-radiaupdate-hboot
  •         command为空:正常启动,进入main system

        而recovery[1024]中则存放着升级包路径,其存储结构如下:第一行存放字符串“recovery”;第二行存放路径信息“--update=/mnt/sdcard/update.zip”等。

        除了BCB块外,还可以将路径信息--update=/mnt/sdcard/update.zip写入文件/cache/recovery/command传递给recovery模式。

        进入recovery模式,系统通过get_args函数(如下代码所示)获取升级包信息。此函数首先获取BCB块信息(代码07-34),如果未检测到相关信息,则继续检测/cache/

recovery/command文件(代码36-53);最后,将启动命令boot-recovery及升级包路径--update=/mnt/sdcard/update.zip重新写入到BCB块中(代码55-64),以便系统下次启动时再次进入到recovery模式,直到升级成功后执行finish_recovery函数清空BCB及/cache/recovery/command文件。

// command line args come from, in decreasing precedence:
//   - the actual command line
//   - the bootloader control block (one per line, after "recovery")
//   - the contents of COMMAND_FILE (one per line)
static void
get_args(int *argc, char ***argv) {
    struct bootloader_message boot;
    memset(&boot, 0, sizeof(boot));
    get_bootloader_message(&boot);  // this may fail, leaving a zeroed structure

    if (boot.command[0] != 0 && boot.command[0] != 255) {
        LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
    }

    if (boot.status[0] != 0 && boot.status[0] != 255) {
        LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
    }

    // --- if arguments weren't supplied, look in the bootloader control block
    if (*argc <= 1) {
        boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
        const char *arg = strtok(boot.recovery, "\n");
        if (arg != NULL && !strcmp(arg, "recovery")) {
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = strdup(arg);
            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
                if ((arg = strtok(NULL, "\n")) == NULL) break;
                (*argv)[*argc] = strdup(arg);
            }
            LOGI("Got arguments from boot message\n");
        } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
            LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
        }
    }

    // --- if that doesn't work, try the command file
    if (*argc <= 1) {
        FILE *fp = fopen_path(COMMAND_FILE, "r");
        if (fp != NULL) {
            char *argv0 = (*argv)[0];
            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
            (*argv)[0] = argv0;  // use the same program name

            char buf[MAX_ARG_LENGTH];
            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
                if (!fgets(buf, sizeof(buf), fp)) break;
                (*argv)[*argc] = strdup(strtok(buf, "\r\n"));  // Strip newline.
            }

            check_and_fclose(fp, COMMAND_FILE);
            LOGI("Got arguments from %s\n", COMMAND_FILE);
        }
    }

    // --> write the arguments we have back into the bootloader control block
    // always boot into recovery after this (until finish_recovery() is called)
    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
    int i;
    for (i = 1; i < *argc; ++i) {
        strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
        strlcat(boot.recovery, "\n", sizeof(boot.recovery));
    }
    set_bootloader_message(&boot);
}

代码段1 get_args()函数

2.函数install_package()

        此函数定义如下

int
install_package(const char* path, int* wipe_cache, const char* install_file)
{
    FILE* install_log = fopen_path(install_file, "w");
    if (install_log) {
        fputs(path, install_log);
        fputc('\n', install_log);
    } else {
        LOGE("failed to open last_install: %s\n", strerror(errno));
    }
    int result = really_install_package(path, wipe_cache);
    if (install_log) {
        fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
        fputc('\n', install_log);
        fclose(install_log);
    }
    return result;
}

代码段2 install_package()函数定义
       

此函数所实现的功能为:调用函数really_install_package进行升级,并将升级结果写入到文件install_file中。recovery.c中对于存放升级结果的文件定义如下:

static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";

当升级完成后进入main system,可以通过此文件读取升级结果,并给出用户相应的提示:升级成功或失败。

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

// If the package contains an update binary, extract it and run it.
static int
try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
    const ZipEntry* binary_entry =
            mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
    if (binary_entry == NULL) {
        mzCloseZipArchive(zip);
        return INSTALL_CORRUPT;
    }

    char* binary = "/tmp/update_binary";
    unlink(binary);
    int fd = creat(binary, 0755);
    if (fd < 0) {
        mzCloseZipArchive(zip);
        LOGE("Can't make %s\n", binary);
        return INSTALL_ERROR;
    }
    bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
    close(fd);
    mzCloseZipArchive(zip);

    if (!ok) {
        LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
        return INSTALL_ERROR;
    }

    int pipefd[2];
    pipe(pipefd);

    // When executing the update binary contained in the package, the
    // arguments passed are:
    //
    //   - the version number for this interface
    //
    //   - an fd to which the program can write in order to update the
    //     progress bar.  The program can write single-line commands:
    //
    //        progress <frac> <secs>
    //            fill up the next <frac> part of of the progress bar
    //            over <secs> seconds.  If <secs> is zero, use
    //            set_progress commands to manually control the
    //            progress of this segment of the bar
    //
    //        set_progress <frac>
    //            <frac> should be between 0.0 and 1.0; sets the
    //            progress bar within the segment defined by the most
    //            recent progress command.
    //
    //        firmware <"hboot"|"radio"> <filename>
    //            arrange to install the contents of <filename> in the
    //            given partition on reboot.
    //
    //            (API v2: <filename> may start with "PACKAGE:" to
    //            indicate taking a file from the OTA package.)
    //
    //            (API v3: this command no longer exists.)
    //
    //        ui_print <string>
    //            display <string> on the screen.
    //
    //   - the name of the package zip file.
    //

    char** args = malloc(sizeof(char*) * 5);
    args[0] = binary;
    args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk
    args[2] = malloc(10);
    sprintf(args[2], "%d", pipefd[1]);
    args[3] = (char*)path;
    args[4] = NULL;

    pid_t pid = fork();
    if (pid == 0) {
        close(pipefd[0]);
        execv(binary, args);
        fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
        _exit(-1);
    }
    close(pipefd[1]);

    *wipe_cache = 0;

    char buffer[1024];
    FILE* from_child = fdopen(pipefd[0], "r");
    while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
        char* command = strtok(buffer, " \n");
        if (command == NULL) {
            continue;
        } else if (strcmp(command, "progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            char* seconds_s = strtok(NULL, " \n");

            float fraction = strtof(fraction_s, NULL);
            int seconds = strtol(seconds_s, NULL, 10);

            ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
                             seconds);
        } else if (strcmp(command, "set_progress") == 0) {
            char* fraction_s = strtok(NULL, " \n");
            float fraction = strtof(fraction_s, NULL);
            ui_set_progress(fraction);
        } else if (strcmp(command, "ui_print") == 0) {
            char* str = strtok(NULL, "\n");
            if (str) {
                ui_print("%s", str);
            } else {
                ui_print("\n");
            }
        } else if (strcmp(command, "wipe_cache") == 0) {
            *wipe_cache = 1;
        } else {
            LOGE("unknown command [%s]\n", command);
        }
    }
    fclose(from_child);

    int status;
    waitpid(pid, &status, 0);
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
        return INSTALL_ERROR;
    }

    return INSTALL_SUCCESS;
}

代码段3 try_update_binary

代码04-21:提取升级包中的"META-INF/com/google/android/update-binary"文件,此文件定义了系统升级所需要进行的操作,系统将根据其中的命令执行相应的操作;

代码73-80:创建一个子进程,执行脚本文件META-INF/com/google/android/update-binary,这是系统升级的核心所在。如下是某升级包中update-script(update-script

                       是update-binary文件的文本形式,我们可以通过此文件来了解升级时的具体操作)中的部分指令:

mount("ext4", "EMMC", "/dev/block/mmcblk0p12", "/system");
assert(file_getprop("/system/build.prop", "ro.build.fingerprint") == "ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A04_20130322-094434:userdebug/test-keys" ||
       file_getprop("/system/build.prop", "ro.build.fingerprint") == "ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A05_20130322-094434:userdebug/test-keys");
assert(getprop("ro.product.device") == "sp8825ea" ||
       getprop("ro.build.product") == "sp8825ea");
ui_print("Verifying current system...");
ui_print("pre-build:ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A04_20130322-094434:userdebug/test-keys");
ui_print("post-build:ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A05_20130322-094434:userdebug/test-keys");
show_progress(0.100000, 0);
assert(apply_disk_space(8928364));
assert(apply_patch_check("/system/build.prop", "098db2c72e97c3e84a1b14b782befe9446e44f49", "64a46c872b8766b20feeaf2a4802ecae53a75e97"));
set_progress(0.000285);
assert(apply_patch_check("/system/lib/modules/blcr.ko", "78dbaacbe60f9106d160dbe5cbd3778441c3b999", "7eb55dbf8b929c8296a51f62d2c0cfb56a8a7f32"));
set_progress(0.014533);
assert(apply_patch_check("/system/lib/modules/blcr_imports.ko", "9a42096b4b1da8345e9f87da68a492740de50a61", "288ff8c8aafbd6c00b467ed89c4f2580937f77a8"));
set_progress(0.016106);
assert(apply_patch_check("EMMC:/dev/block/mmcblk0p2:5509716:78bbdc657109f192eb606371f12a30b881fbeda9:5512524:b1d56633f28bbed2a7530daeeee85b6710173ee0"));
set_progress(0.623307);
assert(apply_patch_check("EMMC:/dev/block/mmcblk0p3:3014656:406b98e17bae26628b46003189122b74b64c5992:3014656:c0e9e938c6a42fe42531d92cfb8dc0301be99885"));
set_progress(0.955538);
assert(apply_patch_check("EMMC:/dev/block/mmcblk0p1:344064:3e46eecb33050e461a80d38787e9e773ab09ff04:339968:c2e4ed3c6d9554fd1415a473f7097ec1a5d0845f"));
set_progress(0.993456);
assert(apply_patch_space(129286));

# ---- start making changes here ----

ui_print("Removing unneeded files...");
delete("/system/app/CalllogManager.apk", "/system/lib/libext2_blkid.so",
       "/system/lib/libext2_com_err.so", "/system/lib/libext2_e2p.so",
       "/system/lib/libext2_profile.so", "/system/lib/libext2_uuid.so",
       "/system/lib/libext2fs.so",
       "/system/recovery.img");
show_progress(0.800000, 0);
ui_print("Patching system files...");
apply_patch("/system/lib/modules/blcr.ko", "-",
            78dbaacbe60f9106d160dbe5cbd3778441c3b999, 129286,
            7eb55dbf8b929c8296a51f62d2c0cfb56a8a7f32, package_extract_file("patch/system/lib/modules/blcr.ko.p"));
set_progress(0.014247);
apply_patch("/system/lib/modules/blcr_imports.ko", "-",
            9a42096b4b1da8345e9f87da68a492740de50a61, 14273,
            288ff8c8aafbd6c00b467ed89c4f2580937f77a8, package_extract_file("patch/system/lib/modules/blcr_imports.ko.p"));
set_progress(0.015820);
delete("/system/recovery-from-boot.p",
       "/system/etc/install-recovery.sh");
ui_print("Patching modem ...");
apply_patch("EMMC:/dev/block/mmcblk0p2:5509716:78bbdc657109f192eb606371f12a30b881fbeda9:5512524:b1d56633f28bbed2a7530daeeee85b6710173ee0",
            "/sdcard/modem.bin", b1d56633f28bbed2a7530daeeee85b6710173ee0,
            5512524,
            78bbdc657109f192eb606371f12a30b881fbeda9, package_extract_file("patch/modem.bin.p"));
set_progress(0.623294);

代码段4 update-script内容

尽管并不确定这些代码的含义,但一眼望去我们仍能够分辨出其中包含的一些命令,诸如:mount,assert,ui_print,package_extract_file,apply_patch等。OK,继续往下读try_update_binary函数,读到一段while循环(86-116),心中一喜,这应该就是对应于各种命令的执行代码吧。遗憾的是,仔细读来,发现并非如此。while循环中只针对于progress、set_progress、wipe_cache、ui_print四种命令进行了处理,而package_extract_file、apply_patch等关键命令却了无踪影。显然,升级代码并非我想象的这么简单,在这个try_update_binary之后,一定还隐藏着其他东东。于是在./bootable目录下尝试搜索了apply_patch关键词,终于在./bootable/recovery/updater/install.c中找到了答案。

先看./bootable/recovery/updater/install.c中的一段代码:

void RegisterInstallFunctions() {
    RegisterFunction("mount", MountFn);
    RegisterFunction("is_mounted", IsMountedFn);
    RegisterFunction("unmount", UnmountFn);
    RegisterFunction("format", FormatFn);
    RegisterFunction("show_progress", ShowProgressFn);
    RegisterFunction("set_progress", SetProgressFn);
    RegisterFunction("delete", DeleteFn);
    RegisterFunction("delete_recursive", DeleteFn);
    RegisterFunction("package_extract_dir", PackageExtractDirFn);
    RegisterFunction("package_extract_file", PackageExtractFileFn);
    RegisterFunction("retouch_binaries", RetouchBinariesFn);
    RegisterFunction("undo_retouch_binaries", UndoRetouchBinariesFn);
    RegisterFunction("symlink", SymlinkFn);
    RegisterFunction("set_perm", SetPermFn);
    RegisterFunction("set_perm_recursive", SetPermFn);

    RegisterFunction("getprop", GetPropFn);
    RegisterFunction("file_getprop", FileGetPropFn);
    RegisterFunction("write_raw_image", WriteRawImageFn);

    RegisterFunction("apply_patch", ApplyPatchFn);
    RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
    RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);

    RegisterFunction("read_file", ReadFileFn);
    RegisterFunction("sha1_check", Sha1CheckFn);

    RegisterFunction("wipe_cache", WipeCacheFn);

    RegisterFunction("ui_print", UIPrintFn);

    RegisterFunction("run_program", RunProgramFn);
}

代码段5 updater-script与执行函数的映射

显而易见,对应于updater-script中的命令操作全都定义在./bootable/recovery/updater/install.c。系统升级时,由try_update_binary中创建的子进程执行这些操作,而try_

update_binary中的while循环只是负责更新升级进度条而已。

结合脚本与实现代码,可以对操作命令有较为清楚的认识。以脚本appy_patch为例,可以将其以函数的形式写为apply_patch(src_file, target_file, target_sha1, target_size, sha1_1, patch_1),它实现的功能为:将源文件src_file利用差分文件patch_1进行升级,并将结果文件写入到target_file中。target_size表示目录文件的大小,target_sha和sha1_1分别为相应的校验数据。

其他命令及对应的代码不再详述。

3.函数finish_recovery()

static void
finish_recovery(const char *send_intent) {
    // By this point, we're ready to return to the main system...
    if (send_intent != NULL) {
        FILE *fp = fopen_path(INTENT_FILE, "w");
        if (fp == NULL) {
            LOGE("Can't open %s\n", INTENT_FILE);
        } else {
            fputs(send_intent, fp);
            check_and_fclose(fp, INTENT_FILE);
        }
    }

    // Copy logs to cache so the system can find out what happened.
    copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
    copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
    copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
    chmod(LOG_FILE, 0600);
    chown(LOG_FILE, 1000, 1000);   // system user
    chmod(LAST_LOG_FILE, 0640);
    chmod(LAST_INSTALL_FILE, 0644);

    // Reset to mormal system boot so recovery won't cycle indefinitely.
    struct bootloader_message boot;
    memset(&boot, 0, sizeof(boot));
    set_bootloader_message(&boot);

    // Remove the command file, so recovery won't repeat indefinitely.
    if (ensure_path_mounted(COMMAND_FILE) != 0 ||
        (unlink(COMMAND_FILE) && errno != ENOENT)) {
        LOGW("Can't unlink %s\n", COMMAND_FILE);
    }

    ensure_path_unmounted(CACHE_ROOT);
    sync();  // For good measure.
}

代码段6 finish_recovery函数

此函数完成的功能包括:

代码04-12:将send_intent写入文件并传递给main system;

代码14-21:将日志信息从TEMPORARY_LOG_FILE复制到LOG_FILE、LAST_LOG_FILE等文件中(这些文件存放在/cache目录);

代码23-26:清空BCB;

代码28-32:删除/cache/recovery/command文件。

显然,当系统升级成功后,执行finish_recovery函数重启,系统将不再进入recovery模式。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值