首先考虑如下情形:在main system下,我们通过OTA客户端(自己编写)选择升级包update.zip(假设存放于/mnt/sdcard中)后进入recovery模式下进行升级。在这一过程中,需要解决两个问题,第一:如果使机器重启并自动进入recovery模式;第二:如何将升级包的路径/mnt/sdcard/update.zip传入到recovery模式,使其能查找到升级包。这涉及到android系统的启动流程,如下图所示:
图1 系统启动流程
机器启动时,首先检测是否有组合键按下,如检测到(音量下+power)组合键,则进入recovery;否则检测系统的/misc分区,根据此分区存储的命令选择不同的模式。
/misc分区下存储着结构体bootloader_message,称之为BCB块,其定义如下:
struct bootloader_message{
char command[32]; //存放不同的启动命令
char status[32]; //存放执行结果
char recovery[1024]; //存放/cache/recovery/command中的命令
};
结构体成员command[32]中存放着不同的启动命令:
-
boot-recovery:系统会进入Recovery模式
-
update-radia或update-hboot:
-
command为空:正常启动,进入main system
而recovery[1024]中则存放着升级包路径,其存储结构如下:第一行存放字符串“recovery”;第二行存放路径信息“--update=/mnt/sdcard/update.zip”等。
除了BCB块外,还可以将路径信息--update=/mnt/sdcard/update.zip写入文件/cache/recovery/command传递给recovery模式。
进入recovery模式,系统通过get_args函数(如下代码所示)获取升级包信息。此函数首先获取BCB块信息(代码07-34),如果未检测到相关信息,则继续检测/cache/
recovery/command文件(代码36-53);最后,将启动命令boot-recovery及升级包路径--update=/mnt/sdcard/update.zip重新写入到BCB块中(代码55-64),以便系统下次启动时再次进入到recovery模式,直到升级成功后执行finish_recovery函数清空BCB及/cache/recovery/command文件。
-
-
-
-
-
static void
-
get_args(int *argc, char ***argv) {
-
struct bootloader_message boot;
-
memset(&boot, 0, sizeof(boot));
-
get_bootloader_message(&boot);
-
-
if (boot.command[0] != 0 && boot.command[0] != 255) {
-
LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
-
}
-
-
if (boot.status[0] != 0 && boot.status[0] != 255) {
-
LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
-
}
-
-
-
if (*argc <= 1) {
-
boot.recovery[sizeof(boot.recovery) - 1] = '\0';
-
const char *arg = strtok(boot.recovery, "\n");
-
if (arg != NULL && !strcmp(arg, "recovery")) {
-
*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
-
(*argv)[0] = strdup(arg);
-
for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
-
if ((arg = strtok(NULL, "\n")) == NULL) break;
-
(*argv)[*argc] = strdup(arg);
-
}
-
LOGI("Got arguments from boot message\n");
-
} else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
-
LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
-
}
-
}
-
-
-
if (*argc <= 1) {
-
FILE *fp = fopen_path(COMMAND_FILE, "r");
-
if (fp != NULL) {
-
char *argv0 = (*argv)[0];
-
*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
-
(*argv)[0] = argv0;
-
-
char buf[MAX_ARG_LENGTH];
-
for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
-
if (!fgets(buf, sizeof(buf), fp)) break;
-
(*argv)[*argc] = strdup(strtok(buf, "\r\n"));
-
}
-
-
check_and_fclose(fp, COMMAND_FILE);
-
LOGI("Got arguments from %s\n", COMMAND_FILE);
-
}
-
}
-
-
-
-
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
-
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
-
int i;
-
for (i = 1; i < *argc; ++i) {
-
strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));
-
strlcat(boot.recovery, "\n", sizeof(boot.recovery));
-
}
-
set_bootloader_message(&boot);
-
}
代码段1 get_args()函数
2.函数install_package()
此函数定义如下
-
int
-
install_package(const char* path, int* wipe_cache, const char* install_file)
-
{
-
FILE* install_log = fopen_path(install_file, "w");
-
if (install_log) {
-
fputs(path, install_log);
-
fputc('\n', install_log);
-
} else {
-
LOGE("failed to open last_install: %s\n", strerror(errno));
-
}
-
int result = really_install_package(path, wipe_cache);
-
if (install_log) {
-
fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
-
fputc('\n', install_log);
-
fclose(install_log);
-
}
-
return result;
-
}
代码段2 install_package()函数定义
此函数所实现的功能为:调用函数really_install_package进行升级,并将升级结果写入到文件install_file中。recovery.c中对于存放升级结果的文件定义如下:
static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
当升级完成后进入main system,可以通过此文件读取升级结果,并给出用户相应的提示:升级成功或失败。
而函数really_install_package会对升级包进行一系列的校验,通过校验后,调用try_update_binary函数完成升级。因此,try_update_binary()才是真正升级的地方。如下:
-
-
static int
-
try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
-
const ZipEntry* binary_entry =
-
mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
-
if (binary_entry == NULL) {
-
mzCloseZipArchive(zip);
-
return INSTALL_CORRUPT;
-
}
-
-
char* binary = "/tmp/update_binary";
-
unlink(binary);
-
int fd = creat(binary, 0755);
-
if (fd < 0) {
-
mzCloseZipArchive(zip);
-
LOGE("Can't make %s\n", binary);
-
return INSTALL_ERROR;
-
}
-
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);
-
close(fd);
-
mzCloseZipArchive(zip);
-
-
if (!ok) {
-
LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);
-
return INSTALL_ERROR;
-
}
-
-
int pipefd[2];
-
pipe(pipefd);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
char** args = malloc(sizeof(char*) * 5);
-
args[0] = binary;
-
args[1] = EXPAND(RECOVERY_API_VERSION);
-
args[2] = malloc(10);
-
sprintf(args[2], "%d", pipefd[1]);
-
args[3] = (char*)path;
-
args[4] = NULL;
-
-
pid_t pid = fork();
-
if (pid == 0) {
-
close(pipefd[0]);
-
execv(binary, args);
-
fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
-
_exit(-1);
-
}
-
close(pipefd[1]);
-
-
*wipe_cache = 0;
-
-
char buffer[1024];
-
FILE* from_child = fdopen(pipefd[0], "r");
-
while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
-
char* command = strtok(buffer, " \n");
-
if (command == NULL) {
-
continue;
-
} else if (strcmp(command, "progress") == 0) {
-
char* fraction_s = strtok(NULL, " \n");
-
char* seconds_s = strtok(NULL, " \n");
-
-
float fraction = strtof(fraction_s, NULL);
-
int seconds = strtol(seconds_s, NULL, 10);
-
-
ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
-
seconds);
-
} else if (strcmp(command, "set_progress") == 0) {
-
char* fraction_s = strtok(NULL, " \n");
-
float fraction = strtof(fraction_s, NULL);
-
ui_set_progress(fraction);
-
} else if (strcmp(command, "ui_print") == 0) {
-
char* str = strtok(NULL, "\n");
-
if (str) {
-
ui_print("%s", str);
-
} else {
-
ui_print("\n");
-
}
-
} else if (strcmp(command, "wipe_cache") == 0) {
-
*wipe_cache = 1;
-
} else {
-
LOGE("unknown command [%s]\n", command);
-
}
-
}
-
fclose(from_child);
-
-
int status;
-
waitpid(pid, &status, 0);
-
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
-
return INSTALL_ERROR;
-
}
-
-
return INSTALL_SUCCESS;
-
}
代码段3 try_update_binary
代码04-21:提取升级包中的"META-INF/com/google/android/update-binary"文件,此文件定义了系统升级所需要进行的操作,系统将根据其中的命令执行相应的操作;
代码73-80:创建一个子进程,执行脚本文件META-INF/com/google/android/update-binary,这是系统升级的核心所在。如下是某升级包中update-script(update-script
是update-binary文件的文本形式,我们可以通过此文件来了解升级时的具体操作)中的部分指令:
-
mount("ext4", "EMMC", "/dev/block/mmcblk0p12", "/system");
-
assert(file_getprop("/system/build.prop", "ro.build.fingerprint") == "ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A04_20130322-094434:userdebug/test-keys" ||
-
file_getprop("/system/build.prop", "ro.build.fingerprint") == "ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A05_20130322-094434:userdebug/test-keys");
-
assert(getprop("ro.product.device") == "sp8825ea" ||
-
getprop("ro.build.product") == "sp8825ea");
-
ui_print("Verifying current system...");
-
ui_print("pre-build:ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A04_20130322-094434:userdebug/test-keys");
-
ui_print("post-build:ETON/sp8825eabase/sp8825ea:4.0.3/IML74K/D525A-V1_A05_20130322-094434:userdebug/test-keys");
-
show_progress(0.100000, 0);
-
assert(apply_disk_space(8928364));
-
assert(apply_patch_check("/system/build.prop", "098db2c72e97c3e84a1b14b782befe9446e44f49", "64a46c872b8766b20feeaf2a4802ecae53a75e97"));
-
set_progress(0.000285);
-
assert(apply_patch_check("/system/lib/modules/blcr.ko", "78dbaacbe60f9106d160dbe5cbd3778441c3b999", "7eb55dbf8b929c8296a51f62d2c0cfb56a8a7f32"));
-
set_progress(0.014533);
-
assert(apply_patch_check("/system/lib/modules/blcr_imports.ko", "9a42096b4b1da8345e9f87da68a492740de50a61", "288ff8c8aafbd6c00b467ed89c4f2580937f77a8"));
-
set_progress(0.016106);
-
assert(apply_patch_check("EMMC:/dev/block/mmcblk0p2:5509716:78bbdc657109f192eb606371f12a30b881fbeda9:5512524:b1d56633f28bbed2a7530daeeee85b6710173ee0"));
-
set_progress(0.623307);
-
assert(apply_patch_check("EMMC:/dev/block/mmcblk0p3:3014656:406b98e17bae26628b46003189122b74b64c5992:3014656:c0e9e938c6a42fe42531d92cfb8dc0301be99885"));
-
set_progress(0.955538);
-
assert(apply_patch_check("EMMC:/dev/block/mmcblk0p1:344064:3e46eecb33050e461a80d38787e9e773ab09ff04:339968:c2e4ed3c6d9554fd1415a473f7097ec1a5d0845f"));
-
set_progress(0.993456);
-
assert(apply_patch_space(129286));
-
-
# ---- start making changes here ----
-
-
ui_print("Removing unneeded files...");
-
delete("/system/app/CalllogManager.apk", "/system/lib/libext2_blkid.so",
-
"/system/lib/libext2_com_err.so", "/system/lib/libext2_e2p.so",
-
"/system/lib/libext2_profile.so", "/system/lib/libext2_uuid.so",
-
"/system/lib/libext2fs.so",
-
"/system/recovery.img");
-
show_progress(0.800000, 0);
-
ui_print("Patching system files...");
-
apply_patch("/system/lib/modules/blcr.ko", "-",
-
78dbaacbe60f9106d160dbe5cbd3778441c3b999, 129286,
-
7eb55dbf8b929c8296a51f62d2c0cfb56a8a7f32, package_extract_file("patch/system/lib/modules/blcr.ko.p"));
-
set_progress(0.014247);
-
apply_patch("/system/lib/modules/blcr_imports.ko", "-",
-
9a42096b4b1da8345e9f87da68a492740de50a61, 14273,
-
288ff8c8aafbd6c00b467ed89c4f2580937f77a8, package_extract_file("patch/system/lib/modules/blcr_imports.ko.p"));
-
set_progress(0.015820);
-
delete("/system/recovery-from-boot.p",
-
"/system/etc/install-recovery.sh");
-
ui_print("Patching modem ...");
-
apply_patch("EMMC:/dev/block/mmcblk0p2:5509716:78bbdc657109f192eb606371f12a30b881fbeda9:5512524:b1d56633f28bbed2a7530daeeee85b6710173ee0",
-
"/sdcard/modem.bin", b1d56633f28bbed2a7530daeeee85b6710173ee0,
-
5512524,
-
78bbdc657109f192eb606371f12a30b881fbeda9, package_extract_file("patch/modem.bin.p"));
-
set_progress(0.623294);
代码段4 update-script内容
尽管并不确定这些代码的含义,但一眼望去我们仍能够分辨出其中包含的一些命令,诸如:mount,assert,ui_print,package_extract_file,apply_patch等。OK,继续往下读try_update_binary函数,读到一段while循环(86-116),心中一喜,这应该就是对应于各种命令的执行代码吧。遗憾的是,仔细读来,发现并非如此。while循环中只针对于progress、set_progress、wipe_cache、ui_print四种命令进行了处理,而package_extract_file、apply_patch等关键命令却了无踪影。显然,升级代码并非我想象的这么简单,在这个try_update_binary之后,一定还隐藏着其他东东。于是在./bootable目录下尝试搜索了apply_patch关键词,终于在./bootable/recovery/updater/install.c中找到了答案。
先看./bootable/recovery/updater/install.c中的一段代码:
-
void RegisterInstallFunctions() {
-
RegisterFunction("mount", MountFn);
-
RegisterFunction("is_mounted", IsMountedFn);
-
RegisterFunction("unmount", UnmountFn);
-
RegisterFunction("format", FormatFn);
-
RegisterFunction("show_progress", ShowProgressFn);
-
RegisterFunction("set_progress", SetProgressFn);
-
RegisterFunction("delete", DeleteFn);
-
RegisterFunction("delete_recursive", DeleteFn);
-
RegisterFunction("package_extract_dir", PackageExtractDirFn);
-
RegisterFunction("package_extract_file", PackageExtractFileFn);
-
RegisterFunction("retouch_binaries", RetouchBinariesFn);
-
RegisterFunction("undo_retouch_binaries", UndoRetouchBinariesFn);
-
RegisterFunction("symlink", SymlinkFn);
-
RegisterFunction("set_perm", SetPermFn);
-
RegisterFunction("set_perm_recursive", SetPermFn);
-
-
RegisterFunction("getprop", GetPropFn);
-
RegisterFunction("file_getprop", FileGetPropFn);
-
RegisterFunction("write_raw_image", WriteRawImageFn);
-
-
RegisterFunction("apply_patch", ApplyPatchFn);
-
RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
-
RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
-
-
RegisterFunction("read_file", ReadFileFn);
-
RegisterFunction("sha1_check", Sha1CheckFn);
-
-
RegisterFunction("wipe_cache", WipeCacheFn);
-
-
RegisterFunction("ui_print", UIPrintFn);
-
-
RegisterFunction("run_program", RunProgramFn);
-
}
代码段5 updater-script与执行函数的映射
显而易见,对应于updater-script中的命令操作全都定义在./bootable/recovery/updater/install.c。系统升级时,由try_update_binary中创建的子进程执行这些操作,而try_
update_binary中的while循环只是负责更新升级进度条而已。
结合脚本与实现代码,可以对操作命令有较为清楚的认识。以脚本appy_patch为例,可以将其以函数的形式写为apply_patch(src_file, target_file, target_sha1, target_size, sha1_1, patch_1),它实现的功能为:将源文件src_file利用差分文件patch_1进行升级,并将结果文件写入到target_file中。target_size表示目录文件的大小,target_sha和sha1_1分别为相应的校验数据。
其他命令及对应的代码不再详述。
3.函数finish_recovery()
-
static void
-
finish_recovery(const char *send_intent) {
-
-
if (send_intent != NULL) {
-
FILE *fp = fopen_path(INTENT_FILE, "w");
-
if (fp == NULL) {
-
LOGE("Can't open %s\n", INTENT_FILE);
-
} else {
-
fputs(send_intent, fp);
-
check_and_fclose(fp, INTENT_FILE);
-
}
-
}
-
-
-
copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
-
copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
-
copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
-
chmod(LOG_FILE, 0600);
-
chown(LOG_FILE, 1000, 1000);
-
chmod(LAST_LOG_FILE, 0640);
-
chmod(LAST_INSTALL_FILE, 0644);
-
-
-
struct bootloader_message boot;
-
memset(&boot, 0, sizeof(boot));
-
set_bootloader_message(&boot);
-
-
-
if (ensure_path_mounted(COMMAND_FILE) != 0 ||
-
(unlink(COMMAND_FILE) && errno != ENOENT)) {
-
LOGW("Can't unlink %s\n", COMMAND_FILE);
-
}
-
-
ensure_path_unmounted(CACHE_ROOT);
-
sync();
-
}
代码段6 finish_recovery函数
此函数完成的功能包括:
代码04-12:将send_intent写入文件并传递给main system;
代码14-21:将日志信息从TEMPORARY_LOG_FILE复制到LOG_FILE、LAST_LOG_FILE等文件中(这些文件存放在/cache目录);
代码23-26:清空BCB;
代码28-32:删除/cache/recovery/command文件。
显然,当系统升级成功后,执行finish_recovery函数重启,系统将不再进入recovery模式。