赞
踩
原文地址:https://skytoby.github.io/2019/Android%20OTA%E5%8D%87%E7%BA%A7%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/
目前Android系统终端的升级主要是通过无线进行的(FOTA,Firmware Over-The-Air),主要流程是通过无线方式将升级包下载到终端,而后调用系统的升级接口进行升级。本文主要分析升级包下载后,调用系统升级接口之后的流程。
升级包是用make otapackage命令生成的,对于差分包,需要生成两个ota整包,再用系统的编译工具利用这两个整包生成一个差分包。下面是某终端Android9.0版本的ota升级包结构。
├─firmware-update └─META-INF └─com ├─android ├─metadata ├─otacert └─google └─android ├─update-binary ├─updater-script ├─boot.img ├─system.new.dat.br ├─system.patch.dat ├─system.transfer.list ├─vendor.new.dat.br ├─vendor.patch.dat ├─vendor.transfer.list
firmware-update:目录下主要是一些固件的升级,如mbn、dtbo、vbmeta等
boot.img:更新boot分区所需要的文件,包括kernel+ramdisk
system.new.dat.br:Android8.1镜像后新的压缩格式,为数据部分,里面是system.img数据。
system.transfer.list:数据转换的描述列表。
system.patch.dat:升级时用到,ota包中大小为0。
vendor和system类似。
update-binary:二进制文件,相当于一个脚本解释器,能够识别updater-script中描述的操作,文件的命名由bootable/recovery/install.cpp 中的UPDATE_BINARY_NAME值而定。
updater-script:升级包的升级脚本,文件的命名由bootable/recovery/updater/updater.cpp中的SCRIPT_NAME值而定。
metadata:描述设备信息及环境变量的元数据,主要包括一些编译选项、签名公钥,时间戳以及设备型号等。
otacert:ota签名。
Android系统启动主要有两种:
1.组合键
用户通过按下组合键,不同的终端可以定义组合键,进入不同的工作模式,在bootable/bootloader/lk/app/aboot/aboot.c文件中判断,具体有两种:
bootloader模式,可进一步进入fastboot(快速刷机模式)。
Recovery模式,进入这种模式系统会出现一个简单的ui界面,用来提示用户的进一步操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RT9PaFD4-1577704943830)(/Android OTA升级流程分析/recovery.jpg)]
2.正常启动
没有按下任何组合键,正常开机,bootloader会读取启动控制信息块BCB(bootloader control block),它是一个结构体,存放着启动命令Command。根据不同的命令,系统可以进入三种不同的启动模式。下面是Bootloader Message的定义,通过注释可以了解具体结构体中成员变量的含义。
[->bootloader_message.h]
/* Bootloader Message (2-KiB) * * This structure describes the content of a block in flash * that is used for recovery and the bootloader to talk to * each other. * * The command field is updated by linux when it wants to * reboot into recovery or to update radio or bootloader firmware. * It is also updated by the bootloader when firmware update * is complete (to boot into recovery for any final cleanup) * * The status field was used by the bootloader after the completion * of an "update-radio" or "update-hboot" command, which has been * deprecated since Froyo. * * The recovery field is only written by linux and used * for the system to send a message to recovery or the * other way around. * * The stage field is written by packages which restart themselves * multiple times, so that the UI can reflect which invocation of the * package it is. If the value is of the format "#/#" (eg, "1/3"), * the UI will add a simple indicator of that status. * * We used to have slot_suffix field for A/B boot control metadata in * this struct, which gets unintentionally cleared by recovery or * uncrypt. Move it into struct bootloader_message_ab to avoid the * issue. */ struct bootloader_message { char command[32]; char status[32]; char recovery[768]; // The 'recovery' field used to be 1024 bytes. It has only ever // been used to store the recovery command line, so 768 bytes // should be plenty. We carve off the last 256 bytes to store the // stage string (for multistage packages) and possible future // expansion. char stage[32]; // The 'reserved' field used to be 224 bytes when it was initially // carved off from the 1024-byte recovery field. Bump it up to // 1184-byte so that the entire bootloader_message struct rounds up // to 2048-byte. char reserved[1184]; };
前面介绍完OTA升级包和系统启动模式,现在看下真正的ota升级的步骤。无论ota包是通过无线下载还是导入到SD卡,最后都会调用到RecoverySystem.installPackage方法,下面分析下这个详细的流程。
[->RecoverySystem.java]
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 file 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. //如果升级包的路径是/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 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); //向bootloader control block写入命名 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?)"); } }
private static final File RECOVERY_DIR = new File("/cache/recovery");
public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, “uncrypt_file”);
这里主要是对ota升级包的处理,由于进入recovery模式后,data分区将不能加载,因此需要将其通过block方式,把ota升级包生成稀疏的描述文件,保存在/cache/recovery/block.map中。升级的命令写入到BCB,重启之后bootloader会读取BCB中的command从而进入升级模式。
[->RecoverySystem.java]
/**
* Talks to RecoverySystemService via Binder to set up the BCB.
*/
private boolean setupBcb(String command) {
try {
//通过binder调用setupBcb
return mService.setupBcb(command);
} catch (RemoteException unused) {
}
return false;
}
[->RecoverySystemService.java]
@Override // Binder call
public boolean setupBcb(String command) {
if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
synchronized (sRequestLock) {
return setupOrClearBcb(true, command);
}
}
[->RecoverySystemService.java]
private boolean setupOrClearBcb(boolean isSetup, String command) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); final boolean available = checkAndWaitForUncryptService(); if (!available) { Slog.e(TAG, "uncrypt service is unavailable."); return false; } //设置ctl.start属性,启动setup-bcb服务 if (isSetup) { SystemProperties.set("ctl.start", "setup-bcb"); } else { SystemProperties.set("ctl.start", "clear-bcb"); } // Connect to the uncrypt service socket. // 连接uncrypt服务 LocalSocket socket = connectService(); if (socket == null) { Slog.e(TAG, "Failed to connect to uncrypt socket"); return false; } DataInputStream dis = null; DataOutputStream dos = null; try { dis = new DataInputStream(socket.getInputStream()); dos = new DataOutputStream(socket.getOutputStream()); // Send the BCB commands if it's to setup BCB. // 发送BCB commands if (isSetup) { byte[] cmdUtf8 = command.getBytes("UTF-8"); dos.writeInt(cmdUtf8.length); dos.write(cmdUtf8, 0, cmdUtf8.length); dos.flush(); } // Read the status from the socket. int status = dis.readInt(); // Ack receipt of the status code. uncrypt waits for the ack so // the socket won't be destroyed before we receive the code. dos.writeInt(0); if (status == 100) { Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") + " bcb successfully finished."); } else { // Error in /system/bin/uncrypt. Slog.e(TAG, "uncrypt failed with status: " + status); return false; } } catch (IOException e) { Slog.e(TAG, "IOException when communicating with uncrypt:", e); return false; } finally { IoUtils.closeQuietly(dis); IoUtils.closeQuietly(dos); IoUtils.closeQuietly(socket); } return true; }
SystemProperties.set(“ctl.start”, “setup-bcb”),通过这种方式来启动服务(SystemProperties设置属性的原理详细见文章Android SystemProperties系统属性分析),而后连接服务,向其中发送BCB command。
[->PowerManager.java]
public void reboot(String reason) {
try {
mService.reboot(false, reason, true);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
[->PowerManagerService.java]
/** * Reboots the device. * * @param confirm If true, shows a reboot confirmation dialog. * @param reason The reason for the reboot, or null if none. * @param wait If true, this call waits for the reboot to complete and does not return. */ @Override // Binder call public void reboot(boolean confirm, String reason, boolean wait) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); if (PowerManager.REBOOT_RECOVERY.equals(reason) || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); } final long ident = Binder.clearCallingIdentity(); try { shutdownOrRebootInternal(HALT_MODE_REBOOT, confirm, reason, wait); } finally { Binder.restoreCallingIdentity(ident); } }
这里传入的参数confirm:false , reason:REBOOT_RECOVERY_UPDATE = “recovery-update”,wait:true
[->PowerManagerService.java]
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm, final String reason, boolean wait) { if (mHandler == null || !mSystemReady) { if (RescueParty.isAttemptingFactoryReset()) { // If we're stuck in a really low-level reboot loop, and a // rescue party is trying to prompt the user for a factory data // reset, we must GET TO DA CHOPPA! PowerManagerService.lowLevelReboot(reason); } else { throw new IllegalStateException("Too early to call shutdown() or reboot()"); } } //上面传过来的是HALT_MODE_REBOOT Runnable runnable = new Runnable() { @Override public void run() { synchronized (this) { if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) { ShutdownThread.rebootSafeMode(getUiContext(), confirm); } else if (haltMode == HALT_MODE_REBOOT) { ShutdownThread.reboot(getUiContext(), reason, confirm); } else { ShutdownThread.shutdown(getUiContext(), reason, confirm); } } } }; // ShutdownThread must run on a looper capable of displaying the UI. Message msg = Message.obtain(UiThread.getHandler(), runnable); msg.setAsynchronous(true); UiThread.getHandler().sendMessage(msg); // PowerManager.reboot() is documented not to return so just wait for the inevitable. if (wait) { synchronized (runnable) { while (true) { try { runnable.wait(); } catch (InterruptedException e) { } } } } }
[->ShutdownThread.java]
public static void reboot(final Context context, String reason, boolean confirm) {
mReboot = true;
mRebootSafeMode = false;
mRebootHasProgressBar = false;
mReason = reason;
shutdownInner(context, confirm);
}
[->ShutdownThread.java]
private static void shutdownInner(final Context context, boolean confirm) { // ShutdownThread is called from many places, so best to verify here that the context passed // in is themed. context.assertRuntimeOverlayThemable(); // ensure that only one thread is trying to power down. // any additional calls are just returned synchronized (sIsStartedGuard) { if (sIsStarted) { Log.d(TAG, "Request to shutdown already running, returning."); return; } } final int longPressBehavior = context.getResources().getInteger( com.android.internal.R.integer.config_longPressOnPowerBehavior); final int resourceId = mRebootSafeMode ? com.android.internal.R.string.reboot_safemode_confirm : (longPressBehavior == 2 ? com.android.internal.R.string.shutdown_confirm_question : com.android.internal.R.string.shutdown_confirm); Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior); if (confirm) { final CloseDialogReceiver closer = new CloseDialogReceiver(context); if (sConfirmDialog != null) { sConfirmDialog.dismiss(); } sConfirmDialog = new AlertDialog.Builder(context) .setTitle(mRebootSafeMode ? com.android.internal.R.string.reboot_safemode_title : com.android.internal.R.string.power_off) .setMessage(resourceId) .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { beginShutdownSequence(context); } }) .setNegativeButton(com.android.internal.R.string.no, null) .create(); closer.dialog = sConfirmDialog; sConfirmDialog.setOnDismissListener(closer); sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); sConfirmDialog.show(); } else { //走这里 beginShutdownSequence(context); } }
[->ShutdownThread.java]
private static void beginShutdownSequence(Context context) { synchronized (sIsStartedGuard) { if (sIsStarted) { Log.d(TAG, "Shutdown sequence already running, returning."); return; } sIsStarted = true; } sInstance.mProgressDialog = showShutdownDialog(context); sInstance.mContext = context; sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); // make sure we never fall asleep again sInstance.mCpuWakeLock = null; try { sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu"); sInstance.mCpuWakeLock.setReferenceCounted(false); sInstance.mCpuWakeLock.acquire(); } catch (SecurityException e) { Log.w(TAG, "No permission to acquire wake lock", e); sInstance.mCpuWakeLock = null; } // also make sure the screen stays on for better user experience sInstance.mScreenWakeLock = null; if (sInstance.mPowerManager.isScreenOn()) { try { sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock( PowerManager.FULL_WAKE_LOCK, TAG + "-screen"); sInstance.mScreenWakeLock.setReferenceCounted(false); sInstance.mScreenWakeLock.acquire(); } catch (SecurityException e) { Log.w(TAG, "No permission to acquire wake lock", e); sInstance.mScreenWakeLock = null; } } if (SecurityLog.isLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN); } // start the thread that initiates shutdown sInstance.mHandler = new Handler() { }; //启动线程 sInstance.start(); }
[->ShutdownThread.java]
private static ProgressDialog showShutdownDialog(Context context) { // Throw up a system dialog to indicate the device is rebooting / shutting down. ProgressDialog pd = new ProgressDialog(context); // Path 1: Reboot to recovery for update // Condition: mReason startswith REBOOT_RECOVERY_UPDATE // // Path 1a: uncrypt needed // Condition: if /cache/recovery/uncrypt_file exists but // /cache/recovery/block.map doesn't. // UI: determinate progress bar (mRebootHasProgressBar == True) // // * Path 1a is expected to be removed once the GmsCore shipped on // device always calls uncrypt prior to reboot. // // Path 1b: uncrypt already done // UI: spinning circle only (no progress bar) // // Path 2: Reboot to recovery for factory reset // Condition: mReason == REBOOT_RECOVERY // UI: spinning circle only (no progress bar) // // Path 3: Regular reboot / shutdown // Condition: Otherwise // UI: spinning circle only (no progress bar) // mReason could be "recovery-update" or "recovery-update,quiescent". //传入的是REBOOT_RECOVERY_UPDATE,走这里 if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) { // We need the progress bar if uncrypt will be invoked during the // reboot, which might be time-consuming. mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists() && !(RecoverySystem.BLOCK_MAP_FILE.exists()); pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title)); //正常升级模式下,mRebootHasProgressBar = true if (mRebootHasProgressBar) { pd.setMax(100); pd.setProgress(0); pd.setIndeterminate(false); pd.setProgressNumberFormat(null); pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setMessage(context.getText( com.android.internal.R.string.reboot_to_update_prepare)); } else { if (showSysuiReboot()) { return null; } pd.setIndeterminate(true); pd.setMessage(context.getText( com.android.internal.R.string.reboot_to_update_reboot)); } } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) { if (RescueParty.isAttemptingFactoryReset()) { // We're not actually doing a factory reset yet; we're rebooting // to ask the user if they'd like to reset, so give them a less // scary dialog message. pd.setTitle(context.getText(com.android.internal.R.string.power_off)); pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); pd.setIndeterminate(true); } else { // Factory reset path. Set the dialog message accordingly. pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title)); pd.setMessage(context.getText( com.android.internal.R.string.reboot_to_reset_message)); pd.setIndeterminate(true); } } else { if (showSysuiReboot()) { return null; } pd.setTitle(context.getText(com.android.internal.R.string.power_off)); pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); pd.setIndeterminate(true); } pd.setCancelable(false); pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); pd.show(); return pd; }
[->ShutdownThread.java]
/** * Makes sure we handle the shutdown gracefully. * Shuts off power regardless of radio state if the allotted time has passed. */ public void run() { TimingsTraceLog shutdownTimingLog = newTimingsLog(); shutdownTimingLog.traceBegin("SystemServerShutdown"); metricShutdownStart(); metricStarted(METRIC_SYSTEM_SERVER); BroadcastReceiver br = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // We don't allow apps to cancel this, so ignore the result. actionDone(); } }; /* * Write a system property in case the system_server reboots before we * get to the actual hardware restart. If that happens, we'll retry at * the beginning of the SystemServer startup. */ { String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : ""); SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); } /* * If we are rebooting into safe mode, write a system property * indicating so. */ if (mRebootSafeMode) { SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); } metricStarted(METRIC_SEND_BROADCAST); shutdownTimingLog.traceBegin("SendShutdownBroadcast"); Log.i(TAG, "Sending shutdown broadcast..."); // First send the high-level shut down broadcast. mActionDone = false; Intent intent = new Intent(Intent.ACTION_SHUTDOWN); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, br, mHandler, 0, null, null); final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; synchronized (mActionDoneSync) { while (!mActionDone) { long delay = endTime - SystemClock.elapsedRealtime(); if (delay <= 0) { Log.w(TAG, "Shutdown broadcast timed out"); break; } else if (mRebootHasProgressBar) { int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 * BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME); sInstance.setRebootProgress(status, null); } try { mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS)); } catch (InterruptedException e) { } } } if (mRebootHasProgressBar) { sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null); } shutdownTimingLog.traceEnd(); // SendShutdownBroadcast metricEnded(METRIC_SEND_BROADCAST); Log.i(TAG, "Shutting down activity manager..."); shutdownTimingLog.traceBegin("ShutdownActivityManager"); metricStarted(METRIC_AM); final IActivityManager am = IActivityManager.Stub.asInterface(ServiceManager.checkService("activity")); if (am != null) { try { am.shutdown(MAX_BROADCAST_TIME); } catch (RemoteException e) { } } if (mRebootHasProgressBar) { sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null); } shutdownTimingLog.traceEnd();// ShutdownActivityManager metricEnded(METRIC_AM); Log.i(TAG, "Shutting down package manager..."); shutdownTimingLog.traceBegin("ShutdownPackageManager"); metricStarted(METRIC_PM); final PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); if (pm != null) { pm.shutdown(); } if (mRebootHasProgressBar) { sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null); } shutdownTimingLog.traceEnd(); // ShutdownPackageManager metricEnded(METRIC_PM); // Shutdown radios. shutdownTimingLog.traceBegin("ShutdownRadios"); metricStarted(METRIC_RADIOS); shutdownRadios(MAX_RADIO_WAIT_TIME); if (mRebootHasProgressBar) { sInstance.setRebootProgress(RADIO_STOP_PERCENT, null); } shutdownTimingLog.traceEnd(); // ShutdownRadios metricEnded(METRIC_RADIOS); //为true,将执行uncrypt操作 if (mRebootHasProgressBar) { sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null); // If it's to reboot to install an update and uncrypt hasn't been // done yet, trigger it now. uncrypt(); } shutdownTimingLog.traceEnd(); // SystemServerShutdown metricEnded(METRIC_SYSTEM_SERVER); saveMetrics(mReboot, mReason); // Remaining work will be done by init, including vold shutdown rebootOrShutdown(mContext, mReboot, mReason); }
[->ShutdownThread.java]
private void uncrypt() { Log.i(TAG, "Calling uncrypt and monitoring the progress..."); final RecoverySystem.ProgressListener progressListener = new RecoverySystem.ProgressListener() { @Override public void onProgress(int status) { if (status >= 0 && status < 100) { // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100). status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100); status += MOUNT_SERVICE_STOP_PERCENT; CharSequence msg = mContext.getText( com.android.internal.R.string.reboot_to_update_package); sInstance.setRebootProgress(status, msg); } else if (status == 100) { CharSequence msg = mContext.getText( com.android.internal.R.string.reboot_to_update_reboot); sInstance.setRebootProgress(status, msg); } else { // Ignored } } }; final boolean[] done = new boolean[1]; done[0] = false; Thread t = new Thread() { @Override public void run() { RecoverySystem rs = (RecoverySystem) mContext.getSystemService( Context.RECOVERY_SERVICE); String filename = null; try { filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null); rs.processPackage(mContext, new File(filename), progressListener); } catch (IOException e) { Log.e(TAG, "Error uncrypting file", e); } done[0] = true; } }; t.start(); try { t.join(MAX_UNCRYPT_WAIT_TIME); } catch (InterruptedException unused) { } if (!done[0]) { Log.w(TAG, "Timed out waiting for uncrypt."); final int uncryptTimeoutError = 100; String timeoutMessage = String.format("uncrypt_time: %d/n" + "uncrypt_error: %d/n", MAX_UNCRYPT_WAIT_TIME / 1000, uncryptTimeoutError); try { FileUtils.stringToFile(RecoverySystem.UNCRYPT_STATUS_FILE, timeoutMessage); } catch (IOException e) { Log.e(TAG, "Failed to write timeout message to uncrypt status", e); } } }
[->RecoverySystem.java]
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); } } }); } }; } //通过progressListener传递进度到界面 if (!rs.uncrypt(filename, progressListener)) { throw new IOException("process package failed"); } }
[->RecoverySystem.java]
/**
* 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;
}
[->RecoverySystemService.java]
public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) { if (DEBUG) Slog.d(TAG, "uncrypt: " + filename); synchronized (sRequestLock) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); final boolean available = checkAndWaitForUncryptService(); if (!available) { Slog.e(TAG, "uncrypt service is unavailable."); return false; } // Write the filename into UNCRYPT_PACKAGE_FILE to be read by // uncrypt. RecoverySystem.UNCRYPT_PACKAGE_FILE.delete(); try (FileWriter uncryptFile = new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE)) { uncryptFile.write(filename + "/n"); } catch (IOException e) { Slog.e(TAG, "IOException when writing /"" + RecoverySystem.UNCRYPT_PACKAGE_FILE + "/":", e); return false; } // Trigger uncrypt via init. //通过设置ctl.start属性,开启uncrypt服务 SystemProperties.set("ctl.start", "uncrypt"); // Connect to the uncrypt service socket. //连接服务 LocalSocket socket = connectService(); if (socket == null) { Slog.e(TAG, "Failed to connect to uncrypt socket"); return false; } // Read the status from the socket. DataInputStream dis = null; DataOutputStream dos = null; try { dis = new DataInputStream(socket.getInputStream()); dos = new DataOutputStream(socket.getOutputStream()); int lastStatus = Integer.MIN_VALUE; while (true) { // 读取进度 int status = dis.readInt(); // Avoid flooding the log with the same message. if (status == lastStatus && lastStatus != Integer.MIN_VALUE) { continue; } lastStatus = status; if (status >= 0 && status <= 100) { // Update status Slog.i(TAG, "uncrypt read status: " + status); if (listener != null) { try { listener.onProgress(status); } catch (RemoteException ignored) { Slog.w(TAG, "RemoteException when posting progress"); } } if (status == 100) { Slog.i(TAG, "uncrypt successfully finished."); // Ack receipt of the final status code. uncrypt // waits for the ack so the socket won't be // destroyed before we receive the code. dos.writeInt(0); break; } } else { // Error in /system/bin/uncrypt. Slog.e(TAG, "uncrypt failed with status: " + status); // Ack receipt of the final status code. uncrypt waits // for the ack so the socket won't be destroyed before // we receive the code. dos.writeInt(0); return false; } } } catch (IOException e) { Slog.e(TAG, "IOException when reading status: ", e); return false; } finally { IoUtils.closeQuietly(dis); IoUtils.closeQuietly(dos); IoUtils.closeQuietly(socket); } return true; } }
SystemProperties.set(“ctl.start”, “uncrypt”);这个操作主要是通过init(启动的Properties服务)进行,而后启动uncrypt服务,通过socket方式systemserver和uncrypt进行通信。
[->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
[->uncrypt.cpp]
// // If the filesystem is using an encrypted block device, it will also // read the file and rewrite it to the same blocks of the underlying // (unencrypted) block device, so the file contents can be read // without the need for the decryption key. // // The output of this program is a "block map" which looks like this: // // /dev/block/platform/msm_sdcc.1/by-name/userdata # block device // 49652 4096 # file size in bytes, block size // 3 # count of block ranges // 1000 1008 # block range 0 // 2100 2102 # ... block range 1 // 30 33 # ... block range 2 // // Each block range represents a half-open interval; the line "30 33" // reprents the blocks [30, 31, 32]. // // Recovery can take this block map file and retrieve the underlying // file data to use as an update package. /** * In addition to the uncrypt work, uncrypt also takes care of setting and * clearing the bootloader control block (BCB) at /misc partition. * * uncrypt is triggered as init services on demand. It uses socket to * communicate with its caller (i.e. system_server). The socket is managed by * init (i.e. created prior to the service starts, and destroyed when uncrypt * exits). * * Below is the uncrypt protocol. * * a. caller b. init c. uncrypt * --------------- ------------ -------------- * a1. ctl.start: * setup-bcb / * clear-bcb / * uncrypt * * b2. create socket at * /dev/socket/uncrypt * * c3. listen and accept * * a4. send a 4-byte int * (message length) * c5. receive message length * a6. send message * c7. receive message * c8. <do the work; may send * the progress> * a9. <may handle progress> * c10. <upon finishing> * send "100" or "-1" * * a11. receive status code * a12. send a 4-byte int to * ack the receive of the * final status code * c13. receive and exit * * b14. destroy the socket * * Note that a12 and c13 are necessary to ensure a11 happens before the socket * gets destroyed in b14. */ int main(int argc, char** argv) { enum { UNCRYPT, SETUP_BCB, CLEAR_BCB, UNCRYPT_DEBUG } 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) { action = SETUP_BCB; } else if (argc == 1) { action = UNCRYPT; } else if (argc == 3) { input_path = argv[1]; map_file = argv[2]; action = UNCRYPT_DEBUG; } else { usage(argv[0]); return 2; } if ((fstab = read_fstab()) == nullptr) { log_uncrypt_error_code(kUncryptFstabReadError); return 1; } if (action == UNCRYPT_DEBUG) { LOG(INFO) << "uncrypt called in debug mode, skip socket communication"; bool success = uncrypt_wrapper(input_path, map_file, -1); if (success) { LOG(INFO) << "uncrypt succeeded"; } else{ LOG(INFO) << "uncrypt failed"; } return success ? 0 : 1; } // c3. The socket is created by init when starting the service. uncrypt // will use the socket to communicate with its caller. android::base::unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str())); if (service_socket == -1) { PLOG(ERROR) << "failed to open socket /"" << UNCRYPT_SOCKET << "/""; log_uncrypt_error_code(kUncryptSocketOpenError); return 1; } fcntl(service_socket, F_SETFD, FD_CLOEXEC); if (listen(service_socket, 1) == -1) { PLOG(ERROR) << "failed to listen on socket " << service_socket.get(); log_uncrypt_error_code(kUncryptSocketListenError); return 1; } android::base::unique_fd socket_fd(accept4(service_socket, nullptr, nullptr, SOCK_CLOEXEC)); if (socket_fd == -1) { PLOG(ERROR) << "failed to accept on socket " << service_socket.get(); log_uncrypt_error_code(kUncryptSocketAcceptError); return 1; } bool success = false; switch (action) { //UNCRYPT操作 case UNCRYPT: success = uncrypt_wrapper(input_path, map_file, socket_fd); break; case SETUP_BCB: success = setup_bcb(socket_fd); break; case CLEAR_BCB: success = clear_bcb(socket_fd); break; default: // Should never happen. LOG(ERROR) << "Invalid uncrypt action code: " << 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, &code, 4)) { LOG(INFO) << " received " << code << ", exiting now"; } else { PLOG(ERROR) << "failed to read the code"; } return success ? 0 : 1; }
通过文件头的注释,可以知道具体的通信方式。
[->uncrypt.cpp]
static bool uncrypt_wrapper(const char* input_path, const char* map_file, const int socket) { // Initialize the uncrypt error to kUncryptErrorPlaceholder. log_uncrypt_error_code(kUncryptErrorPlaceholder); std::string package; if (input_path == nullptr) { if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) { write_status_to_socket(-1, socket); // Overwrite the error message. log_uncrypt_error_code(kUncryptPackageMissingError); return false; } input_path = package.c_str(); } CHECK(map_file != nullptr); auto start = std::chrono::system_clock::now(); //执行uncrypt操作 int status = uncrypt(input_path, map_file, socket); std::chrono::duration<double> duration = std::chrono::system_clock::now() - start; int count = static_cast<int>(duration.count()); std::string uncrypt_message = android::base::StringPrintf("uncrypt_time: %d/n", count); if (status != 0) { // Log the time cost and error code if uncrypt fails. uncrypt_message += android::base::StringPrintf("uncrypt_error: %d/n", status); if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } write_status_to_socket(-1, socket); return false; } if (!android::base::WriteStringToFile(uncrypt_message, UNCRYPT_STATUS)) { PLOG(WARNING) << "failed to write to " << UNCRYPT_STATUS; } //通过socket方式通知进度 write_status_to_socket(100, socket); return true; }
[->uncrypt.cpp]
static int uncrypt(const char* input_path, const char* map_file, const int socket) { LOG(INFO) << "update package is /"" << input_path << "/""; // Turn the name of the file we're supposed to convert into an absolute path, so we can find // what filesystem it's on. char path[PATH_MAX+1]; if (realpath(input_path, path) == nullptr) { PLOG(ERROR) << "failed to convert /"" << input_path << "/" to absolute path"; return kUncryptRealpathFindError; } bool encryptable; bool encrypted; bool f2fs_fs; const char* blk_dev = find_block_device(path, &encryptable, &encrypted, &f2fs_fs); if (blk_dev == nullptr) { LOG(ERROR) << "failed to find block device for " << path; return kUncryptBlockDeviceFindError; } // If the filesystem it's on isn't encrypted, we only produce the // block map, we don't rewrite the file contents (it would be // pointless to do so). LOG(INFO) << "encryptable: " << (encryptable ? "yes" : "no"); LOG(INFO) << " encrypted: " << (encrypted ? "yes" : "no"); // Recovery supports installing packages from 3 paths: /cache, // /data, and /sdcard. (On a particular device, other locations // may work, but those are three we actually expect.) // // On /data we want to convert the file to a block map so that we // can read the package without mounting the partition. On /cache // and /sdcard we leave the file alone. if (strncmp(path, "/data/", 6) == 0) { LOG(INFO) << "writing block map " << map_file; //生成blockmap return produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket); } return 0; }
[->uncrypt.cpp]
static int produce_block_map(const char* path, const char* map_file, const char* blk_dev, bool encrypted, bool f2fs_fs, int socket) { std::string err; if (!android::base::RemoveFileIfExists(map_file, &err)) { LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err; return kUncryptFileRemoveError; } std::string tmp_map_file = std::string(map_file) + ".tmp"; android::base::unique_fd mapfd(open(tmp_map_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)); if (mapfd == -1) { PLOG(ERROR) << "failed to open " << tmp_map_file; return kUncryptFileOpenError; } // Make sure we can write to the socket. if (!write_status_to_socket(0, socket)) { LOG(ERROR) << "failed to write to socket " << socket; return kUncryptSocketWriteError; } struct stat sb; if (stat(path, &sb) != 0) { LOG(ERROR) << "failed to stat " << path; return kUncryptFileStatError; } LOG(INFO) << " block size: " << sb.st_blksize << " bytes"; int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; LOG(INFO) << " file size: " << sb.st_size << " bytes, " << blocks << " blocks"; std::vector<int> ranges; std::string s = android::base::StringPrintf("%s/n%" PRId64 " %" PRId64 "/n", blk_dev, static_cast<int64_t>(sb.st_size), static_cast<int64_t>(sb.st_blksize)); if (!android::base::WriteStringToFd(s, mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } std::vector<std::vector<unsigned char>> buffers; /* * buffers大小为5,static constexpr int WINDOW_SIZE = 5; */ if (encrypted) { buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize)); } int head_block = 0; int head = 0, tail = 0; android::base::unique_fd fd(open(path, O_RDONLY)); if (fd == -1) { PLOG(ERROR) << "failed to open " << path << " for reading"; return kUncryptFileOpenError; } android::base::unique_fd wfd; if (encrypted) { wfd.reset(open(blk_dev, O_WRONLY)); if (wfd == -1) { PLOG(ERROR) << "failed to open " << blk_dev << " for writing"; return kUncryptBlockOpenError; } } #ifndef F2FS_IOC_SET_DONTMOVE #ifndef F2FS_IOCTL_MAGIC #define F2FS_IOCTL_MAGIC 0xf5 #endif #define F2FS_IOC_SET_DONTMOVE _IO(F2FS_IOCTL_MAGIC, 13) #endif if (f2fs_fs && ioctl(fd, F2FS_IOC_SET_DONTMOVE) < 0) { PLOG(ERROR) << "Failed to set non-movable file for f2fs: " << path << " on " << blk_dev; return kUncryptIoctlError; } off64_t pos = 0; int last_progress = 0; while (pos < sb.st_size) { // Update the status file, progress must be between [0, 99]. int progress = static_cast<int>(100 * (double(pos) / double(sb.st_size))); if (progress > last_progress) { last_progress = progress; write_status_to_socket(progress, socket); } if ((tail+1) % WINDOW_SIZE == head) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) { PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying"; int error = retry_fibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } } add_block_to_ranges(ranges, block); //data分区是否加密 if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } head = (head + 1) % WINDOW_SIZE; ++head_block; } // read next block to tail // data分区加密 if (encrypted) { size_t to_read = static_cast<size_t>( std::min(static_cast<off64_t>(sb.st_blksize), sb.st_size - pos)); if (!android::base::ReadFully(fd, buffers[tail].data(), to_read)) { PLOG(ERROR) << "failed to read " << path; return kUncryptReadError; } pos += to_read; } else { // If we're not encrypting; we don't need to actually read // anything, just skip pos forward as if we'd read a // block. pos += sb.st_blksize; } tail = (tail+1) % WINDOW_SIZE; } while (head != tail) { // write out head buffer int block = head_block; if (ioctl(fd, FIBMAP, &block) != 0) { PLOG(ERROR) << "failed to find block " << head_block; return kUncryptIoctlError; } if (block == 0) { LOG(ERROR) << "failed to find block " << head_block << ", retrying"; int error = retry_fibmap(fd, path, &block, head_block); if (error != kUncryptNoError) { return error; } } add_block_to_ranges(ranges, block); //data分区是否加密 if (encrypted) { if (write_at_offset(buffers[head].data(), sb.st_blksize, wfd, static_cast<off64_t>(sb.st_blksize) * block) != 0) { return kUncryptWriteError; } } head = (head + 1) % WINDOW_SIZE; ++head_block; } if (!android::base::WriteStringToFd( android::base::StringPrintf("%zu/n", ranges.size() / 2), mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } for (size_t i = 0; i < ranges.size(); i += 2) { if (!android::base::WriteStringToFd( android::base::StringPrintf("%d %d/n", ranges[i], ranges[i+1]), mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } } if (fsync(mapfd) == -1) { PLOG(ERROR) << "failed to fsync /"" << tmp_map_file << "/""; return kUncryptFileSyncError; } if (close(mapfd.release()) == -1) { PLOG(ERROR) << "failed to close " << tmp_map_file; return kUncryptFileCloseError; } if (encrypted) { if (fsync(wfd) == -1) { PLOG(ERROR) << "failed to fsync /"" << blk_dev << "/""; return kUncryptFileSyncError; } if (close(wfd.release()) == -1) { PLOG(ERROR) << "failed to close " << blk_dev; return kUncryptFileCloseError; } } if (rename(tmp_map_file.c_str(), map_file) == -1) { PLOG(ERROR) << "failed to rename " << tmp_map_file << " to " << map_file; return kUncryptFileRenameError; } // Sync dir to make rename() result written to disk. std::string file_name = map_file; std::string dir_name = dirname(&file_name[0]); android::base::unique_fd dfd(open(dir_name.c_str(), O_RDONLY | O_DIRECTORY)); if (dfd == -1) { PLOG(ERROR) << "failed to open dir " << dir_name; return kUncryptFileOpenError; } if (fsync(dfd) == -1) { PLOG(ERROR) << "failed to fsync " << dir_name; return kUncryptFileSyncError; } if (close(dfd.release()) == -1) { PLOG(ERROR) << "failed to close " << dir_name; return kUncryptFileCloseError; } return 0; }
这里将ota升级包生成block.map,如果升级包在的分区data是加密,那么每次获得每个block实际索引时,读取解密后的block数据到buffer,每当有5个block数据时,然后把buffer数据写入到实际的对应索引block中。
执行完成uncrypt操作,接着2.11节,执行重启的操作。
如果要测试uncrypt的功能,可以在adb shell环境下测试,将ota包推到data目录下,直接执行uncrypt命名。
# uncrypt /data/ota.zip /cache/recovery/block.map
# cat /cache/recovery/block.map
/dev/block/bootdevice/by-name/userdata //block device
1189005639 4096 //文件大小,block块大小
2 //block块的个数
440320 524288 //第一个block块的范围区间
561152 767469 //第二个block块的范围区间
接着2.11中run方法继续。
[->ShutdownThread.java]
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) { // 传过来reboot为true if (reboot) { Log.i(TAG, "Rebooting, reason: " + reason); PowerManagerService.lowLevelReboot(reason); Log.e(TAG, "Reboot failed, will attempt shutdown instead"); reason = null; } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) { // vibrate before shutting down Vibrator vibrator = new SystemVibrator(context); try { vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES); } catch (Exception e) { // Failure to vibrate shouldn't interrupt shutdown. Just log it. Log.w(TAG, "Failed to vibrate during shutdown.", e); } // vibrator is asynchronous so we need to wait to avoid shutting down too soon. try { Thread.sleep(SHUTDOWN_VIBRATE_MS); } catch (InterruptedException unused) { } } // Shutdown power Log.i(TAG, "Performing low-level shutdown..."); PowerManagerService.lowLevelShutdown(reason); }
[->PowerManagerService.java]
/** * Low-level function to reboot the device. On success, this * function doesn't return. If more than 20 seconds passes from * the time a reboot is requested, this method returns. * * @param reason code to pass to the kernel (e.g. "recovery"), or null. */ public static void lowLevelReboot(String reason) { if (reason == null) { reason = ""; } // If the reason is "quiescent", it means that the boot process should proceed // without turning on the screen/lights. // The "quiescent" property is sticky, meaning that any number // of subsequent reboots should honor the property until it is reset. if (reason.equals(PowerManager.REBOOT_QUIESCENT)) { sQuiescent = true; reason = ""; } else if (reason.endsWith("," + PowerManager.REBOOT_QUIESCENT)) { sQuiescent = true; reason = reason.substring(0, reason.length() - PowerManager.REBOOT_QUIESCENT.length() - 1); } if (reason.equals(PowerManager.REBOOT_RECOVERY) || reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) { reason = "recovery"; } if (sQuiescent) { // Pass the optional "quiescent" argument to the bootloader to let it know // that it should not turn the screen/lights on. reason = reason + ",quiescent"; } //重启操作 SystemProperties.set("sys.powerctl", "reboot," + reason); try { Thread.sleep(20 * 1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } Slog.wtf(TAG, "Unexpected return from lowLevelReboot!"); }
public static void lowLevelShutdown(String reason) {
if (reason == null) {
reason = "";
}
//关机
SystemProperties.set("sys.powerctl", "shutdown," + reason);
}
OTA升级重启前,主要的操作是对升级包进行处理的过程,对升级包提前处理的原因是因为在进入recovery模式进行升级时无法加载升级包所在的分区。在重启前的主要操作如下:
1.通过SystemProperties设置属性(setup-bcb),向BCB中设置升级的Command;
2.通过SystemProperties设置属性(uncrypt),开启uncrypt服务,将升级包生成一系列的block块,recovery可以读取block.map文件并获取这个文件的数据作为升级包;
3.uncrypt完成后,重启。
终端重启后,加载bootloader过程中,由于之前写入的recovery的command,则将进入recovery模式.
[->aboot.c]
void aboot_init(const struct app_descriptor *app) { unsigned reboot_mode = 0; int boot_err_type = 0; int boot_slot = INVALID; /* Initialise wdog to catch early lk crashes */ #if WDOG_SUPPORT msm_wdog_init(); #endif /* Setup page size information for nv storage */ if (target_is_emmc_boot()) { page_size = mmc_page_size(); page_mask = page_size - 1; mmc_blocksize = mmc_get_device_blocksize(); mmc_blocksize_mask = mmc_blocksize - 1; } else { page_size = flash_page_size(); page_mask = page_size - 1; } ASSERT((MEMBASE + MEMSIZE) > MEMBASE); read_device_info(&device); read_allow_oem_unlock(&device); /* Detect multi-slot support */ if (partition_multislot_is_supported()) { boot_slot = partition_find_active_slot(); if (boot_slot == INVALID) { boot_into_fastboot = true; dprintf(INFO, "Active Slot: (INVALID)/n"); } else { /* Setting the state of system to boot active slot */ partition_mark_active_slot(boot_slot); dprintf(INFO, "Active Slot: (%s)/n", SUFFIX_SLOT(boot_slot)); } } /* Display splash screen if enabled */ #if DISPLAY_SPLASH_SCREEN #if NO_ALARM_DISPLAY if (!check_alarm_boot()) { #endif dprintf(SPEW, "Display Init: Start/n"); #if DISPLAY_HDMI_PRIMARY if (!strlen(device.display_panel)) strlcpy(device.display_panel, DISPLAY_PANEL_HDMI, sizeof(device.display_panel)); #endif #if ENABLE_WBC /* Wait if the display shutdown is in progress */ while(pm_app_display_shutdown_in_prgs()); if (!pm_appsbl_display_init_done()) target_display_init(device.display_panel); else display_image_on_screen(); #else target_display_init(device.display_panel); #endif dprintf(SPEW, "Display Init: Done/n"); #if NO_ALARM_DISPLAY } #endif #endif target_serialno((unsigned char *) sn_buf); dprintf(SPEW,"serial number: %s/n",sn_buf); memset(display_panel_buf, '/0', MAX_PANEL_BUF_SIZE); /* * Check power off reason if user force reset, * if yes phone will do normal boot. */ if (is_user_force_reset()) goto normal_boot; /* Check if we should do something other than booting up */ if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN)) { dprintf(ALWAYS,"dload mode key sequence detected/n"); reboot_device(EMERGENCY_DLOAD); dprintf(CRITICAL,"Failed to reboot into dload mode/n"); boot_into_fastboot = true; } if (!boot_into_fastboot) { if (keys_get_state(KEY_HOME) || keys_get_state(KEY_VOLUMEUP)) boot_into_recovery = 1; if (!boot_into_recovery && (keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN))) boot_into_fastboot = true; } #if NO_KEYPAD_DRIVER if (fastboot_trigger()) boot_into_fastboot = true; #endif #if USE_PON_REBOOT_REG reboot_mode = check_hard_reboot_mode(); #else reboot_mode = check_reboot_mode(); #endif if (reboot_mode == RECOVERY_MODE) { boot_into_recovery = 1; } else if(reboot_mode == FASTBOOT_MODE) { boot_into_fastboot = true; } else if(reboot_mode == ALARM_BOOT) { boot_reason_alarm = true; } #if VERIFIED_BOOT || VERIFIED_BOOT_2 else if (VB_M <= target_get_vb_version()) { if (reboot_mode == DM_VERITY_ENFORCING) { device.verity_mode = 1; write_device_info(&device); } #if ENABLE_VB_ATTEST else if (reboot_mode == DM_VERITY_EIO) #else else if (reboot_mode == DM_VERITY_LOGGING) #endif { device.verity_mode = 0; write_device_info(&device); } else if (reboot_mode == DM_VERITY_KEYSCLEAR) { if(send_delete_keys_to_tz()) ASSERT(0); } } #endif normal_boot: if (!boot_into_fastboot) { if (target_is_emmc_boot()) { if(emmc_recovery_init()) dprintf(ALWAYS,"error in emmc_recovery_init/n"); if(target_use_signed_kernel()) { if((device.is_unlocked) || (device.is_tampered)) { #ifdef TZ_TAMPER_FUSE set_tamper_fuse_cmd(HLOS_IMG_TAMPER_FUSE); #endif #if USE_PCOM_SECBOOT set_tamper_flag(device.is_tampered); #endif } } retry_boot: /* Trying to boot active partition */ if (partition_multislot_is_supported()) { boot_slot = partition_find_boot_slot(); partition_mark_active_slot(boot_slot); if (boot_slot == INVALID) goto fastboot; } boot_err_type = boot_linux_from_mmc(); switch (boot_err_type) { case ERR_INVALID_PAGE_SIZE: case ERR_DT_PARSE: case ERR_ABOOT_ADDR_OVERLAP: case ERR_INVALID_BOOT_MAGIC: if(partition_multislot_is_supported()) { /* * Deactivate current slot, as it failed to * boot, and retry next slot. */ partition_deactivate_slot(boot_slot); goto retry_boot; } else break; default: break; /* going to fastboot menu */ } } else { //见3.2节 recovery_init(); #if USE_PCOM_SECBOOT if((device.is_unlocked) || (device.is_tampered)) set_tamper_flag(device.is_tampered); #endif boot_linux_from_flash(); } dprintf(CRITICAL, "ERROR: Could not do normal boot. Reverting " "to fastboot mode./n"); } fastboot: /* We are here means regular boot did not happen. Start fastboot. */ /* register aboot specific fastboot commands */ aboot_fastboot_register_commands(); /* dump partition table for debug info */ partition_dump(); /* initialize and start fastboot */ #if !VERIFIED_BOOT_2 fastboot_init(target_get_scratch_address(), target_get_max_flash_size()); #else /* Add salt buffer offset at start of image address to copy VB salt */ fastboot_init(ADD_SALT_BUFF_OFFSET(target_get_scratch_address()), SUB_SALT_BUFF_OFFSET(target_get_max_flash_size())); #endif #if FBCON_DISPLAY_MSG display_fastboot_menu(); #endif }
aboot执行后读取bootloader中command命令,执行recovery_init.
[->recovery.c]
/* Bootloader / Recovery Flow * * On every boot, the bootloader will read the recovery_message * from flash and check the command field. The bootloader should * deal with the command field not having a 0 terminator correctly * (so as to not crash if the block is invalid or corrupt). * * The bootloader will have to publish the partition that contains * the recovery_message to the linux kernel so it can update it. * * if command == "boot-recovery" -> boot recovery.img * else if command == "update-radio" -> update radio image (below) * else -> boot boot.img (normal boot) * * Radio Update Flow * 1. the bootloader will attempt to load and validate the header * 2. if the header is invalid, status="invalid-update", goto #8 * 3. display the busy image on-screen * 4. if the update image is invalid, status="invalid-radio-image", goto #8 * 5. attempt to update the firmware (depending on the command) * 6. if successful, status="okay", goto #8 * 7. if failed, and the old image can still boot, status="failed-update" * 8. write the recovery_message, leaving the recovery field * unchanged, updating status, and setting command to * "boot-recovery" * 9. reboot * * The bootloader will not modify or erase the cache partition. * It is recovery's responsibility to clean up the mess afterwards. */ int recovery_init (void) { struct recovery_message msg; char partition_name[32]; unsigned valid_command = 0; int update_status = 0; // get recovery message if (get_recovery_message(&msg)) return -1; msg.command[sizeof(msg.command)-1] = '/0'; //Ensure termination if (msg.command[0] != 0 && msg.command[0] != 255) { dprintf(INFO,"Recovery command: %d %s/n", sizeof(msg.command), msg.command); } if (!strcmp("boot-recovery",msg.command)) { if(!strcmp("RADIO",msg.status)) { /* We're now here due to radio update, so check for update status */ int ret = get_boot_info_apps(UPDATE_STATUS, (unsigned int *) &update_status); if(!ret && (update_status & 0x01)) { dprintf(INFO,"radio update success/n"); strlcpy(msg.status, "OKAY", sizeof(msg.status)); } else { dprintf(INFO,"radio update failed/n"); strlcpy(msg.status, "failed-update", sizeof(msg.status)); } strlcpy(msg.command, "", sizeof(msg.command)); // clearing recovery command set_recovery_message(&msg); // send recovery message boot_into_recovery = 1; // Boot in recovery mode return 0; } boot_into_recovery = 1; // Boot in recovery mode return 0; } if (!strcmp("update-radio",msg.command)) { dprintf(INFO,"start radio update/n"); valid_command = 1; strlcpy(partition_name, "FOTA", sizeof(partition_name)); } //Todo: Add support for bootloader update too. if(!valid_command) { //We need not to do anything return 0; // Boot in normal mode } if (set_ssd_radio_update(partition_name)) { /* If writing to FOTA partition fails */ strlcpy(msg.command, "", sizeof(msg.command)); strlcpy(msg.status, "failed-update", sizeof(msg.status)); goto SEND_RECOVERY_MSG; } else { /* Setting this to check the radio update status */ strlcpy(msg.command, "boot-recovery", sizeof(msg.command)); strlcpy(msg.status, "RADIO", sizeof(msg.status)); goto SEND_RECOVERY_MSG; } strlcpy(msg.status, "OKAY", sizeof(msg.status)); SEND_RECOVERY_MSG: set_recovery_message(&msg); // send recovery message boot_into_recovery = 1; // Boot in recovery mode reboot_device(0); return 0; }
参照代码前注释boot recovery.img之后,将会执行recovery.cpp中的main方法。
[->recovery.cpp]
/* * 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) * * The arguments which may be supplied in the recovery.command file: * --update_package=path - verify install an OTA package file * --wipe_data - erase user data (and cache), then reboot * --prompt_and_wipe_data - prompt the user that data is corrupt, * with their consent erase user data (and cache), then reboot * --wipe_cache - wipe cache (but not user data), then reboot * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs * --just_exit - do nothing; exit and reboot * * After completing, we remove /cache/recovery/command and reboot. * Arguments may also be supplied in the bootloader control block (BCB). * These important scenarios must be safely restartable at any point: * * FACTORY RESET * 1. user selects "factory reset" * 2. main system writes "--wipe_data" to /cache/recovery/command * 3. main system reboots into recovery * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data" * -- after this, rebooting will restart the erase -- * 5. erase_volume() reformats /data * 6. erase_volume() reformats /cache * 7. finish_recovery() erases BCB * -- after this, rebooting will restart the main system -- * 8. main() calls reboot() to boot main system * * OTA INSTALL * 1. main system downloads OTA package to /cache/some-filename.zip * 2. main system writes "--update_package=/cache/some-filename.zip" * 3. main system reboots into recovery * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..." * -- after this, rebooting will attempt to reinstall the update -- * 5. install_package() attempts to install the update * NOTE: the package install must itself be restartable from any point * 6. finish_recovery() erases BCB * -- after this, rebooting will (try to) restart the main system -- * 7. ** if install failed ** * 7a. prompt_and_wait() shows an error icon and waits for the user * 7b. the user reboots (pulling the battery, etc) into the main system */ int main(int argc, char **argv) { // We don't have logcat yet under recovery; so we'll print error on screen and // log to stdout (which is redirected to recovery.log) as we used to do. android::base::InitLogging(argv, &UiLogger); // 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. if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { minadbd_main(); return 0; } time_t start = time(nullptr); // 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(); has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr; std::vector<std::string> args = get_args(argc, argv); std::vector<char*> args_to_parse(args.size()); std::transform(args.cbegin(), args.cend(), args_to_parse.begin(), [](const std::string& arg) { return const_cast<char*>(arg.c_str()); }); const char* update_package = nullptr; bool should_wipe_data = false; bool should_prompt_and_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; if (has_cache && ensure_path_mounted(CACHE_ROOT) == 0) { //Create /cache/recovery specifically if it is not created //As in cases where device is booted into recovery directly after //flashing recovery folder is not created in init mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle); } int arg; int option_index; //解析参数 while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS, &option_index)) != -1) { switch (arg) { 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 'p': shutdown_after = true; break; case 'r': reason = optarg; break; case 'e': security_update = true; break; case 0: { std::string option = OPTIONS[option_index].name; if (option == "wipe_ab") { should_wipe_ab = true; } else if (option == "wipe_package_size") { android::base::ParseUint(optarg, &wipe_package_size); } else if (option == "prompt_and_wipe_data") { should_prompt_and_wipe_data = true; } break; } case '?': LOG(ERROR) << "Invalid command argument"; continue; } } if (locale.empty()) { if (has_cache) { locale = load_locale_from_cache(); } if (locale.empty()) { locale = DEFAULT_LOCALE; } } printf("locale is [%s]/n", locale.c_str()); printf("stage is [%s]/n", stage.c_str()); printf("reason is [%s]/n", reason); 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(); } } // 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.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } ui->SetBackground(RecoveryUI::NONE); if (show_text) ui->ShowText(true); sehandle = selinux_android_file_context_handle(); selinux_android_set_sehandle(sehandle); if (!sehandle) { ui->Print("Warning: No file_contexts/n"); } device->StartRecovery(); printf("Command:"); for (const auto& arg : args) { printf(" /"%s/"", arg.c_str()); } printf("/n/n"); if (update_package) { 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 file if (is_ufs_dev()) { if(do_sdcard_mount_for_ufs() != 0) { status = INSTALL_ERROR; goto error; } mount_required = false; } else { ui->Print("Update via sdcard on EMMC dev. Using path from fstab/n"); } } } property_list(print_property, nullptr); printf("/n"); ui->Print("Supported API: %d/n", kRecoveryApiVersion); if (update_package != nullptr) { // 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 { // It's a fresh update. Initialize the retry_count in the BCB to 1; therefore we can later // identify the interrupted update due to unexpected reboots. if (retry_count == 0) { set_retry_bootloader_message(retry_count + 1, args); } //见3.4节,安装升级包 status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, mount_required, 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 RETRY_LIMIT // times before we abandon this OTA update. if (status == INSTALL_RETRY && retry_count < RETRY_LIMIT) { copy_logs(); retry_count += 1; 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); } } } } else if (should_wipe_data) { if (!wipe_data(device)) { status = INSTALL_ERROR; } } else if (should_prompt_and_wipe_data) { ui->ShowText(true); ui->SetBackground(RecoveryUI::ERROR); if (!prompt_and_wipe_data(device)) { status = INSTALL_ERROR; } ui->ShowText(false); } 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(&should_wipe_cache, TEMPORARY_INSTALL_FILE); 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) { // If this is an eng or userdebug build, automatically turn on the text display if no command // is specified. Note that this should be called before setting the background to avoid // flickering the background image. if (is_ro_debuggable()) { ui->ShowText(true); } status = INSTALL_NONE; // No command specified ui->SetBackground(RecoveryUI::NO_COMMAND); } error: if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) { ui->SetBackground(RecoveryUI::ERROR); if (!ui->IsTextVisible()) { sleep(5); } } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; // 1. If the recovery menu is visible, prompt and wait for commands. // 2. If the state is INSTALL_NONE, wait for commands. (i.e. In user build, manually reboot into // recovery to sideload a package.) // 3. sideload_auto_reboot is an option only available in user-debug build, reboot the device // without waiting. // 4. In all other cases, reboot the device. Therefore, normal users will observe the device // reboot after it shows the "error" screen for 5s. if ((status == INSTALL_NONE && !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. // 见3.8节 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(); } // Should be unreachable. return EXIT_SUCCESS; }
获取参数后,根据相应的参数执行相应的操作,这里是执行OTA INSTALL的流程,install_package完成之执行finish_recovery操作,之后正式完成升级的操作。
[->install.cpp]
int install_package(const std::string& path, bool* wipe_cache, const std::string& install_file, bool needs_mount, int retry_count) { CHECK(!path.empty()); CHECK(!install_file.empty()); CHECK(wipe_cache != nullptr); modified_flash = true; auto start = std::chrono::system_clock::now(); int start_temperature = GetMaxValueFromThermalZone(); int max_temperature = start_temperature; int result = 0; std::vector<std::string> log_buffer; if (needs_mount == true) result = setup_install_mounts(); if (result != 0 ) { LOG(ERROR) << "failed to set up expected mounts for install; aborting"; result = INSTALL_ERROR; } else { //见3.5节 result = really_install_package(path, wipe_cache, needs_mount, &log_buffer, retry_count, &max_temperature); } // 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()); bool has_cache = volume_for_mount_point("/cache") != nullptr; // Skip logging the uncrypt_status on devices without /cache. if (has_cache) { static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status"; if (ensure_path_mounted(UNCRYPT_STATUS) != 0) { LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS; } else { std::string uncrypt_status; if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) { PLOG(WARNING) << "failed to read uncrypt status"; } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) { LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status; } 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), }; int end_temperature = GetMaxValueFromThermalZone(); max_temperature = std::max(end_temperature, max_temperature); if (start_temperature > 0) { log_buffer.push_back("temperature_start: " + std::to_string(start_temperature)); } if (end_temperature > 0) { log_buffer.push_back("temperature_end: " + std::to_string(end_temperature)); } if (max_temperature > 0) { log_buffer.push_back("temperature_max: " + std::to_string(max_temperature)); } std::string log_content = android::base::Join(log_header, "/n") + "/n" + android::base::Join(log_buffer, "/n") + "/n"; if (!android::base::WriteStringToFile(log_content, install_file)) { PLOG(ERROR) << "failed to write " << install_file; } // Write a copy into last_log. LOG(INFO) << log_content; return result; }
[->install.cpp]
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) { //ui显示 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(INFO) << "Update location: " << path; // Map the update package into memory. ui->Print("Opening update package.../n"); if (needs_mount) { 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"; log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure)); 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; } // 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; } // 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); //见3.6节 int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature); ui->SetEnableReboot(true); ui->Print("/n"); CloseArchive(zip); return result; }
[->install.cpp]
static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache, std::vector<std::string>* log_buffer, int retry_count, int* max_temperature) { read_source_target_build(zip, log_buffer); int pipefd[2]; pipe(pipefd); std::vector<std::string> args; #ifdef AB_OTA_UPDATER //执行update_binary_command操作,见3.7节 int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count, pipefd[1], &args); #else int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1], &args); #endif if (ret) { close(pipefd[0]); close(pipefd[1]); log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); return ret; } // 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. // // 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). // // retry_update // updater encounters some issue during the update. It requests // a reboot to retry the same package automatically. // // log <string> // updater requests logging the string (e.g. cause of the // failure). // // - the name of the package zip file. // // - an optional argument "retry" if this update is a retry of a failed // update attempt. // // 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(); } pid_t pid = fork(); if (pid == -1) { close(pipefd[0]); close(pipefd[1]); PLOG(ERROR) << "Failed to fork update binary"; log_buffer->push_back(android::base::StringPrintf("error: %d", kForkUpdateBinaryFailure)); return INSTALL_ERROR; } if (pid == 0) { umask(022); close(pipefd[0]); execv(chr_args[0], const_cast<char**>(chr_args)); // Bug: 34769056 // We shouldn't use LOG/PLOG in the forked process, since they may cause // the child process to hang. This deadlock results from an improperly // copied mutex in the ui functions. fprintf(stdout, "E:Can't run %s (%s)/n", chr_args[0], strerror(errno)); _exit(EXIT_FAILURE); } close(pipefd[1]); std::atomic<bool> logger_finished(false); std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished)); *wipe_cache = false; bool retry_update = false; 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); logger_finished.store(true); finish_log_temperature.notify_one(); temperature_logger.join(); if (retry_update) { return INSTALL_RETRY; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")"; return INSTALL_ERROR; } return INSTALL_SUCCESS; }
[->install.cpp]
int update_binary_command(const std::string& package, ZipArchiveHandle zip, const std::string& binary_path, 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; if (FindEntry(zip, binary_name, &binary_entry) != 0) { LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME; return INSTALL_CORRUPT; } unlink(binary_path.c_str()); int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755); if (fd == -1) { PLOG(ERROR) << "Failed to create " << binary_path; 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 = { binary_path, std::to_string(kRecoveryApiVersion), std::to_string(status_fd), package, }; if (retry_count > 0) { cmd->push_back("retry"); } return 0; }
前面介绍到update-binary相当于一个脚本解释器,能够识别updater-script中描述的操作。来看下updater-script中的内容:
...
show_progress(0.650000, 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.br", "system.patch.dat") ||
abort("E1001: Failed to update system image.");
show_progress(0.100000, 0);
ui_print("Patching vendor image unconditionally...");
block_image_update("/dev/block/bootdevice/by-name/vendor", package_extract_file("vendor.transfer.list"), "vendor.new.dat.br", "vendor.patch.dat") ||
abort("E2001: Failed to update vendor image.");
show_progress(0.050000, 5);
package_extract_file("boot.img", "/dev/block/bootdevice/by-name/boot");
show_progress(0.200000, 10);
...
调用的是block_image_update,传入的是升级包里面的system.transfer.list和system.new.dat.br来实现升级。
block_image_update在bootable/recovery/updater/blockimg.cpp中,具体的实现PerformBlockImageUpdate函数中,这里不再详细展开。
void RegisterBlockImageFunctions() {
RegisterFunction("block_image_verify", BlockImageVerifyFn);
RegisterFunction("block_image_update", BlockImageUpdateFn);
RegisterFunction("block_image_recover", BlockImageRecoverFn);
RegisterFunction("check_first_block", CheckFirstBlockFn);
RegisterFunction("range_sha1", RangeSha1Fn);
}
来system.transfer.list中的内容:
4
583603
0
0
erase 6,1020,7774,524808,527824,580432,773491
new 6,0,207,222,508,8286,8817
new 2,8817,9841
new 2,9841,10865
...
zero 6,524360,524808,527824,528336,579920,579984
zero 6,579984,580432,773491,774003,786268,786332
zero 2,786332,786431
其中
4:为transfer的版本,目前支持1-4版本
583603:为总共new的block数量
0:stash slots没有使用,所以这里两个都是0
erase:需要擦除的block块范围数
new: 需要写入的block块范围数
zero: 需要填充0的block块范围数
[->recovery.cpp]
static void finish_recovery() { // Save the locale to cache, so if recovery is next started up without a '--locale' argument // (e.g., directly from the bootloader) it will use the last-known locale. if (!locale.empty() && has_cache) { LOG(INFO) << "Saving locale /"" << locale << "/""; if (ensure_path_mounted(LOCALE_FILE) != 0) { LOG(ERROR) << "Failed to mount " << LOCALE_FILE; } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) { PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE; } } copy_logs(); // Reset to normal system boot so recovery won't cycle indefinitely. std::string err; if (!clear_bootloader_message(&err)) { LOG(ERROR) << "Failed to clear BCB message: " << err; } // Remove the command file, so recovery won't repeat indefinitely. if (has_cache) { if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { LOG(WARNING) << "Can't unlink " << COMMAND_FILE; } ensure_path_unmounted(CACHE_ROOT); } sync(); // For good measure. }
完成升级后,清除BCB操作。
ota升级重启后,主要的操作如下:
1.加载bootloader,读取bootloader中的command命令
2.读取到升级的命令后,boot recovery.img,recovery.cpp中的main函数执行。
3.执行install_package操作,这里会解析ota包中的内容(block.map的形式),执行相应的升级包中脚本操作,同时会同步进行一些ui的显示操作。
4.install_package操作完成后,最后finish_recovery,完成升级的操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Dk2eqWs-1577705272566)(https://skytoby.github.io/2019/Android%20OTA%E5%8D%87%E7%BA%A7%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/OTA.jpg)]
本文分析Android终端ota升级的全过程。从介绍ota升级包的格式和系统启动模式开始作为基础,后面详细分析了从升级重启前到重启后的详细流程,其中升级重启前的流程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zInb41pn-1577704943833)(/Android OTA升级流程分析/OTA.jpg)]
OTA升级重启前,主要的操作是对升级包进行处理的过程,对升级包提前处理的原因是因为在进入recovery模式进行升级时无法加载升级包所在的分区。在重启前的主要操作如下:
1.通过SystemProperties设置属性(setup-bcb),向BCB中设置升级的Command;
2.通过SystemProperties设置属性(uncrypt),开启uncrypt服务,将升级包生成一系列的block块,recovery可以读取block.map文件并获取这个文件的数据作为升级包;
3.uncrypt完成后,重启。
ota升级重启后,主要的操作如下:
1.加载bootloader,读取bootloader中的command命令
2.读取到升级的命令后,boot recovery.img,recovery.cpp中的main函数执行。
3.执行install_package操作,这里会解析ota包中的内容(block.map的形式),执行相应的升级包中脚本操作,同时会同步进行一些ui的显示操作。
4.install_package操作完成后,最后finish_recovery,完成升级的操作。
源码路径
frameworks/base/core/java/android/os/RecoverySystem.java
frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
frameworks/base/core/java/android/os/PowerManager.java
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
bootable/recovery/uncrypt/uncrypt.cpp
bootable/recovery/uncrypt/uncrypt.rc
bootable/bootloader/lk/app/aboot/aboot.c
bootable/bootloader/lk/app/aboot/recovery.c
bootable/recovery/updater/install.cpp
bootable/recovery/install.cpp
bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h
bootable/recovery/updater/blockimg.cpp
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。