Recovery啟動流程--recovery.cpp分析
- 2019 年 10 月 7 日
- 筆記
這篇文章主要通過分析高通recovery目錄下的recovery.cpp源碼,對recovery啟動流程有一個宏觀的了解。
當開機以後,在lk階段,如果是recovery,會設置boot_into_recovery=1,然後讀取recovery.img鏡像,把recovery.img的地址和ramdisk等資訊作為參數啟動kernel,從而進入recovery模式,下面進行簡單的分析。
為什麼要分析recovery.cpp這個文件?
下面的程式碼位於bootable/recovery/etc/init.rc,由此可知,進入recovery模式後會執行sbin /recovery,此文件是bootable/recovery/recovery.cpp生成(可查看對應目錄的Android.mk查看),所以recovery.cpp是recovery模式的入口。
service recovery /sbin/recovery seclabel u:r:recovery:s0
1. 前期準備:
首先列出recovery流程的幾個重要點,接著會詳細分析
- 載入recovery.fstab分區表
- 解析傳入的參數
- recovery介面相關的設置
- 執行命令
- 如果沒有命令,等待用戶輸入
- 結束recovery
bootable/recovery/recovery.cpp int main(int argc, char **argv) { // Take last pmsg contents and rewrite it to the current pmsg session. static const char filter[] = "recovery/"; // Do we need to rotate? bool doRotate = false; __android_log_pmsg_file_read( LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &doRotate); //這裡的意思暫時不理解 // Take action to refresh pmsg contents __android_log_pmsg_file_read( LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &doRotate); // If this binary is started with the single argument "--adbd", // instead of being the normal recovery binary, it turns into kind // of a stripped-down version of adbd that only supports the // 'sideload' command. Note this must be a real argument, not // anything in the command file or bootloader control block; the // only way recovery should be run with this argument is when it // starts a copy of itself from the apply_from_adb() function. //如果二進位文件使用單個參數"--adbd"啟動 //而不是正常的recovery啟動(不帶參數即為正常啟動) //它變成精簡版命令時只支援sideload命令。它必須是一個正確可用的參數 //不在/cache/recovery/command中,也不受B2B控制 //是apply_from_adb()的副本 if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { adb_server_main(0, DEFAULT_ADB_PORT, -1); return 0; } time_t start = time(NULL); // redirect_stdio should be called only in non-sideload mode. Otherwise // we may have two logger instances with different timestamps. redirect_stdio(TEMPORARY_LOG_FILE); printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table(); //從上面建立的分區表資訊中讀取是否有cache分區,因為log等重要資訊都存在cache分區里 has_cache = volume_for_path(CACHE_ROOT) != nullptr; //從傳入的參數或/cache/recovery/command文件中得到相應的命令 get_args(&argc, &argv); const char *send_intent = NULL; const char *update_package = NULL; bool should_wipe_data = false; bool should_wipe_cache = false; bool should_wipe_ab = false; size_t wipe_package_size = 0; bool show_text = false; bool sideload = false; bool sideload_auto_reboot = false; bool just_exit = false; bool shutdown_after = false; int retry_count = 0; bool security_update = false; int status = INSTALL_SUCCESS; bool mount_required = true; int arg; int option_index; //while循環解析command或者傳入的參數,並把對應的功能設置為true或給相應的變數賦值 while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { switch (arg) { case 'i': send_intent = optarg; break; case 'n': android::base::ParseInt(optarg, &retry_count, 0); break; case 'u': update_package = optarg; break; case 'w': should_wipe_data = true; break; case 'c': should_wipe_cache = true; break; case 't': show_text = true; break; case 's': sideload = true; break; case 'a': sideload = true; sideload_auto_reboot = true; 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); } break; } case 'p': shutdown_after = true; break; case 'r': reason = optarg; break; case 'e': security_update = true; break; case 'y': security_mode = atoi(optarg); printf("security_mode is [%d]*****n", security_mode); break; case 0: { if (strcmp(OPTIONS[option_index].name, "wipe_ab") == 0) { should_wipe_ab = true; break; } else if (strcmp(OPTIONS[option_index].name, "wipe_package_size") == 0) { android::base::ParseUint(optarg, &wipe_package_size); break; } break; } case '?': LOGE("Invalid command argumentn"); continue; } } if (locale == nullptr && has_cache) { load_locale_from_cache(); } printf("locale is [%s]n", locale); printf("stage is [%s]n", stage); printf("reason is [%s]n", reason); Device* device = make_device(); ui = device->GetUI(); gCurrentUI = ui; ui->SetLocale(locale); ui->Init(); // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); int st_cur, st_max; if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } ui->SetBackground(RecoveryUI::NONE); if (show_text) ui->ShowText(true); struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/file_contexts" } }; sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); if (!sehandle) { ui->Print("Warning: No file_contextsn"); } device->StartRecovery(); printf("Command:"); for (arg = 0; arg < argc; arg++) { printf(" "%s"", argv[arg]); } printf("n"); if (update_package) { // For backwards compatibility on the cache partition only, if // we're given an old 'root' path "CACHE:foo", change it to // "/cache/foo". if (strncmp(update_package, "CACHE:", 6) == 0) { int len = strlen(update_package) + 10; char* modified_path = (char*)malloc(len); if (modified_path) { strlcpy(modified_path, "/cache/", len); strlcat(modified_path, update_package+6, len); printf("(replacing path "%s" with "%s")n", update_package, modified_path); update_package = modified_path; } else printf("modified_path allocation failedn"); } if (!strncmp("/sdcard", update_package, 7)) { //If this is a UFS device lets mount the sdcard ourselves.Depending //on if the device is UFS or EMMC based the path to the sdcard //device changes so we cannot rely on the block dev path from //recovery.fstab if (is_ufs_dev()) { if(do_sdcard_mount_for_ufs() != 0) { status = INSTALL_ERROR; goto error; } if (ensure_path_mounted("/cache") != 0 || ensure_path_mounted("/tmp") != 0) { ui->Print("nFailed to mount tmp/cache partitionn"); status = INSTALL_ERROR; goto error; } mount_required = false; } else { ui->Print("Update via sdcard on EMMC dev. Using path from fstabn"); } } } printf("n"); property_list(print_property, NULL); property_get("ro.build.display.id", recovery_version, ""); printf("n"); /*if(check_identification_code() < 0){ identification_code = -1; ui->Print("check_identification_code failed.n"); }*/ 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 installationn"); log_failure_code(kBootreasonInBlacklist, update_package); status = INSTALL_SKIPPED; } else { status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, mount_required, retry_count); if (status == INSTALL_SUCCESS) { ota_completed = true; } 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, argc, argv); // Print retry count on screen. ui->Print("Retry attempt %dn", retry_count); // Reboot and retry the update int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); if (ret < 0) { ui->Print("Reboot failedn"); } 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); } } } } else if (should_wipe_data) { if (!wipe_data(false, device)) { status = INSTALL_ERROR; } } else if (should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; } } else if (should_wipe_ab) { if (!wipe_ab_device(wipe_package_size)) { status = INSTALL_ERROR; } } else if (sideload) { // 'adb reboot sideload' acts the same as user presses key combinations // to enter the sideload mode. When 'sideload-auto-reboot' is used, text // display will NOT be turned on by default. And it will reboot after // sideload finishes even if there are errors. Unless one turns on the // text display during the installation. This is to enable automated // testing. if (!sideload_auto_reboot) { ui->ShowText(true); } status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); if (status == INSTALL_SUCCESS) { ota_completed = true; } if (status == INSTALL_SUCCESS && should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; } } ui->Print("nInstall from ADB complete (status: %d).n", status); if (sideload_auto_reboot) { ui->Print("Rebooting automatically.n"); } } else if (!just_exit) { status = INSTALL_NONE; // No command specified ui->SetBackground(RecoveryUI::NO_COMMAND); // http://b/17489952 // If this is an eng or userdebug build, automatically turn on the // text display if no command is specified. if (is_ro_debuggable()) { ui->ShowText(true); } } error: if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { copy_logs(); ui->SetBackground(RecoveryUI::ERROR); } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) || ui->IsTextVisible()) { Device::BuiltinAction temp = prompt_and_wait(device, status); if (temp != Device::NO_ACTION) { after = temp; } } // Save logs and clean up before rebooting or shutting down. finish_recovery(send_intent); switch (after) { case Device::SHUTDOWN: ui->Print("Shutting down...n"); property_set(ANDROID_RB_PROPERTY, "shutdown,"); break; case Device::REBOOT_BOOTLOADER: ui->Print("Rebooting to bootloader...n"); property_set(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; default: ui->Print("Rebooting...n"); property_set(ANDROID_RB_PROPERTY, "reboot,"); break; } while (true) { pause(); } // Should be unreachable. return EXIT_SUCCESS; }首先:
1.1 啟動adb進程
啟動adbd進程,為了使用adb sideload命令
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { adb_server_main(0, DEFAULT_ADB_PORT, -1); return 0; }1.2 重定向到recovery.log
重定向標準輸出和標準出錯到/tmp/recovery.log 這個文件里
redirect_stdio(TEMPORARY_LOG_FILE);
1.3 裝載分區表
做完這些步驟以後,會初始化並裝載recovery的分區表recovery.fstab
void load_volume_table() { int i; int ret; fstab = fs_mgr_read_fstab("/etc/recovery.fstab"); if (!fstab) { LOGE("failed to read /etc/recovery.fstabn"); return; } //將對應的資訊加入到一條鏈表中 ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk"); //如果load到的分區表為空,後面做釋放操作 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"); //到這一步,列印分區表資訊,這類資訊在 //recovery啟動的時候的log可以看到 //分別是以下 //編號| 掛載節點| 文件系統類型| 塊設備| 長度 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"); }這裡主要看如何裝載分區表的流程,先來看看recovery.fstab
/dev/block/bootdevice/by-name/system /system ext4 ro,barrier=1 wait /dev/block/bootdevice/by-name/cache /cache ext4 noatime,nosuid,nodev,barrier=1,data=ordered wait,check /dev/block/bootdevice/by-name/userdata /data ext4 noatime,nosuid,nodev,barrier=1,data=ordered,noauto_da_alloc wait,check,length=-16384 /dev/block/mmcblk1p1 /sdcard vfat nosuid,nodev wait /dev/block/sda1 /usbotg vfat nosuid,nodev wait /dev/block/bootdevice/by-name/boot /boot emmc defaults defaults /dev/block/bootdevice/by-name/recovery /recovery emmc defaults defaults /dev/block/bootdevice/by-name/misc /misc emmc defaults defaults
掛載完相應的分區以後,就需要獲取命令參數,因為只有掛載了對應的分區,才能訪問到前面要寫入command的這個文件,這樣我們才能正確的打開文件,如果分區都沒找到,那麼當然就找不到分區上的文件,上面這個步驟是至關重要的。
//從上面建立的分區表資訊中讀取是否有cache分區,因為log等重要資訊都存在cache分區里 has_cache = volume_for_path(CACHE_ROOT) != nullptr;
1.4 獲取相應參數:
從傳入的參數或/cache/recovery/command文件中得到相應的命令
get_args(&argc, &argv); //從傳入的參數或/cache/recovery/command文件中得到相應的命令
while循環解析command或者傳入的參數,並把對應的功能設置為true或給相應的變數賦值
獲取到對應的命令,就會執行對應的標誌,後面會根據標誌來執行對應的操作。
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { //while循環解析command或者傳入的參數,並把對應的功能設置為true或給相應的變數賦值 switch (arg) { case 'i': send_intent = optarg; break; case 'u': update_package = optarg; break; case 'w': should_wipe_data = true; break; case 'c': should_wipe_cache = true; break; case 't': show_text = true; break; case 's': sideload = true; break; case 'a': sideload = true; sideload_auto_reboot = true; 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); } break; } case 'p': shutdown_after = true; break; case 'r': reason = optarg; break; case '?': LOGE("Invalid command argumentn"); continue; } }get_args()函數的主要作用是建立recovery的啟動參數,如果系統啟動recovery時已經傳遞了啟動參數,那麼這個函數只是把啟動參數的內容複製到函數的參數boot對象中,否則函數會首先從/misc分區中獲取命令字元串來構建啟動參數。如果/misc分區下沒有內容,則嘗試打開/cache/recovery/command文件並讀取文件的內容來建立啟動參數。從這個函數我們可以看到,更新系統最簡單的方式是把更新命令寫到/cache/recovery/command文件中。get_args()函數是通過get_bootloader_message()函數來讀取/misc分區的數據的
get_args()函數是通過read_bootloader_message()函數來讀取/misc分區的數據的,read_bootloader_message()函數的程式碼如下所示:
read_bootloader_message --> read_misc_partition --> get_misc_blk_device
static std::string get_misc_blk_device(std::string* err) { struct fstab* fstab = read_fstab(err); if (fstab == nullptr) { return ""; } fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab, "/misc"); if (record == nullptr) { *err = "failed to find /misc partition"; return ""; } return record->blk_device; }從read_bootloader_message()函數的程式碼可以看到,它打開/misc分區來讀取數據;
get_args()函數的結尾調用了set_bootloader_message()函數,函數的作用是把啟動參數的資訊又保存到了/misc分區中。這樣做的目的是防止升級過程中發生崩潰,這樣重啟後仍然可以從/misc分區中讀取更新的命令,繼續進行更新操作。這也是為什麼get_args()函數要從幾個地方讀取啟動參數的原因。
1.5 load_locale_from_cache()函數
load_locale_from_cache不展開說了,大致過程就是從之前解析分區表得到的fstab中查詢/cache/recovery/last_locale文件是否存在,如果存在就讀取裡面的值
/cache/recovery/last_locale關係到中文顯示或者英文顯示
1.6 設置UI模型
UI模型詳情參考這篇文章:
//創建設備 Device* device = make_device(); //獲取UI ui = device->GetUI(); //設置當前的UI gCurrentUI = ui; //設置UI的語言資訊 ui->SetLocale(locale); //UI初始化 ui->Init(); //這裡會調用SetSystemUpdateText 方法把顯示哪種文字的選擇存在installing_text中,後面解析具體命令的時候會調用GetCurrentText來顯示 ui->SetSystemUpdateText(security_update); //設置介面上是否能夠顯示字元,使能ui->print函數開關 if (show_text) ui->ShowText(true); //設置selinux許可權,一般我會把selinux 給disabled struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/file_contexts" } }; sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); if (!sehandle) { ui->Print("Warning: No file_contextsn"); } if (!sehandle) { ui->Print("Warning: No file_contextsn"); } //虛函數,沒有做什麼流程 device->StartRecovery(); printf("Command:"); for (arg = 0; arg < argc; arg++) { printf(" "%s"", argv[arg]); } printf("n");2. 重要環節-升級
2.1 Recovery介面升級
//如果update_package(也就是要升級的OTA包)不為空的情況下 //這裡要對升級包的路徑做一下路徑轉換,這裡可以自由訂製自己升級包的路徑 if (update_package) { // For backwards compatibility on the cache partition only, if // we're given an old 'root' path "CACHE:foo", change it to // "/cache/foo". //這裡就是做轉換的方法 //先比較傳進來的recovery參數的前6個byte是否是CACHE //如果是將其路徑轉化為/cache/CACHE: ...... if (strncmp(update_package, "CACHE:", 6) == 0) { int len = strlen(update_package) + 10; char* modified_path = (char*)malloc(len); if (modified_path) { strlcpy(modified_path, "/cache/", len); strlcat(modified_path, update_package+6, len); printf("(replacing path "%s" with "%s")n", update_package, modified_path); //這個update_package就是轉換後的路徑 update_package = modified_path; } else printf("modified_path allocation failedn"); } //這裡修改為我們自己的/sdcard路徑 if (!strncmp("/sdcard", update_package, 7)) { //If this is a UFS device lets mount the sdcard ourselves.Depending //on if the device is UFS or EMMC based the path to the sdcard //device changes so we cannot rely on the block dev path from //recovery.fstab if (is_ufs_dev()) { if(do_sdcard_mount_for_ufs() != 0) { status = INSTALL_ERROR; goto error; } if (ensure_path_mounted("/cache") != 0 || ensure_path_mounted("/tmp") != 0) { ui->Print("nFailed to mount tmp/cache partitionn"); status = INSTALL_ERROR; goto error; } mount_required = false; } else { ui->Print("Update via sdcard on EMMC dev. Using path from fstabn"); } } } printf("n"); property_list(print_property, NULL); //獲取屬性,這裡應該是從一個文件中找到ro.build.display.id //獲取recovery的版本資訊 property_get("ro.build.display.id", recovery_version, ""); printf("n"); 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 installationn"); log_failure_code(kBootreasonInBlacklist, update_package); status = INSTALL_SKIPPED; } else { status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, mount_required, retry_count); if (status == INSTALL_SUCCESS) { ota_completed = true; } 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, argc, argv); // Print retry count on screen. ui->Print("Retry attempt %dn", retry_count); // Reboot and retry the update int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); if (ret < 0) { ui->Print("Reboot failedn"); } 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); } } } }2.2 install_package函數
我們來分析一波這個install_package函數:
int install_package(const char* path, bool* wipe_cache, const char* install_file, bool needs_mount, int retry_count) { modified_flash = true; auto start = std::chrono::system_clock::now(); int result = 0; std::vector<std::string> log_buffer; timeout_exit_stop(); if (needs_mount == true) result = setup_install_mounts();//確保/tmp和/cache分區已經mount if (result != 0) { LOGE("failed to set up expected mounts for install; abortingn"); result = INSTALL_ERROR; } else { //一般來到這裡 result = really_install_package(path, wipe_cache, needs_mount, log_buffer, retry_count); } // Measure the time spent to apply OTA update in seconds. std::chrono::duration<double> duration = std::chrono::system_clock::now() - start; int time_total = static_cast<int>(duration.count()); if (ensure_path_mounted(UNCRYPT_STATUS) != 0) { LOGW("Can't mount %sn", UNCRYPT_STATUS); } else { std::string uncrypt_status; if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) { LOGW("failed to read uncrypt status: %sn", strerror(errno)); } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) { LOGW("corrupted uncrypt_status: %s: %sn", uncrypt_status.c_str(), strerror(errno)); } else { log_buffer.push_back(android::base::Trim(uncrypt_status)); } } // The first two lines need to be the package name and install result. std::vector<std::string> log_header = { path, result == INSTALL_SUCCESS ? "1" : "0", "time_total: " + std::to_string(time_total), "retry: " + std::to_string(retry_count), }; std::string log_content = android::base::Join(log_header, "n") + "n" + android::base::Join(log_buffer, "n"); if (!android::base::WriteStringToFile(log_content, install_file)) { LOGE("failed to write %s: %sn", install_file, strerror(errno)); } // Write a copy into last_log. LOGI("%sn", log_content.c_str()); timeout_exit_start(); return result; }我們來到really_install_package函數中:
static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, std::vector<std::string>& log_buffer, int retry_count) { 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); LOGI("Update location: %sn", path); // Map the update package into memory. ui->Print("Opening update package...n"); if (path && needs_mount) { //確保更新包所在的路徑已經moun if (path[0] == '@') { ensure_path_mounted(path+1); } else { ensure_path_mounted(path); } } MemMapping map; if (sysMapFile(path, &map) != 0) { LOGE("failed to map filen"); 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. ZipArchive zip; int err = mzOpenZipArchive(map.addr, map.length, &zip); if (err != 0) { LOGE("Can't open %sn(%s)n", path, err != -1 ? strerror(err) : "bad"); log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure)); sysReleaseMap(&map); return INSTALL_CORRUPT; } // Verify and install the contents of the package. ui->Print("Installing update...n"); if (retry_count > 0) { ui->Print("Retry attempt: %dn", retry_count); } ui->SetEnableReboot(false); int result = try_update_binary(path, &zip, wipe_cache, log_buffer, retry_count); //開始安裝 ui->SetEnableReboot(true); ui->Print("n"); sysReleaseMap(&map); #ifdef USE_MDTP /* If MDTP update failed, return an error such that recovery will not finish. */ if (result == INSTALL_SUCCESS) { if (!mdtp_update()) { ui->Print("Unable to verify integrity of /system for MDTP, update aborted.n"); return INSTALL_ERROR; } ui->Print("Successfully verified integrity of /system for MDTP.n"); } #endif /* USE_MDTP */ return result; }注釋如上;
函數really_install_package會對升級包進行一系列的校驗,通過校驗後,調用try_update_binary函數完成升級。因此,try_update_binary()才是真正升級的地方。如下:
2.3 真正升級的try_update_binary()函數
try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) { const ZipEntry* binary_entry = //在升級包中查找是否存在META-INF/com/google/android/update-binary文件 mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); if (binary_entry == NULL) { mzCloseZipArchive(zip); return INSTALL_CORRUPT; } const char* binary = "/tmp/update_binary"; //在tmp中創建臨時文件夾,許可權755 unlink(binary); int fd = creat(binary, 0755); if (fd < 0) { mzCloseZipArchive(zip); LOGE("Can't make %sn", binary); return INSTALL_ERROR; } bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); //把update.zip升級包解壓到/tmp/update_binary文件夾中 sync(); close(fd); mzCloseZipArchive(zip); if (!ok) { LOGE("Can't copy %sn", ASSUMED_UPDATE_BINARY_NAME); return INSTALL_ERROR; } int pipefd[2]; pipe(pipefd); // When executing the update binary contained in the package, the // arguments passed are: // // - the version number for this interface // // - an fd to which the program can write in order to update the // progress bar. The program can write single-line commands: // // progress <frac> <secs> // fill up the next <frac> part of of the progress bar // over <secs> seconds. If <secs> is zero, use // set_progress commands to manually control the // progress of this segment of the bar. // // set_progress <frac> // <frac> should be between 0.0 and 1.0; sets the // progress bar within the segment defined by the most // recent progress command. // // firmware <"hboot"|"radio"> <filename> // arrange to install the contents of <filename> in the // given partition on reboot. // // (API v2: <filename> may start with "PACKAGE:" to // indicate taking a file from the OTA package.) // // (API v3: this command no longer exists.) // // ui_print <string> // display <string> on the screen. // // wipe_cache // a wipe of cache will be performed following a successful // installation. // // clear_display // turn off the text display. // // 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). // // - the name of the package zip file. // const char** args = (const char**)malloc(sizeof(char*) * 5); //創建指針數組,並分配記憶體 args[0] = binary; //[0]存放字元串 "/tmp/update_binary" ,也就是升級包解壓的目的地址 args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk //[1]存放RECOVERY_API_VERSION,在Android.mk中定義,我的值為3 RECOVERY_API_VERSION := 3 char* temp = (char*)malloc(10); sprintf(temp, "%d", pipefd[1]); args[2] = temp; args[3] = (char*)path; //[3]存放update.zip路徑 args[4] = NULL; pid_t pid = fork(); //創建一個新進程,為子進程 if (pid == 0) { //進程創建成功,執行META-INF/com/google/android/update-binary腳本,給腳本傳入參數args umask(022); close(pipefd[0]); execv(binary, (char* const*)args); fprintf(stdout, "E:Can't run %s (%s)n", binary, strerror(errno)); _exit(-1); } close(pipefd[1]); *wipe_cache = false; char buffer[1024]; FILE* from_child = fdopen(pipefd[0], "r"); while (fgets(buffer, sizeof(buffer), from_child) != NULL) { //父進程通過管道pipe讀取子進程的值,使用strtok分割函數把子進程傳過來的參數進行解析,執行相應的ui修改 char* command = strtok(buffer, " n"); if (command == NULL) { continue; } else if (strcmp(command, "progress") == 0) { char* fraction_s = strtok(NULL, " n"); char* seconds_s = strtok(NULL, " n"); float fraction = strtof(fraction_s, NULL); int seconds = strtol(seconds_s, NULL, 10); ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds); } else if (strcmp(command, "set_progress") == 0) { char* fraction_s = strtok(NULL, " n"); float fraction = strtof(fraction_s, NULL); ui->SetProgress(fraction); } else if (strcmp(command, "ui_print") == 0) { char* str = strtok(NULL, "n"); if (str) { ui->Print("%s", str); } else { ui->Print("n"); } fflush(stdout); } else if (strcmp(command, "wipe_cache") == 0) { *wipe_cache = true; } else if (strcmp(command, "clear_display") == 0) { ui->SetBackground(RecoveryUI::NONE); } else if (strcmp(command, "enable_reboot") == 0) { // 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 { LOGE("unknown command [%s]n", command); } } fclose(from_child); int status; waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOGE("Error in %sn(Status %d)n", path, WEXITSTATUS(status)); return INSTALL_ERROR; } return INSTALL_SUCCESS; }try_update_binary流程:
- 查找META-INF/com/google/android/update-binary二進位腳本
- 解壓update.zip包到/tmp/update_binary
- 創建子進程,執行update-binary二進位安裝腳本,並通過管道與父進程通訊,父進程更新ui介面。
再看看之後的操作:
if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) { copy_logs(); set_retry_bootloader_message(retry_count, argc, argv); // Print retry count on screen. ui->Print("Retry attempt %dn", retry_count); // Reboot and retry the update int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery"); if (ret < 0) { ui->Print("Reboot failedn"); } 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); }再看看之後的操作:
else if (should_wipe_data) { //只清除用戶數據 if (!wipe_data(false, device)) { status = INSTALL_ERROR; } } else if (should_wipe_cache) { //只清除快取 if (!wipe_cache(false, device)) { status = INSTALL_ERROR; } } else if (sideload) {//執行adb reboot sideload命令後會跑到這個程式碼段 // 'adb reboot sideload' acts the same as user presses key combinations // to enter the sideload mode. When 'sideload-auto-reboot' is used, text // display will NOT be turned on by default. And it will reboot after // sideload finishes even if there are errors. Unless one turns on the // text display during the installation. This is to enable automated // testing. if (!sideload_auto_reboot) { ui->ShowText(true); } status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); if (status == INSTALL_SUCCESS) { ota_completed = true; } if (status == INSTALL_SUCCESS && should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; } } ui->Print("nInstall from ADB complete (status: %d).n", status); if (sideload_auto_reboot) { ui->Print("Rebooting automatically.n"); } } else if (!just_exit) { status = INSTALL_NONE; // No command specified ui->SetBackground(RecoveryUI::NO_COMMAND); // http://b/17489952 // If this is an eng or userdebug build, automatically turn on the // text display if no command is specified. if (is_ro_debuggable()) { ui->ShowText(true); }2.4 死循環prompt_and_wait
if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { //安裝失敗,複製log資訊到/cache/recovery/。如果進行了wipe_data/wipe_cache/apply_from_sdcard(也就是修改了flash), //直接return結束recovery,否則現實error背景圖片 copy_logs(); ui->SetBackground(RecoveryUI::ERROR); } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; if ((status != INSTALL_SUCCESS && !sideload_auto_reboot) || ui->IsTextVisible()) { //status在just_exit中已經變為none,會執行此if語句 #ifdef SUPPORT_UTF8_MULTILINGUAL ml_select(device); #endif Device::BuiltinAction temp = prompt_and_wait(device, status); //prompt_and_wait()函數是個死循環 開始顯示recovery選項 並處理用戶通過按鍵或者觸控螢幕的選項,如Reboot system等 if (temp != Device::NO_ACTION) { after = temp; } }這裡是根據你的按鍵去選擇判斷進入哪一個函數裡面,進入那一段的升級裡面;
。。。。 case Device::NO_ACTION: break; case Device::REBOOT: case Device::SHUTDOWN: case Device::REBOOT_BOOTLOADER: return chosen_action; case Device::WIPE_DATA: timeout_exit_stop(); //wipe_data(ui->IsTextVisible(), device); nexgo_wipe_data(ui->IsTextVisible(), device); timeout_exit_start(); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; case Device::WIPE_CACHE: timeout_exit_stop(); ui->Print("n-- Wiping cache...n"); wipe_cache(ui->IsTextVisible(), device); ui->Print("Cache wipe complete.n"); timeout_exit_start(); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; case Device::APPLY_ADB_SIDELOAD: case Device::APPLY_SDCARD: case Device::APPLY_USB: { #if 0 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); } #else char *apply_names; if (chosen_action == Device::APPLY_ADB_SIDELOAD) { apply_names = "ADB"; status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); } else if(chosen_action == Device::APPLY_SDCARD){ apply_names = "SD card"; status = apply_from_sdcard(device, &should_wipe_cache); } else if(chosen_action == Device::APPLY_USB){ apply_names = "USB OTG"; status = apply_from_usbotg(device, &should_wipe_cache); } #endif if (status == INSTALL_SUCCESS) { ota_completed = true; } 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",apply_names); } } break; /*case Device::APPLY_U_DISK: { status = apply_from_u_disk(device, &should_wipe_cache); if (status == INSTALL_SUCCESS) { ota_completed = true; } 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 U Disk complete.n"); } } break*/ case Device::VIEW_RECOVERY_LOGS: timeout_exit_stop(); choose_recovery_file(device); timeout_exit_start(); break; #if 0 case Device::RUN_GRAPHICS_TEST: run_graphics_test(device); break; //#endif case Device::MOUNT_SYSTEM: { #ifdef USE_MDTP if (is_mdtp_activated()) { ui->Print("Mounting /system forbidden by MDTP.n"); } else #endif { char system_root_image[PROPERTY_VALUE_MAX]; property_get("ro.build.system_root_image", system_root_image, ""); // For a system image built with the root directory (i.e. // system_root_image == "true"), we mount it to /system_root, and symlink /system // to /system_root/system to make adb shell work (the symlink is created through // the build system). // Bug: 22855115 if (strcmp(system_root_image, "true") == 0) { if (ensure_path_mounted_at("/", "/system_root") != -1) { ui->Print("Mounted /system.n"); } } else { if (ensure_path_mounted("/system") != -1) { ui->Print("Mounted /system.n"); } } } break; } #endif case Device::SECURE_UNLOCK: { unlock_device(); if (!ui->IsTextVisible()) return Device::NO_ACTION; security_mode = 0; error_code = 0; break; } case Device::DOWNLOAD_SECURE_INFO: { printf("security_info_download=====n"); int ret = security_info_download(); if(ret == 0) { ui->Print("n-- Wiping data...n"); device->PreWipeData(); erase_volume("/data"); erase_volume("/cache"); ui->Print("Data wipe complete.n"); need_wipe = 0; return Device::REBOOT; } if (!ui->IsTextVisible()) return Device::NO_ACTION; security_mode = 0; error_code = 0; break; } case Device::DOWNLOAD_HWC_INFO: { printf("hwc ---downloadn"); int ret = security_hwc_download(); if(ret == 0) { return Device::REBOOT; break; } if (!ui->IsTextVisible()) return Device::NO_ACTION; security_mode = 0; error_code = 0; break; } case Device::APPLY_OTACONFIG_EXT: apply_from_config(device,SDCARD_ROOT); break; case Device::APPLY_OTACONFIG_USB_PATH: /*int chonsen = factory_jump_usbpath(factory_jump_usbpath_flag,device); factory_jump_usbpath_flag = 0; if(chonsen == 0) { apply_from_path(device,USBOTG_ROOT); } else if(chonsen == 2)//進入download hwc { factory_chosen_item = 1; } break; */ { int ret = apply_from_path(device,USBOTG_ROOT); if((ret != 0) && (factory_jump_usbpath_flag == 1)){ factory_chosen_item = 1; } factory_jump_usbpath_flag = 0; break; } case Device::APPLY_OTACONFIG_USB: { dictionary * ini ; const char * otafilename; char otapathname[128]; int ret = 0,i; char inipathname[256] = {0}; char section[20] = {0}; char *prefixPath; int status = 0; APPLY_OTACONFIG_FLAG = 1; ui->Print("n-- Install from %s ...n", USBOTG_ROOT); ensure_path_mounted(USBOTG_ROOT); char* path = browse_directory(USBOTG_ROOT, device); if (path == NULL) { ui->Print("n-- No package file selected.n", path); ensure_path_unmounted(USBOTG_ROOT); break; } ui->Print("n-- Install %s ...n", path); set_sdcard_update_bootloader_message(); if(strncasecmp(&path[strlen(path)-4], ".ini", 4) == 0) { snprintf(inipathname,256,"%s",path); ini = iniparser_load(inipathname); if (ini==NULL) { fprintf(stderr, "cannot parse file: %sn", inipathname); break; } prefixPath = strrchr(path, '/'); //<BB><F1>?ini?<U+05FA>??<BE><B6>path *prefixPath = ''; ensure_path_unmounted(USBOTG_ROOT); for(i=1; i<10; i++) { ensure_path_mounted(USBOTG_ROOT); snprintf(section,20,"Path:OTApackage%d", i); otafilename = iniparser_getstring(ini, section, NULL); //<BB><F1>?ini<CE>?<FE><C3><FB> if (otafilename) printf("Path: [%s]n",otafilename ); else printf("Path%d: [UNDEF]n", i); if (!otafilename) continue; strcpy(otapathname, path); strcat(otapathname, "/"); strcat(otapathname, otafilename); ui->Print("n-- otapathname = %sn", otapathname); //void* token = start_usbotg_fuse(otapathname); //status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache, //finish_sdcard_fuse(token);ensure_path_unmounted(USBOTG_ROOT); status = install_package_usb(otapathname, &should_wipe_cache); if (status != INSTALL_SUCCESS) break; } iniparser_freedict(ini); } else { status = install_package_usb(path, &should_wipe_cache); } ensure_path_unmounted(USBOTG_ROOT); if (status == INSTALL_SUCCESS && should_wipe_cache) { ui->Print("n-- Wiping cache (at package request)...n"); if (erase_volume("/cache")) { ui->Print("Cache wipe failed.n"); } else { ui->Print("Cache wipe complete.n"); } } if (status >= 0) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.n"); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("nInstall from sdcard complete.n"); } } break; } break; }