隨着android版本的更新,系統固件的大小也越來越大,升級包也越來越大,cache分區已經不夠存儲update.zip了,所以應用把update.zip下載到data分區,默認情況下data分區是可以存儲升級包的。
我們有分區加密的功能,當打開加密分區后,data分區是加密的,當升級包存在data分區的時候,recovery下獲取不到對應的秘鑰,也沒有對應的程序去解密,所以recovery無法正常掛載data分區,獲取升級包升級。那么google是如何完成分區加密時,從data分區升級的呢?
當應用從遠程服務器下載update.zip升級包后,是如何一步步進入recovery升級的呢?
android P(9.0)aosp code:
frameworks/base/core/java/android/os/RecoverySystem.java
主要分為兩步進行,第一步處理升級包(processPackage),第二步安裝升級包(installPackage):
處理升級包:
public static void processPackage(Context context, File packageFile, final ProgressListener listener, final Handler handler) throws IOException { String filename = packageFile.getCanonicalPath(); if (!filename.startsWith("/data/")) { return; } RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); IRecoverySystemProgressListener progressListener = null; if (listener != null) { final Handler progressHandler; if (handler != null) { progressHandler = handler; } else { progressHandler = new Handler(context.getMainLooper()); } progressListener = new IRecoverySystemProgressListener.Stub() { int lastProgress = 0; long lastPublishTime = System.currentTimeMillis(); @Override public void onProgress(final int progress) { final long now = System.currentTimeMillis(); progressHandler.post(new Runnable() { @Override public void run() { if (progress > lastProgress && now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { lastProgress = progress; lastPublishTime = now; listener.onProgress(progress); } } }); } }; } if (!rs.uncrypt(filename, progressListener)) { throw new IOException("process package failed"); } }
主要做了如下工作:
(1) 只處理升級包在/data分區的場景
(2) 處理進度顯示
(3)調用rs.uncrypt(filename, progressListener) 處理升級包
/** * Talks to RecoverySystemService via Binder to trigger uncrypt. */ private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) { try { return mService.uncrypt(packageFile, listener); } catch (RemoteException unused) { } return false; }
RecoverySystem 通過Binder 觸發 uncrypt服務
/system/etc/init/uncrypt.rc
service uncrypt /system/bin/uncrypt class main socket uncrypt stream 600 system system disabled oneshot service setup-bcb /system/bin/uncrypt --setup-bcb class main socket uncrypt stream 600 system system disabled oneshot service clear-bcb /system/bin/uncrypt --clear-bcb class main socket uncrypt stream 600 system system disabled oneshot
調用了/system/bin/uncrypt程序來處理升級包, uncrypt對應的源碼在 bootable/recovery/uncrypt/uncrypt.cpp
具體處理細節我們在章節詳解:recovery uncrypt功能解析(bootable/recovery/uncrypt/uncrypt.cpp)
安裝升級包:
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. 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. 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"; } final String filenameArg = "--update_package=" + filename + "\n"; final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n"; final String securityArg = "--security\n"; String command = filenameArg + localeArg; if (securityUpdate) { command += securityArg; } 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 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); String reason = PowerManager.REBOOT_RECOVERY_UPDATE; // On TV, reboot quiescently if the screen is off if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); if (wm.getDefaultDisplay().getState() != Display.STATE_ON) { reason += ",quiescent"; } } pm.reboot(reason); throw new IOException("Reboot failed (no permissions?)"); } }
主要做了如下工作:
(1) 如果升級包路徑為/data開始的根目錄,把升級包的名字寫到文件/cache/recovery/uncrypt_file里
(2) 寫升級命令--update_package=@/cache/recovery/block.map到文件/cache/recovery/command
(3) 調用pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE)重啟。
參考資料:https://blog.csdn.net/miaotao/article/details/45129423
http://feed.askmaclean.com/archives/linux查看稀疏文件的哪些塊沒有分配空間.html