一、ota流程之framwork installpackage流程
关于install_package的流程,大部分应该已经比较了解,按方法流程执行,这里我们流程上还是做简单说明,主要要要说明其中的一些细节
1、setup-bcb
2、block.map的生成
按方法流程分析如下
framwork/base/core/java/android/os/RecoverySystem.java @RequiresPermission(android.Manifest.permission.RECOVERY) public static void installPackage(Context context, File packageFile) throws IOException { installPackage(context, packageFile, false); } //传入context,文件路径,processed = false public static void installPackage(Context context, File packageFile, boolean processed) throws IOException { //防止并发执行请求。 synchronized (sRequestLock) { //删除cache/recovery/log LOG_FILE.delete(); // Must delete the file in case it was created by system server. //刪除cache/recovery/uncrypt文件,保证为当前系统服务创建 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/")) { //这里传进来的就是false,如果是直接调取的installpackage 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.如果unvrypt_file不可读也不可写,打印出错误 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文件 BLOCK_MAP_FILE.delete(); } // If the package is on the /data partition, use the block map // file as the package name instead. //设定文件名为@/cache/recovery/block.map 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"; //最初的command,包含文件名和语言 String command = filenameArg + localeArg; if (securityUpdate) { command += securityArg; } RecoverySystem rs = (RecoverySystem) context.getSystemService( Context.RECOVERY_SERVICE); //判断是否可以正常写入bcb,传入command, //这里存在一个优化点,我们碰到很多解密失败的问题,那为什么不先解密完成后返回状态,再写入bcb呢, //这样直接判定失败,就不用在recovery出现failed to map file的问题 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); //public static final String REBOOT_RECOVERY_UPDATE = "recovery-update"; 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?)"); } }
setupBcb(command)
framwork/base/core/java/android/os/RecoverySystem.java /** * Talks to RecoverySystemService via Binder to clear up the BCB. */ private boolean clearBcb() { try { return mService.clearBcb(); } catch (RemoteException unused) { } return false; }
framwork/base/services/core/java/com/android/server/RecoverySystemService.java @Override // Binder call public boolean clearBcb() { if (DEBUG) Slog.d(TAG, "clearBcb"); synchronized (sRequestLock) { return setupOrClearBcb(false, null); } }
framwork/base/services/core/java/com/android/server/RecoverySystemService.java //setupbcb传入 isSetup = true , command = 路径 + 语言 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; } if (isSetup) { //属性“ ctrl.start ”和“ ctrl.stop ”是用来启动和停止服务。每一项服务必须在/init.rc中定义.系统启动时, //与init守护进程将解析init.rc和启动属性服务。一旦收到设置“ ctrl.start ”属性的请求,属性服务将使用该属性值作为服务名找到该服务,启动该服务。 //这项服务的启动结果将会放入“ init.svc.<服务名>“属性中 。客户端应用程序可以轮询那个属性值,以确定结果。 SystemProperties.set("ctl.start", "setup-bcb"); } else { SystemProperties.set("ctl.start", "clear-bcb"); } // Connect to the uncrypt service socket. 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. // 将commands参数传入uncrypt.cpp,执行write_bootloader_message写入command信息 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; } }
通过socket连接到uncryt服务,传入commang数据,并执行写入
framwork/base/services/core/java/com/android/server/RecoverySystemService.java //通过socket连接到服务 private LocalSocket connectService() { LocalSocket socket = new LocalSocket(); boolean done = false; // The uncrypt socket will be created by init upon receiving the // service request. It may not be ready by this point. So we will // keep retrying until success or reaching timeout. for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { try { socket.connect(new LocalSocketAddress(UNCRYPT_SOCKET, LocalSocketAddress.Namespace.RESERVED)); done = true; break; } catch (IOException ignored) { try { Thread.sleep(1000); } catch (InterruptedException e) { Slog.w(TAG, "Interrupted:", e); } } } if (!done) { Slog.e(TAG, "Timed out connecting to uncrypt socket"); return null; } return socket; }
SystemProperties.set("ctl.start", "setup-bcb");后会启动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
该服务的编译是在bootable/recovery/uncrypt/编译生成的,生成为system/bin/下的可执行文件uncrypt
开启服务后,这里传入了setup-bcb,那么我们看下uncrypt.cpp的main方法,
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; } bool success = false; switch (action) { 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; } }
setup_bcb(socket_fd)
static bool setup_bcb(const int socket) { // c5. receive message length int length; if (!android::base::ReadFully(socket, &length, 4)) { PLOG(ERROR) << "failed to read the length"; return false; } length = ntohl(length); // c7. receive message std::string content; content.resize(length); if (!android::base::ReadFully(socket, &content[0], length)) { PLOG(ERROR) << "failed to read the message"; return false; } LOG(INFO) << " received command: [" << content << "] (" << content.size() << ")"; std::vector<std::string> options = android::base::Split(content, "\n"); std::string wipe_package; for (auto& option : options) { if (android::base::StartsWith(option, "--wipe_package=")) { std::string path = option.substr(strlen("--wipe_package=")); if (!android::base::ReadFileToString(path, &wipe_package)) { PLOG(ERROR) << "failed to read " << path; return false; } option = android::base::StringPrintf("--wipe_package_size=%zu", wipe_package.size()); } } // c8. setup the bcb command std::string err; //具体写入的方法,之前分析recovery流程的时候也有这个方法 if (!write_bootloader_message(options, &err)) { LOG(ERROR) << "failed to set bootloader message: " << err; write_status_to_socket(-1, socket); return false; } if (!wipe_package.empty() && !write_wipe_package(wipe_package, &err)) { PLOG(ERROR) << "failed to set wipe package: " << err; write_status_to_socket(-1, socket); return false; } // c10. send "100" status write_status_to_socket(100, socket); return true; }
pm.reboot(reason);
//public static final String REBOOT_RECOVERY_UPDATE = "recovery-update"; framwok/base/core/java/android/os/PowerManager.java public void reboot(String reason) { try { mService.reboot(false, reason, true); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
framwork/base/services/core/java/com/android/server/power/PowerManagerService.java @Override // Binder call //mService.reboot(false, reason, true) 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); } //关于clearCallingIdentity,restoreCallingIdentity https://blog.csdn.net/windskier/article/details/6921672 final long ident = Binder.clearCallingIdentity(); try { shutdownOrRebootInternal(HALT_MODE_REBOOT, confirm, reason, wait); } finally { Binder.restoreCallingIdentity(ident); } }
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()"); } } Runnable runnable = new Runnable() { @Override public void run() { synchronized (this) { if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) { ShutdownThread.rebootSafeMode(getUiContext(), confirm); //传入的参数为HALT_MODE_REBOOT,执行reboot } 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.reboot(getUiContext(), reason, confirm);
framwok/base/services/core/java/com/android/server/power/ShutdownThread.java public static void reboot(final Context context, String reason, boolean confirm) { mReboot = true; mRebootSafeMode = false; mRebootHasProgressBar = false; mReason = reason; shutdownInner(context, confirm); }
shutdownInner(context, confirm) ---> beginShutdownSequence(Context context)--->sInstance.start()--->run()
framwok/base/services/core/java/com/android/server/power/ShutdownThread.java 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); 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. } rebootOrShutdown(mContext, mReboot, mReason); }
uncrypt();
framwok/base/services/core/java/com/android/server/power/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); } } }
framwork/base/core/java/android/os/RecoverySystem.java @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(Context context, File packageFile, final ProgressListener listener) throws IOException { processPackage(context, packageFile, listener, null); } 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"); } }
rs.uncrypt(filename, progressListener) 这个时候生成block.map的流程与前面setup-bcb的流程是相似的,使用binder机制回调uncrypt方法,使用socket写入数据和返回结果,具体由uncrypt.cpp执行
framwork/base/core/java/android/os/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; }
framwork/base/services/core/java/com/android/server/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. 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; } }
uncrypt_wrapper(input_path, map_file, socket_fd);
uncrypt.cpp 官方注解
// This program takes a file on an ext4 filesystem and produces a list
// of the blocks that file occupies, which enables the file contents
// to be read directly from the block device without mounting the
// filesystem.
//
// 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.
bootable/recovery/uncrypt/uncrypt.cpp 传入参数input_path /data/data/com.adups.fota/files/adupsfota/update.zip map_file /cache/recovery/block.map 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; //如果传入的input_path为空 if (input_path == nullptr) { //从UNCRYPT_PATH_FILE中找到对应升级包的路径 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并返回status的状态通过socket给到socket给到recoverysystem 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; } write_status_to_socket(100, socket); return true; }
uncrypt(input_path, map_file, socket);
bootable/recovery/uncrypt/uncrypt.cpp static int uncrypt(const char* input_path, const char* map_file, const int socket) { //update package is "/data/data/com.adups.fota/files/adupsfota/update.zip" 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]; //函数说明:realpath()用来将参数path所指的相对路径转换成绝对路径后存于参数resolved_path所指的字符串数组或指针中 //返回值: 成功则返回指向resolved_path的指针,失败返回NULL,错误代码存于errno if (realpath(input_path, path) == nullptr) { PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path"; return kUncryptRealpathFindError; } //这三个参数来着fstab文件 bool encryptable; bool encrypted; bool f2fs_fs; //block_dev /dev/block/platform/bootdevice/by-name/userdata 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). //I /system/bin/uncrypt: encryptable: yes //I /system/bin/uncrypt: encrypted: yes 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. //I /system/bin/uncrypt: writing block map /cache/recovery/block.ma if (strncmp(path, "/data/", 6) == 0) { LOG(INFO) << "writing block map " << map_file; return produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket); } return 0; }
produce_block_map(path, map_file, blk_dev, encrypted, f2fs_fs, socket); 核心方法
bootable/recovery/uncrypt/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; //如果block.map存在值,先删除之前的内容 if (!android::base::RemoveFileIfExists(map_file, &err)) { LOG(ERROR) << "failed to remove the existing map file " << map_file << ": " << err; return kUncryptFileRemoveError; } //使用block.map.tmp进行写入操作 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; //stat函数可以返回一个结构,里面包括文件的全部属性 if (stat(path, &sb) != 0) { LOG(ERROR) << "failed to stat " << path; return kUncryptFileStatError; } ///system/bin/uncrypt: block size: 4096 bytes LOG(INFO) << " block size: " << sb.st_blksize << " bytes"; //计算出升级包大小占用的block总数 int blocks = ((sb.st_size-1) / sb.st_blksize) + 1; ///system/bin/uncrypt: file size: 13766599 bytes, 3361 blocks 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)); //将blk_dev block块,sb.st_size 所占用的总大小,sb.st_blksize单个block块大小 //通过writestringtofd方法写入到block.map.tmp文件中 if (!android::base::WriteStringToFd(s, mapfd)) { PLOG(ERROR) << "failed to write " << tmp_map_file; return kUncryptWriteError; } std::vector<std::vector<unsigned char>> buffers; if (encrypted) { //resize()的作用是改变vector中元素的数目。 //如果n比当前的vector元素数目要小,vector的容量要缩减到resize的第一个参数大小,既n。并移除那些超出n的元素同时销毁他们。 //如果n比当前vector元素数目要大,在vector的末尾扩展需要的元素数目,如果第二个参数val指定了,扩展的新元素初始化为val的副本,否则按类型默认初始化。 //申请5个blocksize大小的buffer空间 buffers.resize(WINDOW_SIZE, std::vector<unsigned char>(sb.st_blksize)); } int head_block = 0; int head = 0, tail = 0; //打开升级包路径data/data..../update.zip 句柄为fd android::base::unique_fd fd(open(path, O_RDONLY)); if (fd == -1) { PLOG(ERROR) << "failed to open " << path << " for reading"; return kUncryptFileOpenError; } //打开dev_blk /dev/block/platform/bootdevice/by-name/userdata 句柄为wfd 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; } } // F2FS-specific ioctl // It requires the below kernel commit merged in v4.16-rc1. // 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file") // In android-4.4, // 56ee1e817908 ("f2fs: updates on v4.16-rc1") // In android-4.9, // 2f17e34672a8 ("f2fs: updates on v4.16-rc1") // In android-4.14, // ce767d9a55bc ("f2fs: updates on v4.16-rc1") #ifndef F2FS_IOC_SET_PIN_FILE #ifndef F2FS_IOCTL_MAGIC #define F2FS_IOCTL_MAGIC 0xf5 #endif #define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32) #define F2FS_IOC_GET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 14, __u32) #endif //生成于升级包对应的稀疏列表 if (f2fs_fs) { __u32 set = 1; int error = ioctl(fd, F2FS_IOC_SET_PIN_FILE, &set); // Don't break the old kernels which don't support it. if (error < 0) { PLOG(ERROR) << "Failed to set pin_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); 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 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); 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; } //把稀疏列表写入到block.map.tmp 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; } } //修改名称block.map.tmp 为 block.map 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; }
rebootOrShutdown(mContext, mReboot, mReason);
framwork/base/services/core/java/com/android/server/power/ShutdownThread.java public static void rebootOrShutdown(final Context context, boolean reboot, String reason) { 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) { } } ///M: added for shutdown Enhancement@{ sInstance.mLowLevelShutdownSeq(context); /// @} // Shutdown power Log.i(TAG, "Performing low-level shutdown..."); PowerManagerService.lowLevelShutdown(reason); }
lowLevelReboot(String reason)
填坑1、关于把boot-recovery写入bcb
一开始我一直认为写入bcb的时候,直接把boot-command传入了 boot-recovery,但是在流程中,是最后写入属性SystemProperties.set("sys.powerctl", "reboot," + reason);调用reboot可执行程序,最后通过linux内核与bootloader交互进入recovery,这里给到的command就是boot-recovery
framwok/base/services/core/java/com/android/server/power/PowerManagerService.java 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"; } //设置重启参数,调用init和可执行程序reboot SystemProperties.set("sys.powerctl", "reboot," + reason); try { Thread.sleep(20 * 1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } Slog.wtf(TAG, "Unexpected return from lowLevelReboot!"); }
这基本上就时重启之前的流程了