我是靠谱客的博主 洁净外套,最近开发中收集的这篇文章主要介绍Android Recovery OTA升级(二)—— Recovery源码解析目录概述Recovery源码解析main函数,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

  • 目录
  • 概述
  • 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.fstabn");
        return;
    }

    // 在fstab结构体中增加/tmp分区
    ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk", 0);
    if (ret < 0) {
        LOGE("failed to add /tmp entry to fstabn");
        fs_mgr_free_fstab(fstab);
        fstab = NULL;
        return;
    }

    printf("recovery filesystem tablen");
    printf("=========================n");
    for (i = 0; i < fstab->num_entries; i ++) {
        Volume* v = &fstab->recs[i];
        printf("  %d %s %s %s %lldn", 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 %sn", 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, "rn");
                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 == '') {
                char buffer[20] = "1/";
                strncat(buffer, optarg, sizeof(buffer)-3);
                stage = strdup(buffer);
            }
        case 'p': shutdown_after = true; break;
        case '?':
            LOGE("Invalid command argumentn");
            continue;
        }
    }
}

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


install_package

源码如下:

// 卸载除了/tmp和/cache的其他分区
int setup_install_mounts() {
    if (fstab == NULL) {
        LOGE("can't set up install mounts: no fstab loadedn");
        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 %sn", 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: %sn", 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 32

Certificate* 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 1

int 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函数了。

最后

以上就是洁净外套为你收集整理的Android Recovery OTA升级(二)—— Recovery源码解析目录概述Recovery源码解析main函数的全部内容,希望文章能够帮你解决Android Recovery OTA升级(二)—— Recovery源码解析目录概述Recovery源码解析main函数所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部