Android 8.0 OTA 分析——recovery

这里是OTA分析的第一部分,主要是recovery模块,后面会对脚本进行分析
如果有分析不到位的地方请指出,共同学习进步

一、首先来看下升级的入口

1.上层写入命令主动升级:

这是最常见的方式,负责升级的APK会向command中写入升级的信息,比如”–update_package”参数和升级包的路径等

[recovery.cpp]
if (update_package != NULL) {
    // 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 {
        status = install_package(update_package, &should_wipe_cache,
                                 TEMPORARY_INSTALL_FILE, true, 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 EIO_RETRY_COUNT
            // times before we abandon this OTA update.
            if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
                copy_logs();
                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);
            }
        }
    }
}

从上面的逻辑可以看到,首先要进行电量的检测,然后是启动原因的检查,最后调用install_package升级

2.从SD卡安装升级包

这是我们调试经常要操作的地方:
在recovery选择菜单上有这一项,在之前Android 8.0 recovery 流程分析中有说到在prompt_and_wait中会检测用户选择:

case Device::APPLY_SDCARD:
{
  bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD);
  if (adb) {
    status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE);
  } else {
    status = apply_from_sdcard(device, &should_wipe_cache);
  }

  if (status == INSTALL_SUCCESS && should_wipe_cache) {
    if (!wipe_cache(false, device)) {
      status = INSTALL_ERROR;
    }
  }

  if (status != INSTALL_SUCCESS) {
    ui->SetBackground(RecoveryUI::ERROR);
    ui->Print("Installation aborted.\n");
    copy_logs();
  } else if (!ui->IsTextVisible()) {
    return Device::NO_ACTION;  // reboot if logs aren't visible
  } else {
    ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card");
  }
}
break;

可以看到,调用了apply_from_sdcard方法,这个方法会执行挂载SD等准备操作,在项目前期的过程中可能无法挂载,需要驱动调试
最终也是调用install_package进行升级

3.使用adb方式更新

如果没有SD卡,又要本地调试,那么可以使用这是方法:
输入命令adb sideload XXX.zip就可以进行升级
这种方式比较特殊,以后会做补充分析,这次先略过,只需知道最终还是通过install_package进行升级即可

二、具体的升级过程分析

install_package是通过really_install_package进一步安装,详细看看:

[install.cpp]
static int
really_install_package(const char *path, bool* wipe_cache, bool needs_mount,
                       std::vector<std::string>& log_buffer, int retry_count, int* max_temperature)
{
    //更新屏幕的提示为正在更新  
    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中记录升级包的路径
    LOG(INFO) << "Update location: " << path;

    // Map the update package into memory.
    ui->Print("Opening update package...\n");
    //path是升级包的路径,needs_mount参数代表是否需要挂载,1是挂载
    if (path && needs_mount) {
        //确保升级包所在的目录是挂载的
        //这里能看到对加密和非加密有区分,路径名带@的是加密后的手机
        if (path[0] == '@') {
            ensure_path_mounted(path+1);
        } else {
            ensure_path_mounted(path);
        }
    }

    //通过mmap把升级包路径映射到内存中
    MemMapping map;
    if (sysMapFile(path, &map) != 0) {
        LOG(ERROR) << "failed to map file";
        return INSTALL_CORRUPT;
    }

    // Verify package.
    if (!verify_package(map.addr, map.length)) {
        log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
        sysReleaseMap(&map);
        return INSTALL_CORRUPT;
    }

    // Try to open the package.
    ZipArchiveHandle zip;
    //从内存中打开升级包
    int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip);
    if (err != 0) {
        LOG(ERROR) << "Can't open " << path << " : " << ErrorCodeString(err);
        log_buffer.push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));

        sysReleaseMap(&map);
        CloseArchive(zip);
        return INSTALL_CORRUPT;
    }

    // Additionally verify the compatibility of the package.
    //这里可以看到调用verify_package_compatibility函数来检查升级包的兼容性
    //从函数的注释中可以看到这个和Treble功能有关
    //应该是在升级包中有个compatibility.zip,有的话继续判断,没有就返回true
    //目前遇到的项目中都没有这个压缩包,所以先不看
    //有个疑问是,这个函数里会打出log,但是升级log中没有??
    if (!verify_package_compatibility(zip)) {
      log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
      sysReleaseMap(&map);
      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);
    //具体执行升级的地方
    int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
    ui->SetEnableReboot(true);
    ui->Print("\n");

    sysReleaseMap(&map);
    CloseArchive(zip);
    return result;
}

1.校验升级包

这里跟踪下verify_package,比较重要

[install.cpp]
bool verify_package(const unsigned char* package_data, size_t package_size) {
  std::vector loadedKeys;
  //从/res/keys文件中装载设备的签名文件
  //文件路径在out/target/product/msm8937_64/recovery/root/res/下
  //如果在recovery模式下连接adb就会看到手机中对应的目录下有keys文件
  if (!load_keys(PUBLIC_KEYS_FILE, loadedKeys)) {
    LOG(ERROR) << "Failed to load keys";
    return false;
  }
  LOG(INFO) << loadedKeys.size() << " key(s) loaded from " << PUBLIC_KEYS_FILE;

  // Verify package.
  ui->Print("Verifying update package...\n");
  auto t0 = std::chrono::system_clock::now();
  //这里用了bind这个函数模版,目的是在verify_file函数中调用显示进度条的方法
  //校验升级包的签名,判断是否被修改,一般在调试ota升级时会把这段代码进行屏蔽,使本地编译的升级包可以正常升级
  int err = verify_file(package_data, package_size, loadedKeys,
                        std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
  std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
  ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
  if (err != VERIFY_SUCCESS) {
    LOG(ERROR) << "Signature verification failed";
    LOG(ERROR) << "error: " << kZipVerificationFailure;
    return false;
  }
  return true;
}

2.执行升级

最终执行升级的地方是try_update_binary

[install.cpp]
static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache,
                             std::vector<std::string>& log_buffer, int retry_count,
                             int* max_temperature) {
  //这里是先判断升级包中/META-INF/com/android/metadata文件相关值并写入到last_install中,
  //注意,只有差分升级才会写入,全分不会
  read_source_target_build(zip, log_buffer);
  //创建管道,用于父进程和子进程之间通信
  //根据管道的基本知识可以知道,pipefd[0]只能读数据,pipefd[1]只能写数据
  int pipefd[2];
  pipe(pipefd);

  std::vector<std::string> args;
  //根据A/B升级是否开启选择对应的update_binary_command函数,这里看non A/B的情况
  int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args);
  if (ret) {
    close(pipefd[0]);
    close(pipefd[1]);
    return ret;
  }

  //下面将args(args就是update_binary_command中的cmd)中的参数转换成数组形式
  // 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();
  }
  //fork了一个进程
  pid_t pid = fork();

  if (pid == -1) {
    close(pipefd[0]);
    close(pipefd[1]);
    PLOG(ERROR) << "Failed to fork update binary";
    return INSTALL_ERROR;
  }

  if (pid == 0) {
    //当前进程为子进程
    umask(022);
    //子进程准备写入数据,所以需要关闭读的通道
    close(pipefd[0]);
    //调用脚本解释器执行升级脚本
    execv(chr_args[0], const_cast<char**>(chr_args));

    fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
    _exit(EXIT_FAILURE);
  }
  //父进程读取数据,需要关闭写的通道
  close(pipefd[1]);

  std::thread temperature_logger(log_max_temperature, max_temperature);

  *wipe_cache = false;
  bool retry_update = false;

  char buffer[1024];
  FILE* from_child = fdopen(pipefd[0], "r");
  //通过fgets方法从pipefd[0]中读取子进程传入的值给buffer
  //再对这些参数进行解析,执行相应的命令,比如设置进度条之类的
  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 
        //其它命令省略
        ……
  }
  //用完即关好习惯
  fclose(from_child);

  int status;
  //等待子进程执行完毕
  waitpid(pid, &status, 0);

  finish_log_temperature.notify_one();
  temperature_logger.join();

  if (retry_update) {
    return INSTALL_RETRY;
  }
  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
    LOG(ERROR) << "Error in " << path << " (Status " << WEXITSTATUS(status) << ")";
    return INSTALL_ERROR;
  }

  return INSTALL_SUCCESS;
}

这里有个比较重要的地方是read_source_target_build这里会读取/META-INF/com/android/metadata
差分包和全分包这个文件不一样,差分包如下:

metadata记录了几个重要数据:
ota-type(BLOCK或者FILE)
post-build(目标版本的fingerprint)
post-build-incremental,pre-build-incremental(这两个和差分升级有关,在`parse_build_number`中会用到)
post-timestamp(时间戳)
pre-build(基础版本的fingerprint)
pre-device(设备名)

另一个比较重要的知识点是pipe(管道)通信,用于父进程(recovery)和子进程(升级进程)
pipe的应用网上例程比较多,可以搜索动手敲一下,这里需要知道pipefd[0]只能读数据,pipefd[1]只能写数据 所以升级进程向pipefd[1]写数据,recovery从pipefd[0]读数据

上面一个重要的函数是update_binary_command在A/B和non A/B下有不同的重载函数,A/B下的函数做了一些升级前的准备工作

[install.cpp]
int update_binary_command(const std::string& path, ZipArchiveHandle zip, 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;
  //在升级包中找到脚本解释器update-binary
  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
    LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
    return INSTALL_CORRUPT;
  }

  const char* binary = "/tmp/update_binary";
  unlink(binary);
  //创建/tmp/update_binary文件夹,并赋予对应权限
  int fd = creat(binary, 0755);
  if (fd == -1) {
    PLOG(ERROR) << "Failed to create " << binary;
    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实际上是即将传给子进程执行的命令
  *cmd = {
    binary,//脚本解释器
    EXPAND(RECOVERY_API_VERSION),  // defined in Android.mk,这里是3
    std::to_string(status_fd),  //status_fd是管道的写入端,应该是让子进程写数据的
    path,
  };
  if (retry_count > 0) {
    cmd->push_back("retry");
  }
  return 0;
}

从上面可以看到,具体命令的执行是由脚本解释器完成的
脚本解释器在升级包的/META-INF/com/google/android/update-binary,代码位于bootable/recovery/updater/可以单编这个模块,生成updater可执行文件,再重命名push到升级包中

可能有人会问,命令都由update-binary完成了,为什么在try_update_binary中仍然有对命令的处理逻辑,这是因为update-binary只能处理一些简单的命令,复杂的命令无法完成,比如在屏幕上打印log的ui_print等,这就需要recovery进程来处理

3.升级后的扫尾工作

待续……

三、总结

如果嫌update-binary提供的命令不太好用,可以自己添加额外的命令,在bootable/recovery/updater/install.cpp添加额外命令,并且在RegisterInstallFunctions中注册一下即可

OTA模块基本上没有什么问题,更多的工作是如何利用升级达到我们需要实现的功能,以及给其它模块擦屁股:)

你可能感兴趣的:(OTA,相关,Android,源码分析)