Recovery 升级流程

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

  1. public static void verifyPackage(File packageFile,  
  2.                                  ProgressListener listener,  
  3.                                  File deviceCertsZipFile)  
  4.     throws IOException, GeneralSecurityException  

 

    签名验证函数,实现过程就不贴出来了,参数,

        packageFile--升级文件

        listener--进度监督器

        deviceCertsZipFile--签名文件,如果为空,则使用系统默认的签名

    只有当签名验证正确才返回,否则将抛出异常。

    在Recovery模式下进行升级时候也是会进行签名验证的,如果这里先不进行验证也不会有什么问题。但是我们建议在重启前,先验证,以便及早发现问题。

    如果签名验证没有问题,就执行installPackage开始升级。

2、installPackage开始升级

    如果签名验证没有问题,就进行重启升级,

[java] view plain copy

  1. public static void installPackage(Context context, File packageFile)  
  2.     throws IOException {  
  3.     String filename = packageFile.getCanonicalPath();  
  4.     Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");  
  5.   
  6.     final String filenameArg = "--update_package=" + filename;  
  7.     final String localeArg = "--locale=" + Locale.getDefault().toString();  
  8.     bootCommand(context, filenameArg, localeArg);  
  9. }  

    这里定义了两个参数,我们接着看,

 

 

[java] view plain copy

  1. private static void bootCommand(Context context, String... args) throws IOException {  
  2.     RECOVERY_DIR.mkdirs();  // In case we need it  
  3.     COMMAND_FILE.delete();  // In case it's not writable  
  4.     LOG_FILE.delete();  
  5.   
  6.     FileWriter command = new FileWriter(COMMAND_FILE);  
  7.     try {  
  8.         for (String arg : args) {  
  9.             if (!TextUtils.isEmpty(arg)) {  
  10.                 command.write(arg);  
  11.                 command.write("\n");  
  12.             }  
  13.         }  
  14.     } finally {  
  15.         command.close();  
  16.     }  
  17.   
  18.     // Having written the command file, go ahead and reboot  
  19.     PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);  
  20.     pm.reboot(PowerManager.REBOOT_RECOVERY);  
  21.   
  22.     throw new IOException("Reboot failed (no permissions?)");  
  23. }  

 

    创建目录/cache/recovery/,command文件保存在该目录下;如果存在command文件,将其删除;然后将上面一步生成的两个参数写入到command文件。

    最后重启设备,重启过程就不再详述了。

三、系统重启进入Recovery模式

    系统重启时会判断/cache/recovery目录下是否有command文件,如果存在就进入recovery模式,否则就正常启动。

    进入到Recovery模式下,将执行recovery.cpp的main函数,下面贴出关键代码片段,

[java] view plain copy

  1. int arg;  
  2. while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {  
  3.     switch (arg) {  
  4.     case 's': send_intent = optarg; break;  
  5.     case 'u': update_package = optarg; break;  
  6.     case 'w': wipe_data = wipe_cache = 1; break;  
  7.     case 'c': wipe_cache = 1; break;  
  8.     case 't': show_text = 1; break;  
  9.     case 'x': just_exit = true; break;  
  10.     case 'l': locale = optarg; break;  
  11.     case 'g': {  
  12.         if (stage == NULL || *stage == '\0') {  
  13.             char buffer[20] = "1/";  
  14.             strncat(buffer, optarg, sizeof(buffer)-3);  
  15.             stage = strdup(buffer);  
  16.         }  
  17.         break;  
  18.     }  
  19.     case 'p': shutdown_after = true; break;  
  20.     case 'r': reason = optarg; break;  
  21.     case '?':  
  22.         LOGE("Invalid command argument\n");  
  23.         continue;  
  24.     }  
  25. }  

    这是一个While循环,用来读取recoverycommand参数,OPTIONS的不同选项定义如下,
[java] view plain copy

  1. static const struct option OPTIONS[] = {  
  2.   { "send_intent", required_argument, NULL, 's' },  
  3.   { "update_package", required_argument, NULL, 'u' },  
  4.   { "wipe_data", no_argument, NULL, 'w' },  
  5.   { "wipe_cache", no_argument, NULL, 'c' },  
  6.   { "show_text", no_argument, NULL, 't' },  
  7.   { "just_exit", no_argument, NULL, 'x' },  
  8.   { "locale", required_argument, NULL, 'l' },  
  9.   { "stages", required_argument, NULL, 'g' },  
  10.   { "shutdown_after", no_argument, NULL, 'p' },  
  11.   { "reason", required_argument, NULL, 'r' },  
  12.   { NULL, 0, NULL, 0 },  
  13. };  

    显然,根据第二步写入的命令文件内容,将为update_package 赋值。

    接着看,

[java] view plain copy

  1. if (update_package) {  
  2.     // For backwards compatibility on the cache partition only, if  
  3.     // we're given an old 'root' path "CACHE:foo", change it to  
  4.     // "/cache/foo".  
  5.     if (strncmp(update_package, "CACHE:", 6) == 0) {  
  6.         int len = strlen(update_package) + 10;  
  7.         char* modified_path = (char*)malloc(len);  
  8.         strlcpy(modified_path, "/cache/", len);  
  9.         strlcat(modified_path, update_package+6, len);  
  10.         printf("(replacing path \"%s\" with \"%s\")\n",  
  11.                update_package, modified_path);  
  12.         update_package = modified_path;  
  13.     }  
  14. }  

    兼容性处理。
[java] view plain copy

  1. int status = INSTALL_SUCCESS;  
  2.   
  3. if (update_package != NULL) {  
  4.     status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);  
  5.     if (status == INSTALL_SUCCESS && wipe_cache) {  
  6.         if (erase_volume("/cache")) {  
  7.             LOGE("Cache wipe (requested by package) failed.");  
  8.         }  
  9.     }  
  10.     if (status != INSTALL_SUCCESS) {  
  11.         ui->Print("Installation aborted.\n");  
  12.   
  13.         // If this is an eng or userdebug build, then automatically  
  14.         // turn the text display on if the script fails so the error  
  15.         // message is visible.  
  16.         char buffer[PROPERTY_VALUE_MAX+1];  
  17.         property_get("ro.build.fingerprint", buffer, "");  
  18.         if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) {  
  19.             ui->ShowText(true);  
  20.         }  
  21.     }  
  22. } else if (wipe_data) {  
  23.     if (device->WipeData()) status = INSTALL_ERROR;  
  24.     if (erase_volume("/data")) status = INSTALL_ERROR;  
  25.     if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;  
  26.     if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR;  
  27.     if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n");  
  28. } else if (wipe_cache) {  
  29.     if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;  
  30.     if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n");  
  31. } else if (!just_exit) {  
  32.     status = INSTALL_NONE;  // No command specified  
  33.     ui->SetBackground(RecoveryUI::NO_COMMAND);  
  34. }  

    update_package不为空,执行install_package方法。

    我们也可以看到擦除数据、缓存的实现也是在这个里执行的,这里就不展开了。

 

四、在install.cpp进行升级操作

    具体的升级过程都是在install.cpp中执行的,先看install_package方法,

 

 

[java] view plain copy

  1. int  
  2. install_package(const char* path, int* wipe_cache, const char* install_file,  
  3.                 bool needs_mount)  
  4. {  
  5.     FILE* install_log = fopen_path(install_file, "w");  
  6.     if (install_log) {  
  7.         fputs(path, install_log);  
  8.         fputc('\n', install_log);  
  9.     } else {  
  10.         LOGE("failed to open last_install: %s\n", strerror(errno));  
  11.     }  
  12.     int result;  
  13.     if (setup_install_mounts() != 0) {  
  14.         LOGE("failed to set up expected mounts for install; aborting\n");  
  15.         result = INSTALL_ERROR;  
  16.     } else {  
  17.         result = really_install_package(path, wipe_cache, needs_mount);  
  18.     }  
  19.     if (install_log) {  
  20.         fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);  
  21.         fputc('\n', install_log);  
  22.         fclose(install_log);  
  23.     }  
  24.     return result;  
  25. }  

    这个方法中首先创建了log文件,升级过程包括出错的信息都会写到这个文件中,便于后续的分析工作。继续跟进,really_install_package,
[java] view plain copy

  1. static int  
  2. really_install_package(const char *path, int* wipe_cache, bool needs_mount)  
  3. {  
  4.     ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);  
  5.     ui->Print("Finding update package...\n");  
  6.     // Give verification half the progress bar...  
  7.     ui->SetProgressType(RecoveryUI::DETERMINATE);  
  8.     ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);  
  9.     LOGI("Update location: %s\n", path);  
  10.   
  11.     // Map the update package into memory.  
  12.     ui->Print("Opening update package...\n");  
  13.   
  14.     if (path && needs_mount) {  
  15.         if (path[0] == '@') {  
  16.             ensure_path_mounted(path+1);  
  17.         } else {  
  18.             ensure_path_mounted(path);  
  19.         }  
  20.     }  
  21.   
  22.     MemMapping map;  
  23.     if (sysMapFile(path, &map) != 0) {  
  24.         LOGE("failed to map file\n");  
  25.         return INSTALL_CORRUPT;  
  26.     }  
  27.   
  28.     // 装入签名文件  
  29.     int numKeys;  
  30.     Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);  
  31.     if (loadedKeys == NULL) {  
  32.         LOGE("Failed to load keys\n");  
  33.         return INSTALL_CORRUPT;  
  34.     }  
  35.     LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);  
  36.   
  37.     ui->Print("Verifying update package...\n");  
  38.   
  39.     // 验证签名  
  40.     int err;  
  41.     err = verify_file(map.addr, map.length, loadedKeys, numKeys);  
  42.     free(loadedKeys);  
  43.     LOGI("verify_file returned %d\n", err);  
  44.     // 签名失败的处理  
  45.     if (err != VERIFY_SUCCESS) {  
  46.         LOGE("signature verification failed\n");  
  47.         sysReleaseMap(&map);  
  48.         return INSTALL_CORRUPT;  
  49.     }  
  50.   
  51.     /* Try to open the package. 
  52.      */  
  53.     // 打开升级包  
  54.     ZipArchive zip;  
  55.     err = mzOpenZipArchive(map.addr, map.length, &zip);  
  56.     if (err != 0) {  
  57.         LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");  
  58.         sysReleaseMap(&map);  
  59.         return INSTALL_CORRUPT;  
  60.     }  
  61.   
  62.     /* Verify and install the contents of the package. 
  63.      */  
  64.     ui->Print("Installing update...\n");  
  65.     ui->SetEnableReboot(false);  
  66.     // 执行升级脚本文件,开始升级  
  67.     int result = try_update_binary(path, &zip, wipe_cache);  
  68.     ui->SetEnableReboot(true);  
  69.     ui->Print("\n");  
  70.   
  71.     sysReleaseMap(&map);  
  72.   
  73.     return result;  
  74. }  

 

    该方法主要做了三件事

1、验证签名

[java] view plain copy

  1. int numKeys;  
  2. Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);  
  3. if (loadedKeys == NULL) {  
  4.     LOGE("Failed to load keys\n");  
  5.     return INSTALL_CORRUPT;  
  6. }  

    装载签名文件,如果为空 ,终止升级;
[java] view plain copy

  1. int err;  
  2. err = verify_file(map.addr, map.length, loadedKeys, numKeys);  
  3. free(loadedKeys);  
  4. LOGI("verify_file returned %d\n", err);  
  5. // 签名失败的处理  
  6. if (err != VERIFY_SUCCESS) {  
  7.     LOGE("signature verification failed\n");  
  8.     sysReleaseMap(&map);  
  9.     return INSTALL_CORRUPT;  
  10. }  

    调用verify_file进行签名验证,这个方法定义在verifier.cpp文件中,此处不展开,如果验证失败立即终止升级。

 

2、读取升级包信息

[java] view plain copy

  1. ZipArchive zip;  
  2. err = mzOpenZipArchive(map.addr, map.length, &zip);  
  3. if (err != 0) {  
  4.     LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");  
  5.     sysReleaseMap(&map);  
  6.     return INSTALL_CORRUPT;  
  7. }  

    执行mzOpenZipArchive方法,打开升级包并扫描,将包的内容拷贝到变量zip中,该变量将作为参数用来执行升级脚本。

3、执行升级脚本文件,开始升级[java] view plain copy

  1. int result = try_update_binary(path, &zip, wipe_cache);  

    try_update_binary方法用来处理升级包,执行制作升级包中的脚本文件update_binary,进行系统更新。

五、try_update_binary执行升级脚本

 

 

[java] view plain copy

  1. // If the package contains an update binary, extract it and run it.  
  2. static int  
  3. try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {  
  4.     // 检查update-binary是否存在  
  5.     const ZipEntry* binary_entry =  
  6.             mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);  
  7.     if (binary_entry == NULL) {  
  8.         mzCloseZipArchive(zip);  
  9.         return INSTALL_CORRUPT;  
  10.     }  
  11.   
  12.     const char* binary = "/tmp/update_binary";  
  13.     unlink(binary);  
  14.     int fd = creat(binary, 0755);  
  15.     if (fd < 0) {  
  16.         mzCloseZipArchive(zip);  
  17.         LOGE("Can't make %s\n", binary);  
  18.         return INSTALL_ERROR;  
  19.     }  
  20.     // update-binary拷贝到"/tmp/update_binary"  
  21.     bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);  
  22.     close(fd);  
  23.     mzCloseZipArchive(zip);  
  24.   
  25.     if (!ok) {  
  26.         LOGE("Can't copy %s\n", ASSUMED_UPDATE_BINARY_NAME);  
  27.         return INSTALL_ERROR;  
  28.     }  
  29.   
  30.     // 创建管道,用于下面的子进程和父进程之间的通信  
  31.     int pipefd[2];  
  32.     pipe(pipefd);  
  33.   
  34.     // When executing the update binary contained in the package, the  
  35.     // arguments passed are:  
  36.     //  
  37.     //   - the version number for this interface  
  38.     //  
  39.     //   - an fd to which the program can write in order to update the  
  40.     //     progress bar.  The program can write single-line commands:  
  41.     //  
  42.     //        progress <frac> <secs>  
  43.     //            fill up the next <frac> part of of the progress bar  
  44.     //            over <secs> seconds.  If <secs> is zero, use  
  45.     //            set_progress commands to manually control the  
  46.     //            progress of this segment of the bar  
  47.     //  
  48.     //        set_progress <frac>  
  49.     //            <frac> should be between 0.0 and 1.0; sets the  
  50.     //            progress bar within the segment defined by the most  
  51.     //            recent progress command.  
  52.     //  
  53.     //        firmware <"hboot"|"radio"> <filename>  
  54.     //            arrange to install the contents of <filename> in the  
  55.     //            given partition on reboot.  
  56.     //  
  57.     //            (API v2: <filename> may start with "PACKAGE:" to  
  58.     //            indicate taking a file from the OTA package.)  
  59.     //  
  60.     //            (API v3: this command no longer exists.)  
  61.     //  
  62.     //        ui_print <string>  
  63.     //            display <string> on the screen.  
  64.     //  
  65.     //   - the name of the package zip file.  
  66.     //  
  67.   
  68.     const char** args = (const char**)malloc(sizeof(char*) * 5);  
  69.     args[0] = binary;  
  70.     args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk  
  71.     char* temp = (char*)malloc(10);  
  72.     sprintf(temp, "%d", pipefd[1]);  
  73.     args[2] = temp;  
  74.     args[3] = (char*)path;  
  75.     args[4] = NULL;  
  76.   
  77.     // 创建子进程。负责执行binary脚本  
  78.     pid_t pid = fork();  
  79.     if (pid == 0) {  
  80.         umask(022);  
  81.         close(pipefd[0]);  
  82.         execv(binary, (char* const*)args);// 执行binary脚本  
  83.         fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));  
  84.         _exit(-1);  
  85.     }  
  86.     close(pipefd[1]);  
  87.   
  88.     *wipe_cache = 0;  
  89.   
  90.     // 父进程负责接受子进程发送的命令去更新ui显示  
  91.     char buffer[1024];  
  92.     FILE* from_child = fdopen(pipefd[0], "r");  
  93.     while (fgets(buffer, sizeof(buffer), from_child) != NULL) {  
  94.         char* command = strtok(buffer, " \n");  
  95.         if (command == NULL) {  
  96.             continue;  
  97.         } else if (strcmp(command, "progress") == 0) {  
  98.             char* fraction_s = strtok(NULL, " \n");  
  99.             char* seconds_s = strtok(NULL, " \n");  
  100.   
  101.             float fraction = strtof(fraction_s, NULL);  
  102.             int seconds = strtol(seconds_s, NULL, 10);  
  103.   
  104.             ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);  
  105.         } else if (strcmp(command, "set_progress") == 0) {  
  106.             char* fraction_s = strtok(NULL, " \n");  
  107.             float fraction = strtof(fraction_s, NULL);  
  108.             ui->SetProgress(fraction);  
  109.         } else if (strcmp(command, "ui_print") == 0) {  
  110.             char* str = strtok(NULL, "\n");  
  111.             if (str) {  
  112.                 ui->Print("%s", str);  
  113.             } else {  
  114.                 ui->Print("\n");  
  115.             }  
  116.             fflush(stdout);  
  117.         } else if (strcmp(command, "wipe_cache") == 0) {  
  118.             *wipe_cache = 1;  
  119.         } else if (strcmp(command, "clear_display") == 0) {  
  120.             ui->SetBackground(RecoveryUI::NONE);  
  121.         } else if (strcmp(command, "enable_reboot") == 0) {  
  122.             // packages can explicitly request that they want the user  
  123.             // to be able to reboot during installation (useful for  
  124.             // debugging packages that don't exit).  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值