Skytoby

Android OTA升级流程分析

Android OTA升级流程分析

一、概述

目前Android系统终端的升级主要是通过无线进行的(FOTA,Firmware Over-The-Air),主要流程是通过无线方式将升级包下载到终端,而后调用系统的升级接口进行升级。本文主要分析升级包下载后,调用系统升级接口之后的流程。

1.1 升级包结构

升级包是用make otapackage命令生成的,对于差分包,需要生成两个ota整包,再用系统的编译工具利用这两个整包生成一个差分包。下面是某终端Android9.0版本的ota升级包结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
├─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签名。

1.2 系统启动模式

Android系统启动主要有两种:

1.组合键

用户通过按下组合键,不同的终端可以定义组合键,进入不同的工作模式,在bootable/bootloader/lk/app/aboot/aboot.c文件中判断,具体有两种:

bootloader模式,可进一步进入fastboot(快速刷机模式)。

Recovery模式,进入这种模式系统会出现一个简单的ui界面,用来提示用户的进一步操作。

recovery

2.正常启动

没有按下任何组合键,正常开机,bootloader会读取启动控制信息块BCB(bootloader control block),它是一个结构体,存放着启动命令Command。根据不同的命令,系统可以进入三种不同的启动模式。下面是Bootloader Message的定义,通过注释可以了解具体结构体中成员变量的含义。

[->bootloader_message.h]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* 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升级的步骤。无论ota包是通过无线下载还是导入到SD卡,最后都会调用到RecoverySystem.installPackage方法,下面分析下这个详细的流程。

2.1 RS.installPackage

[->RecoverySystem.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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从而进入升级模式。

2.2 RS.setupBcb

[->RecoverySystem.java]

1
2
3
4
5
6
7
8
9
10
11
/**
* 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;
}

2.3 RSS.setupBcb

[->RecoverySystemService.java]

1
2
3
4
5
6
7
@Override // Binder call
public boolean setupBcb(String command) {
if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
synchronized (sRequestLock) {
return setupOrClearBcb(true, command);
}
}

2.4 RSS.setupOrClearBcb

[->RecoverySystemService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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。

2.5 PM.reboot

[->PowerManager.java]

1
2
3
4
5
6
7
public void reboot(String reason) {
try {
mService.reboot(false, reason, true);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

2.6 PMS.reboot

[->PowerManagerService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 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

2.7 PMS.shutdownOrRebootInternal

[->PowerManagerService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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) {
}
}
}
}
}

2.8 ST.reboot

[->ShutdownThread.java]

1
2
3
4
5
6
7
public static void reboot(final Context context, String reason, boolean confirm) {
mReboot = true;
mRebootSafeMode = false;
mRebootHasProgressBar = false;
mReason = reason;
shutdownInner(context, confirm);
}

2.9 ST.shutdownInner

[->ShutdownThread.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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);
}
}

2.10 ST.beginShutdownSequence

[->ShutdownThread.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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();
}

2.10.1 ST.showShutdownDialog

[->ShutdownThread.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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;
}

2.11 ST.run

[->ShutdownThread.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* 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);
}

2.12 ST.uncrypt

[->ShutdownThread.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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);
}
}
}

2.13 RS.processPackage

[->RecoverySystem.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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");
}
}

2.14 RS.uncrypt

[->RecoverySystem.java]

1
2
3
4
5
6
7
8
9
10
/**
* 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;
}

2.15 RSS.uncrypt

[->RecoverySystemService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

2.16 uncrypt.main

[->uncrypt.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//
// 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;
}

通过文件头的注释,可以知道具体的通信方式。

2.17 uncrypt.uncrypt_wrapper

[->uncrypt.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
}

2.18 uncrypt.uncrypt

[->uncrypt.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;
}

2.19 uncrypt.produce_block_map

[->uncrypt.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  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命名。

1
2
3
4
5
6
7
# 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.20 ST.rebootOrShutdown

接着2.11中run方法继续。

[->ShutdownThread.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
}

2.20.1 PWS.lowLevelReboot

[->PowerManagerService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 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!");
}

2.20.2 PWS.lowLevelReboot

1
2
3
4
5
6
7
public static void lowLevelShutdown(String reason) {
if (reason == null) {
reason = "";
}
//关机
SystemProperties.set("sys.powerctl", "shutdown," + reason);
}

2.21 小结

OTA升级重启前,主要的操作是对升级包进行处理的过程,对升级包提前处理的原因是因为在进入recovery模式进行升级时无法加载升级包所在的分区。在重启前的主要操作如下:

1.通过SystemProperties设置属性(setup-bcb),向BCB中设置升级的Command;

2.通过SystemProperties设置属性(uncrypt),开启uncrypt服务,将升级包生成一系列的block块,recovery可以读取block.map文件并获取这个文件的数据作为升级包;

3.uncrypt完成后,重启。

三、OTA升级重启后

终端重启后,加载bootloader过程中,由于之前写入的recovery的command,则将进入recovery模式.

3.1 aboot.aboot_init

[->aboot.c]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
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.

3.2 recovery.recovery_init

[->recovery.c]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/* 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方法。

3.3 recovery.main

[->recovery.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/*
* 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操作,之后正式完成升级的操作。

3.4 install.install_package

[->install.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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;
}

3.5 install.really_install_package

[->install.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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;
}

3.6 install.try_update_binary

[->install.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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;
}

3.7 install.update_binary_command

[->install.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
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函数中,这里不再详细展开。

1
2
3
4
5
6
7
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中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
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块范围数

3.8 recovery.finish_recovery

[->recovery.cpp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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操作。

3.9 小结

ota升级重启后,主要的操作如下:

1.加载bootloader,读取bootloader中的command命令

2.读取到升级的命令后,boot recovery.img,recovery.cpp中的main函数执行。

3.执行install_package操作,这里会解析ota包中的内容(block.map的形式),执行相应的升级包中脚本操作,同时会同步进行一些ui的显示操作。

4.install_package操作完成后,最后finish_recovery,完成升级的操作。

四、总结

本文分析Android终端ota升级的全过程。从介绍ota升级包的格式和系统启动模式开始作为基础,后面详细分析了从升级重启前到重启后的详细流程,其中升级重启前的流程如下:

OTA

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,完成升级的操作。

附录

源码路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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