Recovery升级准备流程

  1. OTA应用调用Framework内提供的系统接口RecoverySystem.installPackage()后,在/cache/recovery/block.map中写入升级信息。该文件将会在设备重启时,由启动引导程序读取内容,并根据内容启动为recovery模式,调起recovery进行OTA包的升级。
  2. Recovery启动后,首先挂载分区,读取系统属性,然后通过block.map指定的路径尝试读取升级文件并进行校验。
  3. 取出OTA包内的升级文件和列表system.patch.datsystem.new.datsystem.transfer.listboot.imguboot.imgmetadata用于校验版本。若update-binary有,则取出,用于执行升级脚本定义的升级命令。updater-script定义了用于执行升级的脚本。
  4. 根据OTA包内metadata定义的post-build和pre-build的匹配来确定当前OTA是否是本设备对应Android版本的合适差分包。确认版本号匹配后,才会进行校验。若版本号不匹配,则在校验进行之前就会退出安装。

OTA安装流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* OTA INSTALL
* 1. main system downloads OTA package to /cache/some-filename.zip
* 2. main system writes "--update_package=/cache/some-filename.zip"
* 3. main system reboots into recovery
* 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
* -- after this, rebooting will attempt to reinstall the update --
* 5. install_package() attempts to install the update
* NOTE: the package install must itself be restartable from any point
* 6. finish_recovery() erases BCB
* -- after this, rebooting will (try to) restart the main system --
* 7. ** if install failed **
* 7a. prompt_and_wait() shows an error icon and waits for the user
* 7b; the user reboots (pulling the battery, etc) into the main system
* 8. main() calls maybe_install_firmware_update()
* ** if the update contained radio/hboot firmware **:
* 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
* -- after this, rebooting will reformat cache & restart main system --
* 8b. m_i_f_u() writes firmware image into raw cache partition
* 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
* -- after this, rebooting will attempt to reinstall firmware --
* 8d. bootloader tries to flash firmware
* 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
* -- after this, rebooting will reformat cache & restart main system --
* 8f. erase_volume() reformats /cache
* 8g. finish_recovery() erases BCB
* -- after this, rebooting will (try to) restart the main system --
* 9. main() calls reboot() to boot main system
*/

Recovery执行升级

  • metadata: 键值对存储目标版本和源版本的信息,匹配成功才会进行OTA包的写入。
  • *.dat *.list: 按照block块diff得到的数据文件。
  • update-binary、update-script: recovery会fork子进程,exec执行OTA包内的update-binary,而update-binary实际上是一个脚本执行器,它执行的脚本就是OTA包的安装指令,这些指令存储在update-script内。
  1. recovery挂载分区、准备参数、获取升级文件后,调用install_package()(实际上是really_install_package()真正执行安装)进行OTA包的安装。该方法校验升级包后,通过try_update_binary()执行安装。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    static int
    really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
    std::vector<std::string>& log_buffer, int retry_count)
    {
    // ...

    // Map the update package into memory.
    ui->Print("Opening update package...\n");

    // ...

    // 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\n(%s)\n", 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...\n");
    if (retry_count > 0) {
    ui->Print("Retry attempt: %d\n", retry_count);
    }
    ui->SetEnableReboot(false);
    int result = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count);
    ui->SetEnableReboot(true);
    ui->Print("\n");

    sysReleaseMap(&map);

    return result;
    }
  2. try_update_binary()会fork一个子进程并通过pipe进行通信。fork后的父进程会从pipe中读取子进程发出的指令,并执行对应的方法,如打印字符串到屏幕、重试更新、设置更新进度条和抹除设备数据等。pipe关闭后,父进程获取子进程的退出状态即更新包的安装结果。子进程会exec一个打包进入OTA包的updater。它是一个可执行bin,是执行OTA安装的关键进程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    // 如果OTA升级包内包含update-binary,就会将它解压出来作为安装执行器。即fork的子进程会运行这个bin,这个bin真正执行将OTA升级包写入系统。
    static int
    try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache,
    std::vector<std::string>& log_buffer, int retry_count)
    {
    read_source_target_build(zip, log_buffer);

    // 这里是父进程准备与子进程通信的pipe
    int pipefd[2];
    pipe(pipefd);

    std::vector<std::string> args;

    // 根据系统是否是AB升级,确定升级命令,实际就是从OTA包中取出update-binary并组建升级命令
    // 升级命令既是调用子进程运行update-binary的指令
    int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
    mzCloseZipArchive(zip);
    if (ret) {
    close(pipefd[0]);
    close(pipefd[1]);
    return ret;
    }

    // 接下来通过注释阐述了父进程,即当前进程能够支持的函数
    // 这些函数将由子进程(即将创建的update-binary的进程)通过pipe向父进程(本进程)以字符串的形式发出函数名称和参数,进而根据函数名称调用函数
    // 基本常用的包括设置进度(显示到ui)、ui打印和清除数据
    // 子进程将被创建以执行升级,而本进程(父进程)会循环等待pipe的数据,并解释后执行对应的函数,实现进程分离独立运行(父进程实现UI和其他工作,子进程负责安装)
    // 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.
    //
    // - an optional argument "retry" if this update is a retry of a failed
    // update attempt.
    //

    // 准备创建子进程,准备子进程参数,准备管道
    // Convert the vector to a NULL-terminated char* array suitable for execv.
    const char* chr_args[args.size() + 1];
    chr_args[args.size()] = NULL;
    for (size_t i = 0; i < args.size(); i++) {
    chr_args[i] = args[i].c_str();
    }

    pid_t pid = fork();

    if (pid == -1) {
    close(pipefd[0]);
    close(pipefd[1]);
    LOGE("Failed to fork update binary: %s\n", strerror(errno));
    return INSTALL_ERROR;
    }

    // 子进程,执行update-binary,跳转到updater.cpp
    if (pid == 0) {
    umask(022);
    close(pipefd[0]);
    execv(chr_args[0], const_cast<char**>(chr_args));
    fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
    _exit(-1);
    }
    close(pipefd[1]);

    *wipe_cache = false;
    bool retry_update = false;

    // 父进程,在管道的一端进行读循环,直到子进程退出时关闭管道后,跳出循环
    // 子进程(update-binary)会向管道写入函数名和参数,这些字符串形式的信息在接下来的while()中被解析,然后由父进程(本进程)执行对应的函数。
    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->ShowProgress(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->SetProgress(fraction);
    } else if (strcmp(command, "ui_print") == 0) {
    char* str = strtok(NULL, "\n");
    if (str) {
    ui->PrintOnScreenOnly("%s", str);
    } else {
    ui->PrintOnScreenOnly("\n");
    }
    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 if (strcmp(command, "retry_update") == 0) {
    retry_update = true;
    } else if (strcmp(command, "log") == 0) {
    // Save the logging request from updater and write to
    // last_install later.
    log_buffer.push_back(std::string(strtok(NULL, "\n")));
    } else if (strcmp(command, "wipe_all") == 0){
    printf("set bWipeAfterUpdate to true.\n");
    bWipeAfterUpdate = true;
    }else {
    LOGE("unknown command [%s]\n", command);
    }
    }
    fclose(from_child);

    // 子进程结束,这里获取子进程的退出状态码
    int status;
    waitpid(pid, &status, 0);
    if (retry_update) {
    return INSTALL_RETRY;
    }
    // 分别获取子进程是否正常退出(不是因为崩溃或信号退出的),并获取退出状态码。若子进程正常退出,则这里已经完成了安装,接下来回到install.cpp完成收尾后重启。
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
    LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
    return INSTALL_ERROR;
    }

    return INSTALL_SUCCESS;
    }
  3. 通过子进程执行安装。

  • 子进程是在install.cpptry_binary_update中fork后execv启动的。代码位于```updater.cpp`。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    int main(int argc, char** argv) {
    // ...

    // 通过execv时传递的参数,获取管道fd
    int fd = atoi(argv[2]);
    FILE* cmd_pipe = fdopen(fd, "wb");
    setlinebuf(cmd_pipe);

    // 从OTA升级包中解压得到updater-script,该文件与本进程的bin(update-binary)在同一个目录下
    // 这个script是某种特定格式的脚本,有本进程解析并执行,其内容就是升级的所有内容。包含升级文件的校验、升级流程的控制和升级步骤,全部都在这个文件内控制。
    const char* package_filename = argv[3];
    g_package_file = package_filename;
    MemMapping map;
    if (sysMapFile(package_filename, &map) != 0) {
    printf("failed to map package %s\n", argv[3]);
    return 3;
    }
    ZipArchive za;
    int err;
    err = mzOpenZipArchive(map.addr, map.length, &za);
    if (err != 0) {
    printf("failed to open package %s: %s\n",
    argv[3], strerror(err));
    return 3;
    }
    ota_io_init(&za);

    const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
    if (script_entry == NULL) {
    printf("failed to find %s in %s\n", SCRIPT_NAME, package_filename);
    return 4;
    }

    char* script = reinterpret_cast<char*>(malloc(script_entry->uncompLen+1));
    if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
    printf("failed to read script from package\n");
    return 5;
    }
    script[script_entry->uncompLen] = '\0';

    // 注册处理函数,实际上就是将函数名字符串与对应的函数指针关联起来
    // 从updater-script中读取到的执行升级任务的脚本,其实就是以下注册的函数名加上一些参数。即读出来的内容,根据对应的函数名调用对应的函数,就实现了解析执行安装脚本(updater-script)的功能。
    RegisterBuiltins();
    RegisterInstallFunctions();
    RegisterBlockImageFunctions(); // 例,这里注册了升级system分区的常用的指令,使得update-binary(本进程)可以执行对应的system分区升级命令。
    RegisterDeviceExtensions();
    FinishRegistration();

    // 解析脚本。这里调用的parse_string是updater-script所使用的一种特定格式语言的解析器,经过执行后会将结果保存到`Expr* root`。它是一个链表。这个链表实际上很简单,仅包含了解析出来的命令(字符串形式,在上面注册的Register*Func中找得到对应的函数)、对应的参数(传递给函数的参数)和链表成员指针(指向链表的下一个成员)
    // parse_string按行解析脚本,也将每一行结果保存为一个链表成员。所以每个链表成员实际上都是一行updater-script的内容,也是一行命令,对应了一个函数。
    Expr* root;
    int error_count = 0;
    int error = parse_string(script, &root, &error_count);
    if (error != 0 || error_count > 0) {
    printf("%d parse errors\n", error_count);
    return 6;
    }

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

    sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);

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

    // 完成了updater-script的解析(即通过parse_string将updater-script的指令转换成一个个函数调用指令,通过有序链表存储)
    // 这里开始解析链表,将链表指示的函数调用,通过上面注册的函数(Register*Function)找到对应的函数,并执行。
    UpdaterInfo updater_info;
    updater_info.cmd_pipe = cmd_pipe;
    updater_info.package_zip = &za;
    updater_info.version = atoi(version);
    updater_info.package_zip_addr = map.addr;
    updater_info.package_zip_len = map.length;

    State state;
    state.cookie = &updater_info;
    state.script = script;
    state.errmsg = NULL;

    if (argc == 5) {
    if (strcmp(argv[4], "retry") == 0) {
    state.is_retry = true;
    } else {
    printf("unexpected argument: %s", argv[4]);
    }
    }

    // 执行脚本指示的函数。其中state用于返回,提示调用结果。root即上文的Expr,里面包含了注册的函数指针,包含了从update-script解析出来的函数参数、函数名。
    char* result = Evaluate(&state, root);

    if (have_eio_error) {
    fprintf(cmd_pipe, "retry_update\n");
    }

    // 脚本执行,错误处理
    if (result == NULL) {
    if (state.errmsg == NULL) {
    printf("script aborted (no error message)\n");
    fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
    } else {
    printf("script aborted: %s\n", state.errmsg);
    char* line = strtok(state.errmsg, "\n");
    while (line) {
    // Parse the error code in abort message.
    // Example: "E30: This package is for bullhead devices."
    if (*line == 'E') {
    if (sscanf(line, "E%u: ", &state.error_code) != 1) {
    printf("Failed to parse error code: [%s]\n", line);
    }
    }
    fprintf(cmd_pipe, "ui_print %s\n", line);
    line = strtok(NULL, "\n");
    }
    fprintf(cmd_pipe, "ui_print\n");
    }

    if (state.error_code != kNoError) {
    fprintf(cmd_pipe, "log error: %d\n", state.error_code);
    // Cause code should provide additional information about the abort;
    // report only when an error exists.
    if (state.cause_code != kNoCause) {
    fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
    }
    }

    free(state.errmsg);
    return 7;
    } else {
    fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result);
    free(result);
    }

    if (updater_info.package_zip) {
    mzCloseZipArchive(updater_info.package_zip);
    }
    sysReleaseMap(&map);
    free(script);

    return 0;
    }
  1. 子进程解析脚本调用注册的函数完成处理后,即退出,由父进程完成收尾工作并重启。

多进程/线程架构

  • recovery中可能会同时存在多个进程和线程
  • 如updater就运行于独立的进程。
  • updater执行对应的定义于updater-script的命令时,可能会创建线程。
  • 如block_image_update对应的函数PerformBlockImageUpdate就会创建线程来写入block差分数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    if (params.canwrite) {
    params.nti.za = za;
    params.nti.entry = new_entry;

    pthread_mutex_init(&params.nti.mu, nullptr);
    pthread_cond_init(&params.nti.cv, nullptr);
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    int error = pthread_create(&params.thread, &attr, unzip_new_data, &params.nti);
    if (error != 0) {
    fprintf(stderr, "pthread_create failed: %s\n", strerror(error));
    return StringValue(strdup(""));
    }
    }

update-script、updater-binary与system.patch.dat、system.transfer.list

  1. update-binary是OTA包内包含的执行升级指令的Bin,这个bin根据updater-script定义的一系列脚本来执行升级;
  2. 在较高版本的Android OTA中,recovery支持以block diff形式生成的差分包进行升级。当update-binary进行system分区的升级时,往往是根据updater-script中定义的block_image_update指令进行块差异升级。其中,update-script仅仅只是定义了执行该命令,而真正保存差异的(即即将写入system分区的内容)是system.patch.dat和system.transfer.list