Android Recovery OTA升级(二)—— Recovery源码解析

来源:互联网 发布:淘宝牙齿美白仪有用吗 编辑:程序博客网 时间:2024/04/28 12:40

目录

  • 目录
  • 概述
  • Recovery源码解析
  • main函数
    • 输出重定向
    • 填充fstab结构体
    • 挂载cache分区
    • 获取Recovery命令参数
    • 分析recovery命令
    • install_package
      • load_keys加载公钥原文件
      • verify_file对升级包进行签名校验
      • mzOpenZipArchive-打开升级包获取相关信息
      • try_update_binary-ota真正执行的地方


概述

之前博客里一篇文章讲解了OTA包的生成原理,这篇文章主要是从Recovery源码的角度介绍一下Recovery是如何使用OTA包进行系统升级的。

为了防止泄密,本文源码都是基于Android4.4.2_r1版本进行分析。


Recovery源码解析

Recovery源码的入口位置为:bootable/recovery/recovery.cpp文件。下面我就来分析一下Recovery的源码。

static const char *CACHE_LOG_DIR = "/cache/recovery";static const char *COMMAND_FILE = "/cache/recovery/command";static const char *INTENT_FILE = "/cache/recovery/intent";static const char *LOG_FILE = "/cache/recovery/log";

注释里英文写的很清楚:

The recovery tool communicates with the main system through /cache files.
/cache/recovery/command - INPUT - command line for tool, one arg per line
/cache/recovery/log - OUTPUT - combined log file from recovery run(s)
/cache/recovery/intent - OUTPUT - intent that was passed in

同时,代码里还有一段对Recovery识别命令的注释描述:

The arguments which may be supplied in the command file:
1. –send_intent=anystring - write the text out to recovery.intent
2. –update_package=path - verify install an OTA package file
3. –wipe_data - erase user data (and cache), then reboot
4. –wipe_cache - wipe cache (but not user data), then reboot


main函数

接下面,我们分析一下recovery.c的入口main函数。


输出重定向

static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"int main(int argc, char **argv){    time_t start = time(NULL);    freopen(TEMPORARY_LOG_FILE, "a", stdout);    setbuf(stdout, NULL);    freopen(TEMPORARY_LOG_FILE, "a", stderr);    setbuf(stderr, NULL);    // 打印启动recovery的时间    printf("Starting recovery on %s", ctime(&start));}

该部分的主要作用是:将标准输出和错误输出重定向到/tmp/recovery.log文件中。

填充fstab结构体

struct fstab {    int num_entries;    struct fstab_rec *recs;    char *fstab_filename;};struct fstab_rec {    char *blk_device;    char *mount_point;    char *fs_type;    unsigned long flags;    char *fs_options;    int fs_mgr_flags;    char *key_loc;    char *verity_loc;    long long length;    char *label;    int partnum;    int swap_prio;    unsigned int zram_size;};typdef struct fstab_rec Volume;void load_volume_table(){    int i;    int ret;    // 解析/etc/recovery.fstab配置文件,填充fstab结构体    fstab = fs_mgr_read_fstab("/etc/recovery.fstab");    if (!fstab) {        LOGE("failed to read /etc/recovery.fstab\n");        return;    }    // 在fstab结构体中增加/tmp分区    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk", 0);    if (ret < 0) {        LOGE("failed to add /tmp entry to fstab\n");        fs_mgr_free_fstab(fstab);        fstab = NULL;        return;    }    printf("recovery filesystem table\n");    printf("=========================\n");    for (i = 0; i < fstab->num_entries; i ++) {        Volume* v = &fstab->recs[i];        printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,                v->blk_device, v->length);    }    printf("\n");}int main(int argc, char **argv){    load_volume_table();}

该部分代码的主要作用是用recovery根目录下的/etc/recovery.fstab中的分区内容和/tmp分区内容来填充了fstab结构体,并没有真正的进行分区挂载。


挂载cache分区

源码分析如下:

typedef struct fstab_rec Volume;#define LAST_LOG_FILE "/cache/recovery/last_log"int ensure_path_mounted(const char* path){    // 这里是在fstab结构体中找到挂载点为/cache的fstab_recs    Volume* v = volume_for_path(path);    if (v == NULL) {        LOGE("unknown volume for path [%s]\n", path);        return -1;    }    // ramdisk类型的分区是一直挂载的。    if (strcmp(v->fs_type, "ramdisk") == 0) {        return 0;    }    int result;    result = scan_mounted_volumes();    const MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);    if (mv) {        // 说明当前分区已经被挂载了        return 0;    }    // 下面是具体的挂载过程    mkdir(v->mount_point, 0755);    if (strcmp(v->fs_type, "yaffs2") == 0) {        // .......不用管这个yaffs2分区类型了,目前基本是ext4的。    } else if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "vfat") == 0) {        // ext4和vfat类型的分区调用mount函数进行挂载,挂载成功返回0,失败返回-1        result = mount(v->blk_device, v->mount_point, v->fs_type, MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");        if (result == 0) return 0;        LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));        return -1;    }    LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point);    return -1;}#define LAST_LOG_FILE "/cache/recovery/last_log"int main(int argc, char **argv){    ensure_path_mounted(LAST_LOG_FILE);    // 这个不用care了,就是重命名了log    rotate_last_logs(10);}

从源码可以看出,ensure_path_mounted(LAST_LOG_FILE);代码的主要作用是保证/cache分区被挂载。


获取Recovery命令参数

源码分析如下:

struct bootloader_message{    char command[32];    char status[32];    char recovery[768];    char stage[32];    char reverse[224];};int get_bootloader_message(struct bootloader_message *out) {    Volume* v = volume_for_path("/misc");    if (v == NULL) {        return -1;    }    if (strcmp(v->fs_type, "mtd") == 0) {        return get_bootloader_message_mtd(out, v);    } else if (strcmp(v->fs_type, "emmc") == 0){        return get_bootloader_message_block(out, v);    }    return -1;}static const char *COMMAND_FILE = "/cache/recovery/command";static const int MAX_ARGS = 100;static void get_args(int *argc, char ***argv){    struct bootloader_message boot;    memset(&boot, 0, sizeof(boot));    // 首先从MISC分区中读取BCB数据块到boot变量中,可能存在为空的情况。    get_bootloader_message(&boot);    stage = strndup(boot.stage, sizeof(boot.stage));    // 从/cache/recovery/command获取参数,一般常用的ota升级做法。    if (*argc < 1) {        FILE *fp = fopen_path(COMMAND_FILE, "r");        if (fp != NULL) {            char *token;            char *argv0 = (*argv)[0];            *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);            (*argv)[0] = argv0;            char buf[MAX_ARG_LENGTH];            for (*argc = 1; *argc < MAX_ARGS; ++*argc) {                if (!fgets(buf, sizeof(buf), fp)) break;                token = strtok(buf, "\r\n");                if (token != NULL) {                    (*argv)[*argc] = strdup(token);                } else {                    --*argc;                }            }            check_and_fclose(fp, COMMAND_FILE);        }    }    // ....省略部分代码,代码的作用就是将从/cache/recovery/command获取的参数写入到misc分区}int main(int argc, char **argv){    get_args(&argc, &argv);}

可以看到,对于ota升级来说,get_args函数的作用就是读取/cache/recovery/command文件,将参数存到argv二维数组中。


分析recovery命令

源码如下:

int main(int argc, char **argv){    const char *send_intent = NULL;    const char *update_package = NULL;    int wipe_data = 0, wipe_cache = 0, show_text = 0;    bool just_exit = false;    bool shutdown_after = false;    int arg;    while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {        switch(arg) {        case 's': send_intent = optarg; break;        case 'u': update_package = optarg; break;        case 'w': wipe_data = wipe_cache = 1; break;        case 'c': wipe_cache = 1; break;        case 't': show_text = 1; break;        case 'x': just_exit = true; break;        case 'l': locale = optarg; break;        case 'g':            if (stage == NULL || stage == '\0') {                char buffer[20] = "1/";                strncat(buffer, optarg, sizeof(buffer)-3);                stage = strdup(buffer);            }        case 'p': shutdown_after = true; break;        case '?':            LOGE("Invalid command argument\n");            continue;        }    }}

赋值结束后,就是UI和真正的OTA升级过程了。


install_package

源码如下:

// 卸载除了/tmp和/cache的其他分区int setup_install_mounts() {    if (fstab == NULL) {        LOGE("can't set up install mounts: no fstab loaded\n");        return -1;    }    for (int i = 0; i < fstab.num_entries; i ++) {        Volume* v = fstab->recs + i;        if (strcmp(v->mount_point, "/tmp") == 0 ||            strcmp(v->mount_point, "/cache") == 0) {            if (ensure_path_mounted(v->mount_point) != 0) return -1;        } else {            if (ensure_path_unmounted(v->mount_point) != 0) return -1;        }    }    return 0;}// ota升级的真正实现,这里去除掉跟UI显示相关的逻辑#define PUBLIC_KEYS_FILE "/res/keys"static int really_install_package(const char *path, int* wipe_cache){    // 确保zip包所在的目录是挂载的    if (ensure_path_mounted(path) != 0) {        LOGE("Can't mount %s\n", path);        return INSTALL_CORRUPT;    }    // 加载公钥源文件,根据公钥对zip包进行校验    int numKeys;    Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);    err = verify_file(path, loadedKeys, numKeys);    // 打开升级包,并将相关的信息拷贝到一个临时的ZipArchinve变量中。这一步并未对我们的update.zip包解压。    ZipArchive zip;    err = mzOpenZipArchive(path, &zip);    // 真正fota升级的过程    return try_update_binary(path, &zip, wipe_cache);}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;    if (setup_install_mounts() != 0) {        result = INSTALL_ERROR;    } else {        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;}static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";int main(int argc, char **argv){    if (update_package != NULL) {        status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);        if (status == INSTALL_SUCCESS && wipe_cache) {            if (erase_volume("/cache")) {                LOGE("Cache wipe (requested by package) failed.");            }        }        if (status != INSTALL_SUCCESS) {            ui->Print("Installation aborted.\n");        }    }}

接下来,分别讲解一下really_install_package中具体函数实现。


load_keys——加载公钥原文件

load_keys源码实现如下:

#define RSANUMBYTES 256#define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))typedef struct RSAPublicKey {    int len;    uint32_t n0inv;    uint32_t n[RSANUMWORDS];    uint32_t rr[RSANUMWORDS];    int exponent;} RSAPublicKey;typedef struct Certificate {    int hash_len;    RSAPublicKey* public_key;} Certificate;#define SHA_DIGEST_SIZE 20#define SHA256_DIGEST_SIZE 32Certificate* load_keys(const char *filename, int *numKeys){    Certificate *out = NULL;    *numKeys = 0;    // 打开recovery根目录下的/res/keys文件    FILE *fp = fopen(filename, "r");    if (fp == NULL) {        goto exit;    }    {        int i;        bool done = false;        while (! done) {            ++ *numKeys;            out = (Certificate *)realloc(out, *numKeys * sizeof(Certificate));            Certificate *cert = out + (*numKeys - 1);            cert->public_key = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));            // 分析第一个字符,获取版本和长度            char start_char;            if (fscanf(f, "%c", &start_char) != 1) goto exit;            if (start_char == '{') {                cert->public_key->exponent = 3;                cert->hash_len = SHA_DIGEST_SIZE;            } else if (start_char == 'v') {                int version;                if (fscanf(f, "%d {", version) != 1) goto exit;                switch (version) {                    case 2:                        cert->public_key->exponent = 65537;                        cert->hash_len = SHA_DIGEST_SIZE;                        break;                    case 3:                        cert->public_key->exponent = 3;                        cert->hash_len = SHA256_DIGEST_SIZE;                        break;                    case 4:                        cert->public_key->exponent = 65537;                        cert->hash_len = SHA256_DIGEST_SIZE;                        break;                    default:                        goto exit;                }            }            RSAPublicKey *key = cert->public_key;            if (fscanf(f, " %i , 0x%x , { %u", &(key->len), &(key->n0inv), &(key->n[0])) != 3) goto exit;            // 依次读取key->len个数字到key->n[i]中            for (i = 0; i < key->len; i ++) {                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;            }            // 再次读取(key->len + 1)个数字到key->rr数组中            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;            for (i = 0; i < key->len; i ++) {                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;            }            fscanf(f, " } } ");            switch (fgetc(f)) {                case ',':                    // 还有需要load的key                    break;                case EOF:                    done = true;                    break;                default:                    goto exit;            }        }            }    fclose(f);    return out;exit:    if (f) fclose(f);    free(out);    *numKeys = 0;    return NULL;}源码还是很简单的,就是解析/res/keys文件,将该文件里面的校验key保存到Certificate* loadedKeys中。

verify_file——对升级包进行签名校验

verify_file函数的源码如下:

#define VERIFY_SUCCESS 0#define VERIFY_FAILURE 1int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys) {    FILE *f = fopen(path, "rb");    if (f == NULL) {        return VERIFY_FAILURE;    }// 从文件最后6个字符开始校验,并获取comment_size和signature_start等信息#define FOOTER_SIZE 6    if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) {        fclose(f);        return VERIFY_FAILURE;    }    unsigned char footer[FOOTER_SIZE];    if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) {        fclose(f);        return VERIFY_FAILURE;    }    if (footer[2] != 0xff || footer[3] != 0xff) {        fclose(f);        return VERIFY_FAILURE;    }    size_t comment_size = footer[4] + (footer[5] << 8);    size_t signature_start = footer[0] + (footer[1] << 8);    if (signature_start - FOOTER_SIZE < RSANUMBYTES) {        fclose(f);        return VERIFY_FAILURE;    }// 校验从文件末尾开始的倒数eocd_size个字符#define EOCD_HEADER_SIZE 22    size_t eocd_size = comment_size + EOCD_HEADER_SIZE;    if (fseek(f, -eocd_size, SEEK_END) != 0) {        fclose(f);        return VERIFY_FAILURE;    }    // ... 省略,感兴趣的自己看源码#define BUFFER_SIZE 4096    // ... rsa校验,感兴趣的自己看源码}

verify_file函数校验失败的话,ota过程会直接返回校验失败的错误,不再进行ota升级流程。


mzOpenZipArchive-打开升级包,获取相关信息

mzOpenZipArchive函数的作用是:打开升级包,并将相关的信息拷贝到一个临时的ZipArchinve变量中。具体实现代码如下:

typedef struct MemMapping {    void *addr;    size_t length;    void* baseAddr;    size_t baseLength;} MemMapping;int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive){    MemMapping map;    int err;    map.addr = NULL;    memset(pArchive, 0, sizeof(*pArchive));    pArchive->fd = open(fileName, O_RDONLY, 0);    if (pArchive->fd < 0) {        goto bail;    }    // 调用mmap将zip文件映射到内容中,起始地址保存到map->baseAddr和map->addr,映射长度保存在map->baseLength和map->length中。    if (sysMapFileInShmem(pArchive->fd, &map) != 0) {        goto bail;    }    // 解析map中的内容到pArhchive中(其中,map是zip包文件在内存中的映射)    if (!parseZipArchive(pArchive, &map)) {        goto bail;    }    sysCopyMap(&pArchive->map, &map);    map->addr = NULL;bail:    // 资源回收}

try_update_binary-ota真正执行的地方

try_update_binary才是recovery对update.zip真正开始进行升级的地方。源码如下:

static int try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache){    // 将zip包META-INF/com/google/android/update-binary文件内容保存到binary_entry结构体中    const ZipEntry* binary_entry = mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);    // 这里是将binary_entry结构体的内容保存到/tmp/update_binary文件中    // 其实说白了,就是将ota zip包中的META-INF/com/google/android/update-binary复制到recovery的/tmp/update_binary文件中    const char* binary = "/tmp/update_binary";    unlink(binary);    int fd = creat(binary, 0755);    bool ok = mzExtractZipEntryToFile(META-INF/com/google/android/update-binaryzip, binary_entry, fd);    close(fd);    mzCloseZipArchive(zip);    // 创建管道,用于子进程和父进程的通信    int pipefd[2];    pipe(pipefd);    //     const char** args = (const char**)malloc(sizeof(char*) * 5);    args[0] = binary;    args[1] = EXPAND(RECOVERY_API_VERSION);    char* temp = (char*)malloc(10);    sprintf(temp, "%s", pipefd[1]);    args[2] = temp;    args[3] = (char *)path;    args[4] = NULL;    pid_t pid = fork();    // 子进程执行update_binary,进行刷机操作    if (pid == 0) {        // 子进程发送消息,所以关闭receive fd.        close(pipefd[0]);        // 执行binary,即执行/tmp/update_binary,需要去研究update.c的源码        execv(binary, (char* const*)args);    }    // 父进程用来接收消息,所以关闭send fd.    close(pipefd[1]);    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) {            // 父进程显示UI进度        } else if (strcmp(command, "set_progress") == 0) {            // 父进程设置UI进度        } else if (strcmp(command, "ui_print") == 0) {            // 父进程打印信息        } else if (strcmp(command, "wipe_cache") == 0) {            // 清除cache分区        } else if (strcmp(command, "special_factory_reset") == 0) {            // 清除data和cache分区        } else if (strcmp(command, "clear_display") == 0) {            // 清除UI显示        }    }    fclose(from_child);    int status;    waitpid(pid, &status, 0);    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {        return INSTALL_ERROR;    }    return INSTALL_SUCCESS;}

需要注意的是:execv(binary,args)的作用就是去执行binary程序,这个程序的实质就是去解析update.zip包中的updater-script脚本中的命令并执行。由此,Recovery服务就进入了实际安装update.zip包的过程。继续分析,则需要分析updater.c文件的main函数了。

2 0