您的位置:首页 > 移动开发 > Android开发

Android 8.0 OTA 分析——recovery

2018-02-13 10:56 155 查看
这里是OTA分析的第一部分,主要是recovery模块,后面会对脚本进行分析

如果有分析不到位的地方请指出,共同学习进步

一、首先来看下升级的入口

1.上层写入命令主动升级:

这是最常见的方式,负责升级的APK会向command中写入升级的信息,比如”–update_package”参数和升级包的路径等

[recovery.cpp]
if (update_package != NULL) {
// It's not entirely true that we will modify the flash. But we want
// to log the update attempt since update_package is non-NULL.
modified_flash = true;

if (!is_battery_ok()) {
ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",
BATTERY_OK_PERCENTAGE);
// Log the error code to last_install when installation skips due to
// low battery.
log_failure_code(kLowBattery, update_package);
status = INSTALL_SKIPPED;
} else if (bootreason_in_blacklist()) {
// Skip update-on-reboot when bootreason is kernel_panic or similar
ui->Print("bootreason is in the blacklist; skip OTA installation\n");
log_failure_code(kBootreasonInBlacklist, update_package);
status = INSTALL_SKIPPED;
} else {
status = install_package(update_package, &should_wipe_cache,
TEMPORARY_INSTALL_FILE, true, retry_count);
if (status == INSTALL_SUCCESS && should_wipe_cache) {
wipe_cache(false, device);
}
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
// When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
// times before we abandon this OTA update.
if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
copy_logs();
set_retry_bootloader_message(retry_count, args);
// Print retry count on screen.
ui->Print("Retry attempt %d\n", retry_count);

// Reboot and retry the update
if (!reboot("reboot,recovery")) {
ui->Print("Reboot failed\n");
} else {
while (true) {
pause();
}
}
}
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error
// message is visible.
if (is_ro_debuggable()) {
ui->ShowText(true);
}
}
}
}


从上面的逻辑可以看到,首先要进行电量的检测,然后是启动原因的检查,最后调用
install_package
升级

2.从SD卡安装升级包

这是我们调试经常要操作的地方:

在recovery选择菜单上有这一项,在之前Android 8.0 recovery 流程分析中有说到在
prompt_and_wait
中会检测用户选择:

case Device::APPLY_SDCARD:
{
bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
if (adb) {
status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
} else {
status = apply_from_sdcard(device, &should_wipe_cache);
}

if (status == INSTALL_SUCCESS && should_wipe_cache) {
if (!wipe_cache(false, device)) {
status = INSTALL_ERROR;
}
}

if (status != INSTALL_SUCCESS) {
ui->SetBackground(RecoveryUI::ERROR);
ui->Print("Installation aborted.\n");
copy_logs();
} else if (!ui->IsTextVisible()) {
return Device::NO_ACTION;  // reboot if logs aren't visible
} else {
ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card");
}
}
break;


可以看到,调用了
apply_from_sdcard
方法,这个方法会执行挂载SD等准备操作,在项目前期的过程中可能无法挂载,需要驱动调试

最终也是调用
install_package
进行升级

3.使用adb方式更新

如果没有SD卡,又要本地调试,那么可以使用这是方法:

输入命令
adb sideload XXX.zip
就可以进行升级

这种方式比较特殊,以后会做补充分析,这次先略过,只需知道最终还是通过
install_package
进行升级即可

二、具体的升级过程分析

install_package
是通过
really_install_package
进一步安装,详细看看:

[install.cpp]
static int
really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
//更新屏幕的提示为正在更新
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
ui->Print("Finding update package...\n");
// Give verification half the progress bar…
//设置进度条的类型
ui->SetProgressType(RecoveryUI::DETERMINATE);
//显示进度条
ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
//在log中记录升级包的路径
LOG(INFO) << "Update location: " << path;

// Map the update package into memory.
ui->Print("Opening update package...\n");
//path是升级包的路径,needs_mount参数代表是否需要挂载,1是挂载
if (path && needs_mount) {
//确保升级包所在的目录是挂载的
//这里能看到对加密和非加密有区分,路径名带@的是加密后的手机
if (path[0] == '@') {
ensure_path_mounted(path+1);
} else {
ensure_path_mounted(path);
}
}

//通过mmap把升级包路径映射到内存中
MemMapping map;
if (sysMapFile(path, &map) != 0) {
LOG(ERROR) << "failed to map file";
return INSTALL_CORRUPT;
}

// 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.
ZipArchiveHandle zip;
//从内存中打开升级包
int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip);
if (err != 0) {
LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));

sysReleaseMap(&map);
CloseArchive(zip);
return INSTALL_CORRUPT;
}

// Additionally verify the compatibility of the package.
//这里可以看到调用verify_package_compatibility函数来检查升级包的兼容性
//从函数的注释中可以看到这个和Treble功能有关
//应该是在升级包中有个compatibility.zip,有的话继续判断,没有就返回true
//目前遇到的项目中都没有这个压缩包,所以先不看
//有个疑问是,这个函数里会打出log,但是升级log中没有??
if (!verify_package_compatibility(zip)) {
log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
sysReleaseMap(&map);
CloseArchive(zip);
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, max_temperature);
ui->SetEnableReboot(true);
ui->Print("\n");

sysReleaseMap(&map);
CloseArchive(zip);
return result;
}


1.校验升级包

这里跟踪下
verify_package
,比较重要

[install.cpp]
bool verify_package(const unsigned char* package_data, size_t package_size) {
std::vector<Certificate> loadedKeys;
//从/res/keys文件中装载设备的签名文件
//文件路径在out/target/product/msm8937_64/recovery/root/res/下
//如果在recovery模式下连接adb就会看到手机中对应的目录下有keys文件
if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
LOG(ERROR) << "Failed to load keys";
return false;
}
LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;

// Verify package.
ui->Print("Verifying update package...\n");
auto t0 = std::chrono::system_clock::now();
//这里用了bind这个函数模版,目的是在verify_file函数中调用显示进度条的方法
//校验升级包的签名,判断是否被修改,一般在调试ota升级时会把这段代码进行屏蔽,使本地编译的升级包可以正常升级
int err = verify_file(package_data, package_size, loadedKeys,
std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
if (err != VERIFY_SUCCESS) {
LOG(ERROR) << "Signature verification failed";
LOG(ERROR) << "error: " << kZipVerificationFailure;
return false;
}
return true;
}


2.执行升级

最终执行升级的地方是
try_update_binary


[install.cpp]
static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
std::vector<std::string>& log_buffer, int retry_count,
int* max_temperature) {
//这里是先判断升级包中/META-INF/com/android/metadata文件相关值并写入到last_install中,
//注意,只有差分升级才会写入,全分不会
read_source_target_build(zip, log_buffer);
//创建管道,用于父进程和子进程之间通信
//根据管道的基本知识可以知道,pipefd[0]只能读数据,pipefd[1]只能写数据
int pipefd[2];
pipe(pipefd);

std::vector<std::string> args;
//根据A/B升级是否开启选择对应的update_binary_command函数,这里看non A/B的情况
int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
if (ret) {
close(pipefd[0]);
close(pipefd[1]);
return ret;
}

//下面将args(args就是update_binary_command中的cmd)中的参数转换成数组形式
// Convert the vector to a NULL-terminated char* array suitable for execv.
const char* chr_args[args.size() + 1];
chr_args[args.size()] = nullptr;
for (size_t i = 0; i < args.size(); i++) {
chr_args[i] = args[i].c_str();
}
//fork了一个进程
pid_t pid = fork();

if (pid == -1) {
close(pipefd[0]);
close(pipefd[1]);
PLOG(ERROR) << "Failed to fork update binary";
return INSTALL_ERROR;
}

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(EXIT_FAILURE);
}
//父进程读取数据,需要关闭写的通道
close(pipefd[1]);

std::thread temperature_logger(log_max_temperature, max_temperature);

*wipe_cache = false;
bool retry_update = false;

char buffer[1024];
FILE* from_child = fdopen(pipefd[0], "r");
//通过fgets方法从pipefd[0]中读取子进程传入的值给buffer
//再对这些参数进行解析,执行相应的命令,比如设置进度条之类的
while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
std::string line(buffer);
size_t space = line.find_first_of(" \n");
std::string command(line.substr(0, space));
if (command.empty()) continue;

// Get rid of the leading and trailing space and/or newline.
std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space));

if (command == "progress") {
std::vector<std::string> tokens = android::base::Split(args, " ");
double fraction;
int seconds;
if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) &&
android::base::ParseInt(tokens[1], &seconds)) {
ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
} else {
LOG(ERROR) << "invalid \"progress\" parameters: " << line;
}
} else if
//其它命令省略
……
}
//用完即关好习惯
fclose(from_child);

int status;
//等待子进程执行完毕
waitpid(pid, &status, 0);

finish_log_temperature.notify_one();
temperature_logger.join();

if (retry_update) {
return INSTALL_RETRY;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
return INSTALL_ERROR;
}

return INSTALL_SUCCESS;
}


这里有个比较重要的地方是
read_source_target_build
这里会读取
/META-INF/com/android/metadata


差分包和全分包这个文件不一样,差分包如下:

metadata记录了几个重要数据:
ota-type(BLOCK或者FILE)
post-build(目标版本的fingerprint)
post-build-incremental,pre-build-incremental(这两个和差分升级有关,在`parse_build_number`中会用到)
post-timestamp(时间戳)
pre-build(基础版本的fingerprint)
pre-device(设备名)


另一个比较重要的知识点是pipe(管道)通信,用于父进程(recovery)和子进程(升级进程)

pipe的应用网上例程比较多,可以搜索动手敲一下,这里需要知道
pipefd[0]只能读数据,pipefd[1]只能写数据
所以升级进程向pipefd[1]写数据,recovery从pipefd[0]读数据

上面一个重要的函数是
update_binary_command
在A/B和non A/B下有不同的重载函数,A/B下的函数做了一些升级前的准备工作

[install.cpp]
int update_binary_command(const std::string& path, ZipArchiveHandle zip, int retry_count,
int status_fd, std::vector<std::string>* cmd) {
CHECK(cmd != nullptr);

// On traditional updates we extract the update binary from the package.
static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
ZipString binary_name(UPDATE_BINARY_NAME);
ZipEntry binary_entry;
//在升级包中找到脚本解释器update-binary
if (FindEntry(zip, binary_name, &binary_entry) != 0) {
LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
return INSTALL_CORRUPT;
}

const char* binary = "/tmp/update_binary";
unlink(binary);
//创建/tmp/update_binary文件夹,并赋予对应权限
int fd = creat(binary, 0755);
if (fd == -1) {
PLOG(ERROR) << "Failed to create " << binary;
return INSTALL_ERROR;
}
//将升级包中的脚本解释器拷贝到创建的文件夹中
int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
close(fd);
if (error != 0) {
LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
return INSTALL_ERROR;
}

//cmd实际上是即将传给子进程执行的命令
*cmd = {
binary,//脚本解释器
EXPAND(RECOVERY_API_VERSION),  // defined in Android.mk,这里是3
std::to_string(status_fd),  //status_fd是管道的写入端,应该是让子进程写数据的
path,
};
if (retry_count > 0) {
cmd->push_back("retry");
}
return 0;
}


从上面可以看到,具体命令的执行是由脚本解释器完成的

脚本解释器在升级包的
/META-INF/com/google/android/update-binary
,代码位于
bootable/recovery/updater/
可以单编这个模块,生成updater可执行文件,再重命名push到升级包中

可能有人会问,命令都由update-binary完成了,为什么在
try_update_binary
中仍然有对命令的处理逻辑,这是因为update-binary只能处理一些简单的命令,复杂的命令无法完成,比如在屏幕上打印log的
ui_print
等,这就需要recovery进程来处理

3.升级后的扫尾工作

待续……

三、总结

如果嫌update-binary提供的命令不太好用,可以自己添加额外的命令,在
bootable/recovery/updater/install.cpp
添加额外命令,并且在
RegisterInstallFunctions
中注册一下即可

OTA模块基本上没有什么问题,更多的工作是如何利用升级达到我们需要实现的功能,以及给其它模块擦屁股:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐