Detailed OTA upgrade (3)

A gentleman who knows his husband is not pure enough to be beautiful, 

So reciting the number to run through it,

Think through it,

Treat it for its people,

Eliminate the victim to maintain it;

                                               From Xunzi's "Encouraging Learning"

 

The following is the source code analysis of Android 10 recovery

The source of the code is: https://www.androidos.net.cn/android/10.0.0_r6/xref

The previous two chapters mainly talk about the basics of OTA, detailed explanation of OTA upgrade (2) and  detailed explanation of OTA upgrade (1) . This section mainly talks about how to use the zip upgrade package to upgrade recovery.

First, let's talk about the calling process of the upgrade function from the file level, as follows:

recovery-main.cpp is the main entrance of the upgrade,

recovery.cpp is the process of starting recovery upgrade

install/install.cpp is the processing flow for performing the upgrade (call updater)

updater/updater.cpp is the core process for completing the upgrade.

The Android-recovery upgrade code path is: bootable/recovery/

The main entry code is: recovery-main.cpp

1. Preparation for log related work

  // 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 constexpr const char filter[] = "recovery/";
  // Do we need to rotate?
  bool do_rotate = false;

  __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate);
  // Take action to refresh pmsg contents
  __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate);

  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(Paths::Get().temporary_log_file().c_str());

2. load_volume_table(); Load system partition information, pay attention to this and understand the mount partition

.mount_point = "/tmp", .fs_type = "ramdisk", .blk_device = "ramdisk", .length = 0 

mount_point - mount point

fs_type --Partition type

blk_device - device block name

length - partition size

3. Mount the /cache partition, our upgrade commands are placed under this partition

  has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;

4. Obtain the upgraded parameters and write the BCB block information. In fact, two actions have been completed here

std::vector<std::string> args = get_args(argc, argv);

if (!update_bootloader_message(options, &err)) {
    LOG(ERROR) << "Failed to set BCB message: " << err;
  }

a. Read the misc partition partition and write the recovery mode upgrade mark to the misc partition. The purpose of this is to continue the upgrade after the power is off. After the power is lost during the upgrade, if the next time the system is restarted, the bootloader will read this Mark and re-enter the update_bootloader_message function in recovery mode to complete this function.

b. Read the upgrade parameters from /cache/recovery/command. When the recovery startup process does not include parameters, the command file interface actually has a very detailed explanation

 * 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
 *   --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).
 *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
 *   --just_exit - do nothing; exit and reboot

5. Load recovery_ui_ext.so to complete the display of information on the screen during the upgrade, upgrade progress, upgrade results, etc. Not much to say here.

  static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
  // Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have
  // handed out pointers to code or static [or thread-local] data and doesn't collect them all back
  // in on dlclose).
  void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);

  using MakeDeviceType = decltype(&make_device);
  MakeDeviceType make_device_func = nullptr;
  if (librecovery_ui_ext == nullptr) {
    printf("Failed to dlopen %s: %s\n", kDefaultLibRecoveryUIExt, dlerror());
  } else {
    reinterpret_cast<void*&>(make_device_func) = dlsym(librecovery_ui_ext, "make_device");
    if (make_device_func == nullptr) {
      printf("Failed to dlsym make_device: %s\n", dlerror());
    }
  }

6. After the non-fastboot mode upgrade, the recovery mode upgrade starts, start_recovery

auto ret = fastboot ? StartFastboot(device, args) : start_recovery(device, args);

Enter recovery.cpp 

1. Parameter analysis, these parameters actually come from /cache/recovery/command, which have been read into args through get_arg above

2. Various UI information on the interface is displayed, and the power check waits for auxiliary actions.

3. The function is named Install Upgrade Package, but the upgrade package installation has not actually started yet

status = install_package(update_package, should_wipe_cache, true, retry_count, ui);

4. After the installation is over, finish_recovery() completes the finishing work, saves the log, clears the mark in the BCB, and restarts the device.

static void finish_recovery() {
  std::string locale = ui->GetLocale();
  // 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(save_current_log, has_cache, sehandle);

  // 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.
}

install/install.cpp

1. install.cpp actually enters the preparatory action for installing the upgrade package, the install_package just on is fake, here is really_install_package

result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
                                    retry_count, &max_temperature, ui);

2、

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, RecoveryUI* 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));
    } else {
      ensure_path_mounted(path);
    }
  }

  /* 将zip映射到内存中 */
  auto package = Package::CreateMemoryPackage(
      path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
  if (!package) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
    return INSTALL_CORRUPT;
  }

  // Verify package.进行zip包进行签名校验
  if (!verify_package(package.get(), ui)) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
    return INSTALL_CORRUPT;
  }

  // Try to open the package.打开zip包
  ZipArchiveHandle zip = package->GetZipArchiveHandle();
  if (!zip) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
    return INSTALL_CORRUPT;
  }

  // Additionally verify the compatibility of the package if it's a fresh install.
  if (retry_count == 0 && !verify_package_compatibility(zip)) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
    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 =
      /* 执行升级updater进程进行升级 */
      try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
  ui->SetEnableReboot(true);
  ui->Print("\n");

  return result;
}

updater/updater.cpp

1. Read metadata information from the upgrade package

ReadMetadataFromPackage(zip, &metadata)

2. Read the updater process from the upgrade package

int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
                             int status_fd, std::vector<std::string>* cmd) {
  CHECK(cmd != nullptr);

  // In non-A/B 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;
  }

  const std::string binary_path = Paths::Get().temporary_update_binary();
  unlink(binary_path.c_str());
  android::base::unique_fd 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);
  if (error != 0) {
    LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
    return INSTALL_ERROR;
  }

  // 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 name of the package zip file.
  //   - an optional argument "retry" if this update is a retry of a failed update attempt.
  *cmd = {
    binary_path,
    std::to_string(kRecoveryApiVersion),
    std::to_string(status_fd),
    package,
  };
  if (retry_count > 0) {
    cmd->push_back("retry");
  }
  return 0;
}

4. Create a pipeline, where the child process closes the reading end, and the parent process closes the writing end. This is to ensure one-way information communication from the child process to the parent process.

android::base::Pipe(&pipe_read, &pipe_write, 0)

5. Create a child process and run the update-binary process in the child process

  if (pid == 0) {
    umask(022);
    pipe_read.reset();

    // Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
    auto chr_args = StringVectorToNullTerminatedArray(args);
    /* chr_args[0] 其实就是升级包中的 META-INF/com/google/android/update-binary */
    execv(chr_args[0], chr_args.data());
    // 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.
    // (Bug: 34769056)
    fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
    _exit(EXIT_FAILURE);
  }

6. Recovery obtains and displays the information of the child process, progress, ui_print, etc.

FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
while (fgets(buffer, sizeof(buffer), from_child) != nullptr) 

After execv executes the upgrade process, the work is completed in updater/updater.cpp.

1. The main core here is to construct a script parser to execute the commands in the updater-script. As for how the script parser is constructed and executed, I am not very clear.

2. The core program for installing the upgrade package is those registered callback functions in Configure edify's functions.

int main(int argc, char** argv) {
  // Various things log information to stdout or stderr more or less
  // at random (though we've tried to standardize on stdout).  The
  // log file makes more sense if buffering is turned off so things
  // appear in the right order.
  setbuf(stdout, nullptr);
  setbuf(stderr, nullptr);

  // We don't have logcat yet under recovery. Update logs will always be written to stdout
  // (which is redirected to recovery.log).
  android::base::InitLogging(argv, &UpdaterLogger);

  if (argc != 4 && argc != 5) {
    LOG(ERROR) << "unexpected number of arguments: " << argc;
    return 1;
  }

  /* 支持的版本检查 */
  char* version = argv[1];
  if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
    // We support version 1, 2, or 3.
    LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
    return 2;
  }

  // Set up the pipe for sending commands back to the parent process.
  int fd = atoi(argv[2]);
  FILE* cmd_pipe = fdopen(fd, "wb");
  setlinebuf(cmd_pipe);

  // Extract the script from the package.
  /* 从包中提取脚本 */
  const char* package_filename = argv[3];
  MemMapping map;
  if (!map.MapFile(package_filename)) {
    LOG(ERROR) << "failed to map package " << argv[3];
    return 3;
  }
  ZipArchiveHandle za;
  int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
  if (open_err != 0) {
    LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
    CloseArchive(za);
    return 3;
  }

  ZipString script_name(SCRIPT_NAME);
  ZipEntry script_entry;
  int find_err = FindEntry(za, script_name, &script_entry);
  if (find_err != 0) {
    LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
               << ErrorCodeString(find_err);
    CloseArchive(za);
    return 4;
  }

  std::string script;
  script.resize(script_entry.uncompressed_length);
  int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
                                    script_entry.uncompressed_length);
  if (extract_err != 0) {
    LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
    CloseArchive(za);
    return 5;
  }

  // Configure edify's functions.
  /* 注册updater-script中的回调函数 这里主要是一些断言函数 abort assert*/
  RegisterBuiltins();

  /* 这里主要是一些安装升级包的函数 主要是对有文件系统的分区来说*/
  RegisterInstallFunctions();

  /* 这里主要注册对裸分区进行升级的函数 */
  RegisterBlockImageFunctions();
  
  RegisterDynamicPartitionsFunctions();
  RegisterDeviceExtensions();

  // Parse the script.
  std::unique_ptr<Expr> root;
  int error_count = 0;
  int error = ParseString(script, &root, &error_count);
  if (error != 0 || error_count > 0) {
    LOG(ERROR) << error_count << " parse errors";
    CloseArchive(za);
    return 6;
  }

  sehandle = selinux_android_file_context_handle();
  selinux_android_set_sehandle(sehandle);

  if (!sehandle) {
    fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
  }

  // Evaluate the parsed script.

  UpdaterInfo updater_info;
  updater_info.cmd_pipe = cmd_pipe;
  updater_info.package_zip = za;
  updater_info.version = atoi(version);
  updater_info.package_zip_addr = map.addr;
  updater_info.package_zip_len = map.length;

  State state(script, &updater_info);

  if (argc == 5) {
    if (strcmp(argv[4], "retry") == 0) {
      state.is_retry = true;
    } else {
      printf("unexpected argument: %s", argv[4]);
    }
  }

  std::string result;
  bool status = Evaluate(&state, root, &result);

  if (!status) {
    if (state.errmsg.empty()) {
      LOG(ERROR) << "script aborted (no error message)";
      fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
    } else {
      LOG(ERROR) << "script aborted: " << state.errmsg;
      const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
      for (const std::string& line : lines) {
        // Parse the error code in abort message.
        // Example: "E30: This package is for bullhead devices."
        if (!line.empty() && line[0] == 'E') {
          if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
            LOG(ERROR) << "Failed to parse error code: [" << line << "]";
          }
        }
        fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
      }
    }

    // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
    // a more specific code has been set in errmsg.
    if (state.error_code == kNoError) {
      state.error_code = kScriptExecutionFailure;
    }
    fprintf(cmd_pipe, "log error: %d\n", state.error_code);
    // Cause code should provide additional information about the abort.
    if (state.cause_code != kNoCause) {
      fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
      if (state.cause_code == kPatchApplicationFailure) {
        LOG(INFO) << "Patch application failed, retry update.";
        fprintf(cmd_pipe, "retry_update\n");
      } else if (state.cause_code == kEioFailure) {
        LOG(INFO) << "Update failed due to EIO, retry update.";
        fprintf(cmd_pipe, "retry_update\n");
      }
    }

    if (updater_info.package_zip) {
      CloseArchive(updater_info.package_zip);
    }
    return 7;
  } else {
    fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
  }

  if (updater_info.package_zip) {
    CloseArchive(updater_info.package_zip);
  }

  return 0;
}

The above is the recovery mode upgrade process based on Android OTA. I am here to sort out the trend of the entire upgrade process. Many places are still not detailed enough. I hope readers can understand. The core and key points here are the following.

1. The main system and the recovery upgrade system, and the transfer of upgrade messages

2. The fork sub-process in the main system executes the upgrade process and exchanges information through the pipe

3. Separate the use of commands and execution in the updater, the commands are in the updater-script, and the execution is in the update-binary.

4. The upgrade program is brought in through the upgrade package, so the core upgrade process has the opportunity to change or optimize every time, so it is much more flexible than those that preset the upgrade process in the system.

Follow the WeChat public account [ embedded C tribe ], get more essential articles, massive programming materials, let us make progress and grow together.

Guess you like

Origin blog.csdn.net/weixin_35933684/article/details/103061686