概述
目录
- 目录
- 概述
- 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 == '