Android8.0.0-r4的OTA升级流程
原网址:https://blog.csdn.net/dingfengnupt88/article/details/52875228
Android系统进行升级的时候,有两种途径,一种是通过接口传递升级包路径自动升级(Android系统SD卡升级),升级完之后系统自动重启;另一种是手动进入recovery模式下,选择升级包进行升级,升级完成之后停留在recovery界面,需要手动选择重启。前者多用于手机厂商的客户端在线升级,后者多用于开发和测试人员。但不管哪种,原理都是一样的,都要在recovery模式下进行升级。
1、获取升级包,可以从服务端下载,也可以直接拷贝到SD卡中
2、获取升级包路径,验证签名,通过installPackage接口升级
3、系统重启进入Recovery模式
4、在install.cpp进行升级操作
5、try_update_binary执行升级脚本
6、finish_recovery,重启
一、获取升级包,可以从服务端下载,也可以直接拷贝到SD卡中
假设SD卡中已有升级包update.zip
二、获取升级包路径,验证签名,通过installPackage接口升级
1、调用RecoverySystem类提供的verifyPackage方法进行签名验证
[java] view plain copy
- public static void verifyPackage(File packageFile,
- ProgressListener listener,
- File deviceCertsZipFile)
- throws IOException, GeneralSecurityException
签名验证函数,实现过程就不贴出来了,参数,
packageFile--升级文件
listener--进度监督器
deviceCertsZipFile--签名文件,如果为空,则使用系统默认的签名
只有当签名验证正确才返回,否则将抛出异常。
在Recovery模式下进行升级时候也是会进行签名验证的,如果这里先不进行验证也不会有什么问题。但是我们建议在重启前,先验证,以便及早发现问题。
如果签名验证没有问题,就执行installPackage开始升级。
2、installPackage开始升级
如果签名验证没有问题,就进行重启升级,
[java] view plain copy
- public static void installPackage(Context context, File packageFile)
- throws IOException {
- String filename = packageFile.getCanonicalPath();
- Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
- final String filenameArg = "--update_package=" + filename;
- final String localeArg = "--locale=" + Locale.getDefault().toString();
- bootCommand(context, filenameArg, localeArg);
- }
这里定义了两个参数,我们接着看,
[java] view plain copy
- private static void bootCommand(Context context, String... args) throws IOException {
- RECOVERY_DIR.mkdirs(); // In case we need it
- COMMAND_FILE.delete(); // In case it's not writable
- LOG_FILE.delete();
- FileWriter command = new FileWriter(COMMAND_FILE);
- try {
- for (String arg : args) {
- if (!TextUtils.isEmpty(arg)) {
- command.write(arg);
- command.write("\n");
- }
- }
- } finally {
- command.close();
- }
- // Having written the command file, go ahead and reboot
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- pm.reboot(PowerManager.REBOOT_RECOVERY);
- throw new IOException("Reboot failed (no permissions?)");
- }
创建目录/cache/recovery/,command文件保存在该目录下;如果存在command文件,将其删除;然后将上面一步生成的两个参数写入到command文件。
最后重启设备,重启过程就不再详述了。
三、系统重启进入Recovery模式
系统重启时会判断/cache/recovery目录下是否有command文件,如果存在就进入recovery模式,否则就正常启动。
进入到Recovery模式下,将执行recovery.cpp的main函数,下面贴出关键代码片段,
[java] view plain copy
- int arg;
- while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
- switch (arg) {
- case 's': send_intent = optarg; break;
- case 'u': update_package = optarg; break;
- case 'w': wipe_data = wipe_cache = 1; break;
- case 'c': wipe_cache = 1; break;
- case 't': show_text = 1; break;
- case 'x': just_exit = true; break;
- case 'l': locale = optarg; break;
- case 'g': {
- if (stage == NULL || *stage == '\0') {
- char buffer[20] = "1/";
- strncat(buffer, optarg, sizeof(buffer)-3);
- stage = strdup(buffer);
- }
- break;
- }
- case 'p': shutdown_after = true; break;
- case 'r': reason = optarg; break;
- case '?':
- LOGE("Invalid command argument\n");
- continue;
- }
- }
这是一个While循环,用来读取recovery的command参数,OPTIONS的不同选项定义如下,
[java] view plain copy
- static const struct option OPTIONS[] = {
- { "send_intent", required_argument, NULL, 's' },
- { "update_package", required_argument, NULL, 'u' },
- { "wipe_data", no_argument, NULL, 'w' },
- { "wipe_cache", no_argument, NULL, 'c' },
- { "show_text", no_argument, NULL, 't' },
- { "just_exit", no_argument, NULL, 'x' },
- { "locale", required_argument, NULL, 'l' },
- { "stages", required_argument, NULL, 'g' },
- { "shutdown_after", no_argument, NULL, 'p' },
- { "reason", required_argument, NULL, 'r' },
- { NULL, 0, NULL, 0 },
- };
显然,根据第二步写入的命令文件内容,将为update_package 赋值。
接着看,
[java] view plain copy
- if (update_package) {
- // For backwards compatibility on the cache partition only, if
- // we're given an old 'root' path "CACHE:foo", change it to
- // "/cache/foo".
- if (strncmp(update_package, "CACHE:", 6) == 0) {
- int len = strlen(update_package) + 10;
- char* modified_path = (char*)malloc(len);
- strlcpy(modified_path, "/cache/", len);
- strlcat(modified_path, update_package+6, len);
- printf("(replacing path \"%s\" with \"%s\")\n",
- update_package, modified_path);
- update_package = modified_path;
- }
- }
兼容性处理。
[java] view plain copy
- int status = INSTALL_SUCCESS;
- if (update_package != NULL) {
- status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);
- if (status == INSTALL_SUCCESS && wipe_cache) {
- if (erase_volume("/cache")) {
- LOGE("Cache wipe (requested by package) failed.");
- }
- }
- if (status != INSTALL_SUCCESS) {
- ui->Print("Installation aborted.\n");
- // 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.
- char buffer[PROPERTY_VALUE_MAX+1];
- property_get("ro.build.fingerprint", buffer, "");
- if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) {
- ui->ShowText(true);
- }
- }
- } else if (wipe_data) {
- if (device->WipeData()) status = INSTALL_ERROR;
- if (erase_volume("/data")) status = INSTALL_ERROR;
- if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
- if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;
- if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");
- } else if (wipe_cache) {
- if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;
- if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");
- } else if (!just_exit) {
- status = INSTALL_NONE; // No command specified
- ui->SetBackground(RecoveryUI::NO_COMMAND);
- }
update_package不为空,执行install_package方法。
我们也可以看到擦除数据、缓存的实现也是在这个里执行的,这里就不展开了。
四、在install.cpp进行升级操作
具体的升级过程都是在install.cpp中执行的,先看install_package方法,
[java] view plain copy
- int
- install_package(const char* path, int* wipe_cache, const char* install_file,
- bool needs_mount)
- {
- 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;
- if (setup_install_mounts() != 0) {
- LOGE("failed to set up expected mounts for install; aborting\n");
- result = INSTALL_ERROR;
- } else {
- result = really_install_package(path, wipe_cache, needs_mount);
- }
- if (install_log) {
- fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
- fputc('\n', install_log);
- fclose(install_log);
- }
- return result;
- }
这个方法中首先创建了log文件,升级过程包括出错的信息都会写到这个文件中,便于后续的分析工作。继续跟进,really_install_package,
[java] view plain copy
- static int
- really_install_package(const char *path, int* wipe_cache, bool needs_mount)
- {
- 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);
- LOGI("Update location: %s\n", path);
- // Map the update package into memory.
- ui->Print("Opening update package...\n");
- if (path && needs_mount) {
- if (path[0] == '@') {
- ensure_path_mounted(path+1);
- } else {
- ensure_path_mounted(path);
- }
- }
- MemMapping map;
- if (sysMapFile(path, &map) != 0) {
- LOGE("failed to map file\n");
- return INSTALL_CORRUPT;
- }
- // 装入签名文件
- int numKeys;
- Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
- if (loadedKeys == NULL) {
- LOGE("Failed to load keys\n");
- return INSTALL_CORRUPT;
- }
- LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
- ui->Print("Verifying update package...\n");
- // 验证签名
- int err;
- err = verify_file(map.addr, map.length, loadedKeys, numKeys);
- free(loadedKeys);
- LOGI("verify_file returned %d\n", err);
- // 签名失败的处理
- if (err != VERIFY_SUCCESS) {
- LOGE("signature verification failed\n");
- sysReleaseMap(&map);
- return INSTALL_CORRUPT;
- }
- /* Try to open the package.
- */
- // 打开升级包
- ZipArchive zip;
- err = mzOpenZipArchive(map.addr, map.length, &zip);
- if (err != 0) {
- LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
- sysReleaseMap(&map);
- return INSTALL_CORRUPT;
- }
- /* Verify and install the contents of the package.
- */
- ui->Print("Installing update...\n");
- ui->SetEnableReboot(false);
- // 执行升级脚本文件,开始升级
- int result = try_update_binary(path, &zip, wipe_cache);
- ui->SetEnableReboot(true);
- ui->Print("\n");
- sysReleaseMap(&map);
- return result;
- }
该方法主要做了三件事
1、验证签名
[java] view plain copy
- int numKeys;
- Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);
- if (loadedKeys == NULL) {
- LOGE("Failed to load keys\n");
- return INSTALL_CORRUPT;
- }
装载签名文件,如果为空 ,终止升级;
[java] view plain copy
- int err;
- err = verify_file(map.addr, map.length, loadedKeys, numKeys);
- free(loadedKeys);
- LOGI("verify_file returned %d\n", err);
- // 签名失败的处理
- if (err != VERIFY_SUCCESS) {
- LOGE("signature verification failed\n");
- sysReleaseMap(&map);
- return INSTALL_CORRUPT;
- }
调用verify_file进行签名验证,这个方法定义在verifier.cpp文件中,此处不展开,如果验证失败立即终止升级。
2、读取升级包信息
[java] view plain copy
- ZipArchive zip;
- err = mzOpenZipArchive(map.addr, map.length, &zip);
- if (err != 0) {
- LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
- sysReleaseMap(&map);
- return INSTALL_CORRUPT;
- }
执行mzOpenZipArchive方法,打开升级包并扫描,将包的内容拷贝到变量zip中,该变量将作为参数用来执行升级脚本。
3、执行升级脚本文件,开始升级[java] view plain copy
- int result = try_update_binary(path, &zip, wipe_cache);
try_update_binary方法用来处理升级包,执行制作升级包中的脚本文件update_binary,进行系统更新。
五、try_update_binary执行升级脚本
[java] view plain copy
- // If the package contains an update binary, extract it and run it.
- static int
- try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
- // 检查update-binary是否存在
- const ZipEntry* binary_entry =
- mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
- if (binary_entry == NULL) {
- mzCloseZipArchive(zip);
- return INSTALL_CORRUPT;
- }
- const 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;
- }
- // update-binary拷贝到"/tmp/update_binary"
- 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);
- // 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.
- //
- // firmware <"hboot"|"radio"> <filename>
- // arrange to install the contents of <filename> in the
- // given partition on reboot.
- //
- // (API v2: <filename> may start with "PACKAGE:" to
- // indicate taking a file from the OTA package.)
- //
- // (API v3: this command no longer exists.)
- //
- // ui_print <string>
- // display <string> on the screen.
- //
- // - the name of the package zip file.
- //
- const char** args = (const char**)malloc(sizeof(char*) * 5);
- args[0] = binary;
- args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk
- char* temp = (char*)malloc(10);
- sprintf(temp, "%d", pipefd[1]);
- args[2] = temp;
- args[3] = (char*)path;
- args[4] = NULL;
- // 创建子进程。负责执行binary脚本
- pid_t pid = fork();
- if (pid == 0) {
- umask(022);
- close(pipefd[0]);
- execv(binary, (char* const*)args);// 执行binary脚本
- fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
- _exit(-1);
- }
- close(pipefd[1]);
- *wipe_cache = 0;
- // 父进程负责接受子进程发送的命令去更新ui显示
- 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->ShowProgress(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->SetProgress(fraction);
- } else if (strcmp(command, "ui_print") == 0) {
- char* str = strtok(NULL, "\n");
- if (str) {
- ui->Print("%s", str);
- } else {
- ui->Print("\n");
- }
- fflush(stdout);
- } else if (strcmp(command, "wipe_cache") == 0) {
- *wipe_cache = 1;
- } else if (strcmp(command, "clear_display") == 0) {
- ui->SetBackground(RecoveryUI::NONE);
- } else if (strcmp(command, "enable_reboot") == 0) {
- // packages can explicitly request that they want the user
- // to be able to reboot during installation (useful for
- // debugging packages that don't exit).