Android系統升級流程處理
前段時間負責公司項目的系統升級模塊開發,在開發過程中,對整個升級流程有個基本的瞭解,現在此做一個階段性的總結,方便日後查閱。項目基於高通Android 7.0.1實現, 主要有以下內容:
基本概念
Recovery:Recovery是Android手機備份功能,指的是一種可以對安卓機內部的數據或系統進行修改的模式。
ramdisk:虛擬內存盤,將ram模擬成硬盤來使用的文件系統。相對傳統的磁盤文件系統,這樣可以極大的提高文件訪問速度;ram在掉電後,這部分內容不能保存。ramdisk文件系統是在系統上電後直接從磁盤一次性加載到內存,在整個運行期間都不會有寫操作,所以任何修改都在掉電後丟失。
正常模式下,升級的處理流程
涉及的源碼路徑:
framework/base/core/java/android/os/RecoverySystem.java
framework/base/service/core/java/com/android/server/RecoverySystemService.java
bootable/recovery/uncrypt/uncrypt.rc
bootable/recovery/uncrypt/uncrypt.cpp
bootable/recovery/bootloader_message/Bootloader_message.cpp
升級時序圖
代碼跟蹤
當我們需要進行系統升級或者執行恢復出廠設置等動作時,系統framework通過RecoverySystem封裝了一套完整的API來實現此功能,上層只需要調用接口即可。
該類提供瞭如下接口:
驗證包的簽名
public static void verifyPackage(File packageFile,
ProgressListener listener,
File deviceCertsZipFile)
throws IOException, GeneralSecurityException {
}
這個函數主要是對讀取X509公鑰,並進行驗證。 Recovery模式下,也會對包的簽名進行校驗,這裏針對不同的需求,可選擇是否調用。
安裝升級包
public static void installPackage(Context context, File packageFile)
這裏將會調用系統隱藏的installPackage方法,生成升級命令,並將命令寫入misc分區中。
public static void installPackage(Context context, File packageFile, boolean processed)
throws IOException {
synchronized (sRequestLock) {
LOG_FILE.delete();
// Must delete the file in case it was created by system server.
UNCRYPT_PACKAGE_FILE.delete();
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
// If the package name ends with "_s.zip", it's a security update.
//文件名稱以_s結尾的,表示此次升級爲安全升級模式。
boolean securityUpdate = filename.endsWith("_s.zip");
// If the package is on the /data partition, the package needs to
// be processed (i.e. uncrypt'd). The caller specifies if that has
// been done in 'processed' parameter.
// android7.0對/data分區有加密,所以如果包的路徑在/data分區的話,我們需要從分區中去找包
if (filename.startsWith("/data/")) {
if (processed) {
if (!BLOCK_MAP_FILE.exists()) {
Log.e(TAG, "Package claimed to have been processed but failed to find "
+ "the block map file.");
throw new IOException("Failed to find block map file");
}
} else {
FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
try {
uncryptFile.write(filename + "\n");
} finally {
uncryptFile.close();
}
// UNCRYPT_PACKAGE_FILE needs to be readable and writable
// by system server.
if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
|| !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
}
BLOCK_MAP_FILE.delete();
}
// If the package is on the /data partition, use the block map
// file as the package name instead.
filename = "@/cache/recovery/block.map";
}
//拼接對應的升級命令,這些命令將會在Recovery模式下中使用。
final String filenameArg = "--update_package=" + filename + "\n";
final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
final String securityArg = "--security\n";
String command = filenameArg + localeArg;
if (securityUpdate) {
command += securityArg;
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
//調用RecoverySystemService遠程服務的方法將命令寫入到BCB中
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
// Having set up the BCB (bootloader control block), go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
throw new IOException("Reboot failed (no permissions?)");
}
}
上述函數的功能,主要總結爲製作升級命令,並將其寫入BCB, 然後重啓系統至Recovery, 升級命令的生成,再該函數中已經完成,繼續跟蹤如何將命令寫入BCB中。
寫入BCB的實現在RecoverySystemService.java中,RecoverySystemService是一個遠程服務類,其內部定義了,BinderService實現了IRecoverySystem接口,所以RecoverySystem調用setupBcb實際最終走到RecoverySystemService的內部類BinderService的setupBcb函數。
@Override // Binder call
public boolean setupBcb(String command) {
if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
synchronized (sRequestLock) {
return setupOrClearBcb(true, command);
}
}
此函數未實現相關功能,直接調用服務的方法setupOrClearBcb方法,
private boolean setupOrClearBcb(boolean isSetup, String command) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
//檢查uncrypt服務是否已經運行, uncrypt是一個本地進程
final boolean available = checkAndWaitForUncryptService();
if (!available) {
Slog.e(TAG, "uncrypt service is unavailable.");
return false;
}
if (isSetup) {
//向init進程發送setup-bcb命令
SystemProperties.set("ctl.start", "setup-bcb");
} else {
SystemProperties.set("ctl.start", "clear-bcb");
}
...
...
SystemProperties.set(“ctl.start”, “setup-bcb”);設置屬性後,由init進程響應該動作,啓動一個名稱爲setup-bcb的服務。實際就是執行uncrypt可執行程序。該服務定義在uncrypt.rc中
service setup-bcb /system/bin/uncrypt --setup-bcb
class main
socket uncrypt stream 600 system system
disabled
oneshot
uncrypt被執行,走到此時代碼走到其入口函數處
int main(int argc, char** argv) {
//定義三種動作類型
enum { UNCRYPT, SETUP_BCB, CLEAR_BCB } action;
const char* input_path = nullptr;
const char* map_file = CACHE_BLOCK_MAP.c_str();
if (argc == 2 && strcmp(argv[1], "--clear-bcb") == 0) {
action = CLEAR_BCB;
} else if (argc == 2 && strcmp(argv[1], "--setup-bcb") == 0) {
//我們從屬性設置啓動的參數爲 --setup-bcb, 所以走此處邏輯
action = SETUP_BCB;
} else if (argc == 1) {
action = UNCRYPT;
} else if (argc == 3) {
input_path = argv[1];
map_file = argv[2];
action = UNCRYPT;
} else {
usage(argv[0]);
return 2;
}
//根據平臺讀取分區表
if ((fstab = read_fstab()) == nullptr) {
log_uncrypt_error_code(kUncryptFstabReadError);
return 1;
}
...
...
bool success = false;
switch (action) {
case UNCRYPT:
success = uncrypt_wrapper(input_path, map_file, socket_fd.get());
break;
case SETUP_BCB:
//action 爲SETUP_BCB, 執行setup_back函數。
success = setup_bcb(socket_fd.get());
break;
case CLEAR_BCB:
success = clear_bcb(socket_fd.get());
break;
default: // Should never happen.
ALOGE("Invalid uncrypt action code: %d", action);
return 1;
}
// c13. Read a 4-byte code from the client before uncrypt exits. This is to
// ensure the client to receive the last status code before the socket gets
// destroyed.
int code;
if (android::base::ReadFully(socket_fd.get(), &code, 4)) {
ALOGI(" received %d, exiting now", code);
} else {
ALOGE("failed to read the code: %s", strerror(errno));
}
return success ? 0 : 1;
}
我們此處傳入的參數是 --setup-bcb,所以最終會執行setup_bcb函數,並阻塞在此,等待socket的消息。
static bool setup_bcb(const int socket) {
// c5. receive message length
//讀取接受到的數據長度
//數據長度由RecoverySystemService的setupOrClearBcb函數發送
int length;
if (!android::base::ReadFully(socket, &length, 4)) {
ALOGE("failed to read the length: %s", strerror(errno));
return false;
}
length = ntohl(length);
// c7. receive message
//讀取接受到的數據內容
//這個數據內容由RecoverySystemService的setupOrClearBcb函數發送
std::string content;
content.resize(length);
if (!android::base::ReadFully(socket, &content[0], length)) {
ALOGE("failed to read the length: %s", strerror(errno));
return false;
}
ALOGI(" received command: [%s] (%zu)", content.c_str(), content.size());
...
...
至此,SystemProperties.set(“ctl.start”, “setup-bcb”); 的動作都執行完成。
接着看RecoverySystemService中的setupOrClearBcb
private boolean setupOrClearBcb(boolean isSetup, String command) {
...
...
DataInputStream dis = null;
DataOutputStream dos = null;
try {
//創建輸入流與輸出流與uncrypt進程通信
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
// Send the BCB commands if it's to setup BCB.
if (isSetup) {
//發送命令長度,將會在uncrypt進程中的setup_bcb函數中獲取
dos.writeInt(command.length());
//發送命令內容,將會在uncrypt進程中的setup_bcb函數中獲取
dos.writeBytes(command);
dos.flush();
}
...
...
}
在屬性設置的時候,就已知uncrypt阻塞的其setup_bcb中等待數據的接受,而其接受的數據就是上述函數的DataOutputSteram發送過去的,首先發送數據長度,然後再發送數據內容。uncrypt的setup_bcb在接受到數據後,繼續運行,代碼如下:
static bool setup_bcb(const int socket) {
...
...
// c8. setup the bcb command
std::string err;
//將命令寫入bcb
if (!write_bootloader_message(options, &err)) {
ALOGE("failed to set bootloader message: %s", err.c_str());
//發送失敗,socket回覆 -1
write_status_to_socket(-1, socket);
return false;
}
if (!wipe_package.empty() && !write_wipe_package(wipe_package, &err)) {
ALOGE("failed to set wipe package: %s", err.c_str());
write_status_to_socket(-1, socket);
return false;
}
// c10. send "100" status
//發送成功, socket回覆100
write_status_to_socket(100, socket);
return true;
}
setup_bcb在收到數據之後,會調用write_bootloader_message寫入bcb, 該函數定義在bootloader_message.cpp中
bool write_bootloader_message(const bootloader_message& boot, std::string* err) {
return write_misc_partition(&boot, sizeof(boot), BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err);
}
static bool write_misc_partition(const void* p, size_t size, size_t offset, std::string* err) {
std::string misc_blk_device = get_misc_blk_device(err); //得到misc設備描述符
if (misc_blk_device.empty()) {
return false;
}
//打開misc設備
android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY | O_SYNC));
if (fd.get() == -1) {
*err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
//定位到offset的位置
if (lseek(fd.get(), static_cast<off_t>(offset), SEEK_SET) != static_cast<off_t>(offset)) {
*err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
//寫入升級信息
if (!android::base::WriteFully(fd.get(), p, size)) {
*err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
// TODO: O_SYNC and fsync duplicates each other?
if (fsync(fd.get()) == -1) {
*err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(),
strerror(errno));
return false;
}
return true;
}
uncrypt在執行成功之後,會通過socket返回狀態嗎 100 給RecoverySystemService類中函數setupOrClearBcb的DataInputStream,然後setupOrClearBcb返回true到RecoverySystem的installPackage中,最後由RecoverySystem執行重啓到Recovery模式的動作。
public static void installPackage(Context context, File packageFile, boolean processed){
...
...
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
// Having set up the BCB (bootloader control block), go ahead and reboot
//進入關機流程,系統將重啓至Recovery模式。
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
}
Recovery模式下,升級流程處理
正常模式下,經過一系列的參數設置,通過PowerManagerService執行reboot方法後,系統將會重啓,此時bootloader會檢測到misc分區中,存在系統進入recovery的命令,從而加載recovery.img鏡像進入到Recovery模式下運行。recovery的入口函數定義在recovery.cpp文件中,流程涉及的源碼文件如下:
bootable/recovery/recovery.cpp
bootable/recovery/install.cpp
bootable/recovery/bootloader_message/bootloader_message.cpp
bootable/recovery/roots.cpp
recovery中的邏輯是面向過程的,跟着代碼流程往下走就行,此處不分析日誌重定向以及 --sideload模式的升級,提取出recovery.cpp main函數中比較重要的邏輯如下:
int main(int argc, char **argv) {
......
......
printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start));
load_volume_table(); // 加載分區表
has_cache = volume_for_path(CACHE_ROOT) != nullptr; //確認cache分區是否已掛載
// 獲取recovery命令, 此次進入recovery模式需要執行什麼動作,均由此處獲取的參數決定
std::vector<std::string> args = get_args(argc, argv);
//這塊代碼用於初始化Recovery的UI,就是我們在升級模式看到的進度條,log提示,動畫等
Device* device = make_device();
if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
printf("Quiescent recovery mode.\n");
ui = new StubRecoveryUI();
} else {
ui = device->GetUI();
if (!ui->Init(locale)) {
printf("Failed to initialize UI, use stub UI instead.\n");
ui = new StubRecoveryUI();
}
}
//這裏加載recovery對應的sepolicy權限,該權限定義在/system/sepolicy/recovery.te文件中,可以在此文件中進行內容的增 減
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
//開始啓動UI顯示
device->StartRecovery();
//以上的準備工作完成後,開始進入升級邏輯的處理
int status = INSTALL_SUCCESS;
//recovery命令中存在升級包的路徑
if (update_package != NULL) {
//兩種情況直接跳過升級流程,重啓系統
//電量不足的情況下, 通過is_battery_ok() 函數檢測
//因kernel crash或者類似場景進入recovery模式的, 通過bootreason_in_blacklist()函數檢測
//最後所有條件都滿足的情況下,開始調用安裝升級包的函數
status = install_package(update_package, &should_wipe_cache,
TEMPORARY_INSTALL_FILE, true, retry_count);
}
}
下面逐個分析具體的函數
- load_volume_table() 這個函數的作用是加載分區表, 定義在roots.cpp中,源碼如下
void load_volume_table()
{
int i;
int ret;
//讀取默認的分區表,分區表保存在 /etc/recovery.fstab 中
fstab = fs_mgr_read_fstab_default();
if (!fstab) {
LOG(ERROR) << "failed to read default fstab";
return;
}
ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
if (ret < 0 ) {
LOG(ERROR) << "failed to add /tmp entry to fstab";
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");
}
2.make_device()函數,該函數定義在device.h中,實現方式在android O版本中主要有三種,默認的實現,也就是手機或者平板設備,該函數在default_device.cpp中實現,手錶類的的,則在wear_device.cpp中,VR設備的,定義在vr_device.cpp中,實現方式比較簡單,就是針對不同的設備,創建用於顯示的對象了,這裏我只關注default_device.cpp,中的實現
Device* make_device() {
//這個ScreenRecoveryUI就是實現了Recovery界面繪製的邏輯。
return new Device(new ScreenRecoveryUI);
}
3.install_package函數的實現,該函數就是Recovery開始執行升級動作的開始,定義在install.cpp。
總結: Recovery的啓動時由bootloader檢測到misc分區中的升級信息,從而引導recovery鏡像進入升級模式的過程,Recovery升級流程是面向過程的,入口函數首先會加載recovery模式下的分區信息,然後從啓動的命令參數,或者misc分區,或者command文件中獲取執行參數,然後初始化對應的變量,用來表示我們在Recovery中做何種動作,如執行升級,擦除分區數據等。變量初始完成後,再加載selinux權限,隨後初始化顯示模塊的對象,隨即開始執行具體的邏輯。
進入到install_package函數時,這裏並沒有開始執行升級擦寫的邏輯,先看該函數的前面部分邏輯
......
......
//setup_install_mounts中,對/tmp /cache兩個掛載點是否已掛載以及其他分區是否已卸載進行了判斷,必須
//保證當前只有且僅有/tmp 與/cache兩個分區掛載才能正確執行安裝的邏輯
if (setup_install_mounts() != 0) {
LOG(ERROR) << "failed to set up expected mounts for install; aborting";
result = INSTALL_ERROR;
} else {
//確保分區正確掛載的情況下,繼續進行安裝邏輯的判斷
result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count,
&max_temperature);
}
really_install_package函數聽起來像是真正執行開始擦寫分區的具體實現,然後進入後,發現並沒有,這裏主要是做一些掛載確認,升級包校驗的功能
static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
std::vector<std::string>* log_buffer, int retry_count,
int* max_temperature) {
//前方部分代碼已忽略
if (needs_mount) {
//這裏的邏輯主要是確認一下升級包存放路徑的分區是否已經正常掛載,path[0]這個值之所有有區別,是
//因爲有的設備/data分區是加密的,那麼就不能直接通過寫入指定設備掛載點的路徑來確認升級包路徑
//而是通過dev設備去查找升級包
if (path[0] == '@') {
ensure_path_mounted(path.substr(1).c_str());
} else {
ensure_path_mounted(path.c_str());
}
}
//這裏是內存映射,獲取升級包的內存首地址以及升級包的大小
MemMapping map;
if (!map.MapFile(path)) {
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));
return INSTALL_CORRUPT;
}
// Try to open the package.
ZipArchiveHandle zip;
int err = OpenArchiveFromMemory(map.addr, map.length, path.c_str(), &zip);
if (err != 0) {
LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
CloseArchive(zip);
return INSTALL_CORRUPT;
}
//擴展的兼容性檢查,從Android 8.0開始支持,但是默認關閉
// Additionally verify the compatibility of the package.
if (!verify_package_compatibility(zip)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
CloseArchive(zip);
return INSTALL_CORRUPT;
}
//跑到這裏纔開始執行升級內容的邏輯。
int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
從上面代碼分析看到,運行到try_update_binary的時候,我們已經把一切都準備了,這個時候,該執行分區擦寫動作了吧,繼續看try_update_binary函數
//忽略部分代碼
//pipe是linux中的管道通信,接下來的進度條,動畫更新需要依賴管道通信來進行
int pipefd[2];
pipe(pipefd);
std::vector<std::string> args;
//AB 升級的模式下,調用/sbin/update_engine_sideload可執行程序進行升級擦寫
#ifdef AB_OTA_UPDATER
int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count,
pipefd[1], &args);
#else
//普通模式下,使用update-binary可執行程序進行升級擦寫邏輯,這個update-binary就是打包在升級包中的linux可
//執行程序,我們在做包的時候,系統就已經通過python腳本自動將程序打入升級包中了,它的原名叫做libupdater,
//源代碼在 bootable/recovery/updater下
int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1],
&args);
#endif
if (ret) {
close(pipefd[0]);
close(pipefd[1]);
return ret;
}
//下面的邏輯就是系通過管道跟update-binary可執行程序通信,更新升級進度,界面UI顯示,LOG提示等。
char buffer[1024];
FILE* from_child = fdopen(pipefd[0], "r");
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 (command == "set_progress") {
std::vector<std::string> tokens = android::base::Split(args, " ");
double fraction;
if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) {
ui->SetProgress(fraction);
} else {
LOG(ERROR) << "invalid \"set_progress\" parameters: " << line;
}
} else if (command == "ui_print") {
ui->PrintOnScreenOnly("%s\n", args.c_str());
fflush(stdout);
} else if (command == "wipe_cache") {
*wipe_cache = true;
} else if (command == "clear_display") {
ui->SetBackground(RecoveryUI::NONE);
} else if (command == "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).
ui->SetEnableReboot(true);
} else if (command == "retry_update") {
retry_update = true;
} else if (command == "log") {
if (!args.empty()) {
// Save the logging request from updater and write to last_install later.
log_buffer->push_back(args);
} else {
LOG(ERROR) << "invalid \"log\" parameters: " << line;
}
} else {
LOG(ERROR) << "unknown command [" << command << "]";
}
}
fclose(from_child);
int status;
waitpid(pid, &status, 0);
總結:進入到install.cpp文件的時候,才真正開始進行一些升級包邏輯的處理,如升級包加載,校驗等,最後通過執行從升級包中解壓出來的update-binary可執行文件,執行真正的磁盤內容擦寫動作。
update-binary的功能
在介紹這個可執行程序之前,先看一下android自定義的升級腳本描述,update-script文件
getprop("ro.product.device") == "msm8917" || abort("E3004: This package is for \"msm8917\" devices; this is a \"" + getprop("ro.product.device") + "\".");
ui_print("....");
show_progress(0.750000, 0);
ui_print("Patching system image unconditionally...");
block_image_update("/dev/block/bootdevice/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") ||
abort("E1001: Failed to update system image.");
show_progress(0.050000, 5);
package_extract_file("boot.img", "/dev/block/bootdevice/by-name/boot");
show_progress(0.200000, 10);
# ---- FullOTA_InstallEnd ----
# ---- FullOTA_InstallEnd_MMC ----
# ---- radio update tasks ----
ui_print("Patching firmware images...");
ifelse(msm.boot_update("main"), (
package_extract_file("firmware-update/cmnlib64.mbn", "/dev/block/bootdevice/by-name/cmnlib64");
package_extract_file("firmware-update/cmnlib.mbn", "/dev/block/bootdevice/by-name/cmnlib");
package_extract_file("firmware-update/rpm.mbn", "/dev/block/bootdevice/by-name/rpm");
package_extract_file("firmware-update/tz.mbn", "/dev/block/bootdevice/by-name/tz");
package_extract_file("firmware-update/emmc_appsboot.mbn", "/dev/block/bootdevice/by-name/aboot");
package_extract_file("firmware-update/sbl1.mbn", "/dev/block/bootdevice/by-name/sbl1");
package_extract_file("firmware-update/devcfg.mbn", "/dev/block/bootdevice/by-name/devcfg");
), "");
ifelse(msm.boot_update("backup"), (
package_extract_file("firmware-update/cmnlib64.mbn", "/dev/block/bootdevice/by-name/cmnlib64bak");
package_extract_file("firmware-update/cmnlib.mbn", "/dev/block/bootdevice/by-name/cmnlibbak");
package_extract_file("firmware-update/rpm.mbn", "/dev/block/bootdevice/by-name/rpmbak");
package_extract_file("firmware-update/tz.mbn", "/dev/block/bootdevice/by-name/tzbak");
package_extract_file("firmware-update/emmc_appsboot.mbn", "/dev/block/bootdevice/by-name/abootbak");
package_extract_file("firmware-update/sbl1.mbn", "/dev/block/bootdevice/by-name/sbl1bak");
package_extract_file("firmware-update/devcfg.mbn", "/dev/block/bootdevice/by-name/devcfgbak");
), "");
msm.boot_update("finalize");
package_extract_file("firmware-update/splash.img", "/dev/block/bootdevice/by-name/splash");
package_extract_file("firmware-update/NON-HLOS.bin", "/dev/block/bootdevice/by-name/modem");
ui_print("OTA_InstallEnd");
set_progress(1.000000);
像ui_print,package_extract_file,show_progress,getprop等關鍵字recovery中有專門的語言描述,針對對應的關鍵字做對應的解釋以及對應的執行函數,接下來看下update-binary的入口代碼,這部分定義在bootable/recovery/updater中
//上面忽略了部分代碼
// Set up the pipe for sending commands back to the parent process.
//這裏第二個參數就是我們在install.cpp的try_update_binary中創建的管道文件句柄,接下來binary執行過程中,
//會通過這個句柄跟install.cpp通信,用來刷新UI跟設置參數。
int fd = atoi(argv[2]);
FILE* cmd_pipe = fdopen(fd, "wb");
setlinebuf(cmd_pipe);
// Extract the script from the package.
//從升級包中解壓update-script文件, argv的參數,都是從try_update_binary函數中傳過來的
const char* package_filename = argv[3];
MemMapping map;
if (!map.MapFile(package_filename)) {
LOG(ERROR) << "failed to map package " << argv[3];
return 3;
}
ZipArchiveHandle za;
int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
if (open_err != 0) {
LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
CloseArchive(za);
return 3;
}
ZipString script_name(SCRIPT_NAME);
ZipEntry script_entry;
int find_err = FindEntry(za, script_name, &script_entry);
if (find_err != 0) {
LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
<< ErrorCodeString(find_err);
CloseArchive(za);
return 4;
}
std::string script;
script.resize(script_entry.uncompressed_length);
int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
script_entry.uncompressed_length);
if (extract_err != 0) {
LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
CloseArchive(za);
return 5;
}
// Configure edify's functions.
//這裏就是開始構建關鍵字解析函數,就是把script中定義的關鍵字,跟具體執行的函數掛鉤。
RegisterBuiltins();
RegisterInstallFunctions();
RegisterBlockImageFunctions();
RegisterDeviceExtensions();
// Parse the script.
//解析update-script文件
std::unique_ptr<Expr> root;
int error_count = 0;
int error = parse_string(script.c_str(), &root, &error_count);
if (error != 0 || error_count > 0) {
LOG(ERROR) << error_count << " parse errors";
CloseArchive(za);
return 6;
}
//加載selinux權限
sehandle = selinux_android_file_context_handle();
selinux_android_set_sehandle(sehandle);
if (!sehandle) {
fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
}
// Evaluate the parsed script.
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(script, &updater_info);
if (argc == 5) {
if (strcmp(argv[4], "retry") == 0) {
state.is_retry = true;
} else {
printf("unexpected argument: %s", argv[4]);
}
}
ota_io_init(za, state.is_retry);
std::string result;
//開始執行script中定義的函數。
bool status = Evaluate(&state, root, &result);
從入口函數知道了script中描述的關鍵字都有對應的方法,那下面就針對上述貼出來的update-script文件流程作分析。
首先,需要判斷當前平臺是否匹配,不能讓其他平臺的包互相升級。
getprop(“ro.product.device”) == “msm8917” || abort(“E3004: This package is for “msm8917” devices; this is a “” + getprop(“ro.product.device”) + “”.”);
這個getprop對應 bootable/recovery/updater/install.cpp中的GetpropFn,實際上就是通過系統的GetProperty獲取系統的ro屬性值,屬性值的key = ro.product.device, 這個屬性值,我們也可以通過adb命令,輸入 getprop ro.product.device拿到
Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
std::string key;
if (!Evaluate(state, argv[0], &key)) {
return nullptr;
}
std::string value = android::base::GetProperty(key, "");
return StringValue(value);
}
在判斷平臺設備匹配時,接下來打印一行日誌
ui_print("…"); ui_print對應的函數也定義在bootable/recovery/updater/install.cpp中,最終通過pipe管道把要打印的信息發送到bootable/recovery/install.cpp中接收
//bootable/recovery/updater/install.cpp
static void uiPrint(State* state, const std::string& buffer) {
UpdaterInfo* ui = static_cast<UpdaterInfo*>(state->cookie);
// "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "".
// So skip sending empty strings to UI.
std::vector<std::string> lines = android::base::Split(buffer, "\n");
for (auto& line : lines) {
if (!line.empty()) {
fprintf(ui->cmd_pipe, "ui_print %s\n", line.c_str());
}
}
// On the updater side, we need to dump the contents to stderr (which has
// been redirected to the log file). Because the recovery will only print
// the contents to screen when processing pipe command ui_print.
LOG(INFO) << buffer;
}
//bootable/recovery/install.cpp
else if (command == "ui_print") {
//把接收到的消息繪製在屏幕上
ui->PrintOnScreenOnly("%s\n", args.c_str());
fflush(stdout);
}
show_progress的流程也類似ui_print,這裏就不再描述了,接下來看block_image_update,這個方法用於全量升級時,升級system分區
block_image_update("/dev/block/bootdevice/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || abort("E1001: Failed to update system image.");
函數定義在bootable/recovery/updater/blockimg.cpp文件中
Value* BlockImageUpdateFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
const Command commands[] = {
{ "bsdiff", PerformCommandDiff },
{ "erase", PerformCommandErase },
{ "free", PerformCommandFree },
{ "imgdiff", PerformCommandDiff },
{ "move", PerformCommandMove },
{ "new", PerformCommandNew },
{ "stash", PerformCommandStash },
{ "zero", PerformCommandZero }
};
return PerformBlockImageUpdate(name, state, argv, commands,
sizeof(commands) / sizeof(commands[0]), false);
}
block_image_update函數中對應有許多的子命令,這些子命令分別在不同的函數中去實現功能,對於全量升級來說,在升級包中的system.transfer.list文件找那個有使用這些命令,這些命令都在升級包製作的時候,系統寫入的
erase 24,74806,97792,133684,163328,198446,228864,263623,294400,518633,523776,524802,556544,557570,589312,590338,622080,623106,654848,655874,687616,688642,720384,721410,753152
new 2,0,1024
new 2,1024,2048
new 4,2048,2478,2479,3073
new 4,3073,4054,4055,4098
new 2,4098,5122
new 4,5122,6090,6091,6147
new 2,6147,7171
new 4,7171,7284,7285,8196
new 2,8196,9220
new 6,9220,9397,9398,10143,10144,10246
new 10,10246,11029,11030,11075,11076,11213,11214,11236,11237,11274
new 2,16922,17946
.....
這裏我截取了一部分分析,erase對應的是PerformCommandErase函數,這個就是用ioctl命令來讓驅動去擦除指定設備塊的內容
if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) {
PLOG(ERROR) << "BLKDISCARD ioctl failed";
return -1;
}
new對應PerformCommandNew函數,將指定內容寫入磁盤
ssize_t status = write(fd, buf, nbyte);
if (status == -1 && errno == EIO) {
have_eio_error = true;
}
其他命令等都在blockimg.cpp中實現,這裏不再深入。
update-binary執行完成後,繼續回到recovery進程中,如果安裝成功的情況下,最後保存recovery日誌,最後讓系統重啓
finish_recovery();
switch (after) {
case Device::SHUTDOWN:
ui->Print("Shutting down...\n");
//通知關機
android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");
break;
case Device::REBOOT_BOOTLOADER:
ui->Print("Rebooting to bootloader...\n");
//通知重啓
android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");
break;
default:
ui->Print("Rebooting...\n");
reboot("reboot,");
break;
}
//一直等待,直到關機或者重啓
while (true) {
pause();
}