Android UpdateEngine 模块分析(四)UpdateEngine 升级逻辑

前言

上一篇分析了 UpdateEngine 的整体架构以及升级功能中 Action 机制以及触发流程,提到 UpdateEngine 提供 Action 机制来控制升级过程中的步骤,每一个 Action 动作都有对应的功能,本篇将会分析这些 Action 的具体功能,分析升级过程中 UpdateEngine 是如何完成的。

上一篇:Android UpdateEngine 模块分析(三)升级触发以及Action机制介绍

正文

UpdateEngine 将升级步骤分为 5 个 Acion,分别是 update_boot_flags_action、install_plan_action、download_action、filesystem_verifier_action、postinstall_runner_action。

1 update_boot_flags_action

update_boot_flags_action 的实现类是 UpdateBootFlagsAction。类定义在 system/update_engine/update_boot_flags_action.h 下。

文件:system/update_engine/update_boot_flags_action.cc

/*
 * BootFlagsAction 功能触发的函数
 * 
 */
void UpdateBootFlagsAction::PerformAction() {
  // 判断当前 Action 是否正在运行,如果是,那么直接结束 Action 并 return。
  if (is_running_) {
    LOG(INFO) << "Update boot flags running, nothing to do.";
    processor_->ActionComplete(this, ErrorCode::kSuccess);
    return;
  }
  // 判断当前 Action 是否已经执行过,如果是,那么直接结束 Action 并 return。
  if (updated_boot_flags_) {
    LOG(INFO) << "Already updated boot flags. Skipping.";
    processor_->ActionComplete(this, ErrorCode::kSuccess);
    return;
  }

  // This is purely best effort. Failures should be logged by Subprocess. Run
  // the script asynchronously to avoid blocking the event loop regardless of
  // the script runtime.
  // 将正在运行标志置为 true
  is_running_ = true;
  LOG(INFO) << "Marking booted slot as good.";
  // 调用 boot_control 接口将当前 slot 标记为 successful
  if (!boot_control_->MarkBootSuccessfulAsync( 
          base::Bind(&UpdateBootFlagsAction::CompleteUpdateBootFlags,
                     base::Unretained(this)))) {
    CompleteUpdateBootFlags(false);
  }
}

// UpdateBootFlagsAction 结束时将正在运行标志置为 false。
void UpdateBootFlagsAction::TerminateProcessing() {
  is_running_ = false;
}

// 作为 MarkBootSuccessfulAsync 函数的回调调用。
void UpdateBootFlagsAction::CompleteUpdateBootFlags(bool successful) {
  // 当运行失败时,打印日志,忽略此阶段的失败。
  if (!successful) {
    // We ignore the failure for now because if the updating boot flags is flaky
    // or has a bug in a specific release, then blocking the update can cause
    // devices to stay behind even though we could have updated the system and
    // fixed the issue regardless of this failure.
    //
    // TODO(ahassani): Add new error code metric for kUpdateBootFlagsFailed.
    LOG(ERROR) << "Updating boot flags failed, but ignoring its failure.";
  }

  // As the callback to MarkBootSuccessfulAsync, this function can still be
  // called even after the current UpdateBootFlagsAction object get destroyed by
  // the action processor. In this case, check the value of the static variable
  // |is_running_| and skip executing the callback function.
  if (!is_running_) {
    LOG(INFO) << "UpdateBootFlagsAction is no longer running.";
    return;
  }

  // 正常结束,将正在运行标志置为 false
  is_running_ = false;
  // 将 updated_boot_flags 置为 true,表明 UpdateBootFlagsAction 已经执行
  updated_boot_flags_ = true;
  // 调用 ActionProcessor 的 ActionComplete 函数结束 UpdateBootFlagsAction 的动作
  processor_->ActionComplete(this, ErrorCode::kSuccess);
}

这里出现了 boot_control 模块的调用,关于 boot_control 的功能以及如何提供实现的,需要分析…

2 install_plan_action

InstallPlanAction 类定义在 install_plan.h 文件中,路径为 “android/system/update_engine/payload_consumer”。

文件:android/system/update_engine/payload_consumer/install_plan.h

class InstallPlanAction : public Action<InstallPlanAction> {
 public:
  InstallPlanAction() {}
  // 初始化 InstallPlanAction 类对象的参数为 InstallPlan 结构体
  explicit InstallPlanAction(const InstallPlan& install_plan)
      : install_plan_(install_plan) {}

  // InstallPlanAction 动作的入口函数
  void PerformAction() override {
    // 如果有输出管道,就设置输出管道对象为 install_plan_ 结构体
    if (HasOutputPipe()) {
      SetOutputObject(install_plan_);
    }
    // 调用 ActionProcessor 的 ActionComplete 函数结束 InstallPlanAction 动作
    processor_->ActionComplete(this, ErrorCode::kSuccess);
  }

  // 定义方法可以获取 install_plan_
  InstallPlan* install_plan() { return &install_plan_; }

  // 定义方法可以获取当前的 Action 类型
  static std::string StaticType() { return "InstallPlanAction"; }
  std::string Type() const override { return StaticType(); }

  // 定义了管道机制中的输入和输出
  typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
  typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;

 private:
  InstallPlan install_plan_;

  DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
};

InstallPlanAction 中实际功能是将 install_plan_ 作为 OutputObject,也就是设置为 DownloadAction 的 ActionPipe 的输入对象。

3 download_action

DownloadAction 定义在 “android/system/update_engine/payload_consumer/download_action.h” 文件中,实现在同路径下的 download_action.cc 文件中。

我们现来看下 DownloadAction 的类图,大致了解下 DownloadAction 结构以及相关功能类。

在这里插入图片描述

图中描述了 DownloadAction 类图、重要成员 MultiRangeHttpFetcher 对象以及其 HttpFetcher 功能结构。DownloadAction 中比较重要的成员对象有 MultiRangeHttpFetcher、FileWriter、DeltaPerformer等。

扫描二维码关注公众号,回复: 17213026 查看本文章

下面分析 DownloadAction 的定义:

文件:android/system/update_engine/payload_consumer/download_action.h

/*
 * DownloadAction 会下载 url 指向的升级文件,将 payload 使用管道传输给 DeltaPerformer,通过 DeltaPerformer 将 delta 写入到磁盘,
 * 所以,DownloadAction 是具体写入到分区升级的动作。
 */
class DownloadAction : public InstallPlanAction, public HttpFetcherDelegate {
 public:
  // Debugging/logging
  static std::string StaticType() { return "DownloadAction"; }

  // Takes ownership of the passed in HttpFetcher. Useful for testing.
  // A good calling pattern is:
  // DownloadAction(prefs, boot_contol, hardware, system_state,
  //                new WhateverHttpFetcher, false);
  DownloadAction(PrefsInterface* prefs,
                 BootControlInterface* boot_control,
                 HardwareInterface* hardware,
                 SystemState* system_state,
                 HttpFetcher* http_fetcher,
                 bool interactive);
  ~DownloadAction() override;

--------------------------------------------------------------------------------------------------------------------------------
下面的方法是 InstallPlanAction 接口的方法。主要控制 Action 的运行、暂停、恢复和停止。

  // InstallPlanAction overrides.
  void PerformAction() override;
  void SuspendAction() override;
  void ResumeAction() override;
  void TerminateProcessing() override;

--------------------------------------------------------------------------------------------------------------------------------

  std::string Type() const override { return StaticType(); }

  // Testing
  void SetTestFileWriter(FileWriter* writer) { writer_ = writer; }

  int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }

--------------------------------------------------------------------------------------------------------------------------------
下面的方法是 HttpFetcherDelegate 接口的方法。
DownloadAction 在 UpdateEngine 流程中主要功能是下载和刷写,而 HttpFetcher 就是 Download 中实现这一步骤的关键成员。
HttpFetcher 是基于 HTTP 库 libcurl 的封装,功能类实现使用异步 I/O,可以访问 MessageLoop 以便于在计时器或者文件描述符改变时请求回调。

  // HttpFetcherDelegate methods (see http_fetcher.h)
  // 每次收到字节数据时调用,如果传输终止或完成,返回 false,反则返回 true。
  bool ReceivedBytes(HttpFetcher* fetcher,
                     const void* bytes,
                     size_t length) override;
  // 设置偏移量时调用。
  void SeekToOffset(off_t offset) override;
  // 传输完成后,将恰好调用这两个方法之一。当通过终止传输中止传输时,将调用 TransferTerminated。在所有其他情况下调用 TransferComplete。
  // 销毁 |fetcher|此回调中的对象。
  void TransferComplete(HttpFetcher* fetcher, bool successful) override;
  void TransferTerminated(HttpFetcher* fetcher) override;

--------------------------------------------------------------------------------------------------------------------------------

  DownloadActionDelegate* delegate() const { return delegate_; }
  // 设置 DownloadActionDelegate 委托对象。
  void set_delegate(DownloadActionDelegate* delegate) { delegate_ = delegate; }

  // 设置一个基础的偏移量。
  void set_base_offset(int64_t base_offset) { base_offset_ = base_offset; }

  // 获取 HttpFetcher 对象。
  HttpFetcher* http_fetcher() { return http_fetcher_.get(); }

  // Returns the p2p file id for the file being written or the empty
  // string if we're not writing to a p2p file.
  std::string p2p_file_id() { return p2p_file_id_; }
  
--------------------------------------------------------------------------------------------------------------------------------

 private:
  // Closes the file descriptor for the p2p file being written and
  // clears |p2p_file_id_| to indicate that we're no longer sharing
  // the file. If |delete_p2p_file| is True, also deletes the file.
  // If there is no p2p file descriptor, this method does nothing.
  void CloseP2PSharingFd(bool delete_p2p_file);

  // Starts sharing the p2p file. Must be called before
  // WriteToP2PFile(). Returns True if this worked.
  bool SetupP2PSharingFd();

  // Writes |length| bytes of payload from |data| into |file_offset|
  // of the p2p file. Also does sanity checks; for example ensures we
  // don't end up with a file with holes in it.
  //
  // This method does nothing if SetupP2PSharingFd() hasn't been
  // called or if CloseP2PSharingFd() has been called.
  void WriteToP2PFile(const void* data, size_t length, off_t file_offset);

  // Start downloading the current payload using delta_performer.
  // 使用 delta_performer 开始下载当前的 payload 数据。
  void StartDownloading();

  // The InstallPlan passed in
  InstallPlan install_plan_;

  // Pointer to the current payload in install_plan_.payloads.
  // 指向 install_plan_.payloads 中的当前有效负载的指针。
  InstallPlan::Payload* payload_{nullptr};

  // SystemState required pointers.
  PrefsInterface* prefs_;
  BootControlInterface* boot_control_;
  HardwareInterface* hardware_;

  // Global context for the system.
  SystemState* system_state_;

  // Pointer to the MultiRangeHttpFetcher that does the http work.
  // Http 工作具体是由 MultiRangeHttpFetcher 完成的。
  std::unique_ptr<MultiRangeHttpFetcher> http_fetcher_;

  // If |true|, the update is user initiated (vs. periodic update checks). Hence
  // the |delta_performer_| can decide not to use O_DSYNC flag for faster
  // update.
  bool interactive_;

  // The FileWriter that downloaded data should be written to. It will
  // either point to *decompressing_file_writer_ or *delta_performer_.
  // 具体执行写入分区的操作对象,指向 decompressing_file_writer_ 或者 delta_performer 对象。
  FileWriter* writer_;

  std::unique_ptr<DeltaPerformer> delta_performer_;

  // Used by TransferTerminated to figure if this action terminated itself or
  // was terminated by the action processor.
  ErrorCode code_;

  // For reporting status to outsiders
  // 委托代理对象,用于向外部发送状态。
  DownloadActionDelegate* delegate_;
  uint64_t bytes_received_{0};  // per file/range
  uint64_t bytes_received_previous_payloads_{0};
  uint64_t bytes_total_{0};
  bool download_active_{false};

  // The file-id for the file we're sharing or the empty string
  // if we're not using p2p to share.
  std::string p2p_file_id_;

  // The file descriptor for the p2p file used for caching the payload or -1
  // if we're not using p2p to share.
  int p2p_sharing_fd_;

  // Set to |false| if p2p file is not visible.
  bool p2p_visible_;

  // Loaded from prefs before downloading any payload.
  size_t resume_payload_index_{0};

  // Offset of the payload in the download URL, used by UpdateAttempterAndroid.
  // 有效负载偏移量
  int64_t base_offset_{0};

  DISALLOW_COPY_AND_ASSIGN(DownloadAction);
};

3.1 HttpFetcher 结构

DownloadAction 实现数据的下载与刷写的关键成员就是 HttpFetcher,HttpFetcher 实际上算是一个基类,实际上提供具体功能的是子类的实现,而这里的子类就是 MultiRangeHttpFetcher。从上面的类图中可以发现,DownloadAction 内部的 http_fetcher_ 对象是 MultiRangeHttpFetcher 类型;MultiRangeHttpFetcher 类中定义 base_fetcher_ 对象是 HttpFetcher 类型,此对象的由来实际是在创建 DownloadAction 时由 update_attempter_android.cc 的 BuildUpdateActions 函数传过来的,实际 HttpFetcher 的子类 FileFetcher 对象,传入到 DownloadAction 后,由构造函数将 FileFetcher 作为参数创建了 MultiRangeHttpFetcher 对象作为 http_fetcher_ ;并将 FileFetcher 对象赋值为 base_fetcher_ 。

梳理:

1、在创建 Actions 时,构造了 FileFetcher 对象,将 FileFetcher 对象作为参数传递给 DownloadAction 的构造函数

2、DownloadAction 内部定义了 std::unique_ptr http_fetcher_ 对象,实际上是由 FileFetcher 对象作为参数构建的,http_fetcher_(new MultiRangeHttpFetcher(http_fetcher))。

3、MultiRangeHttpFetcher 类中有一个 std::unique_ptr base_fetcher_ 对象,此 base_fetcher_ 的赋值由 MultiRangeHttpFetcher 构造函数赋值,值来源于创建 Actions 时传入的 FileFetcher 对象。

我们来分下 HttpFetcher 的定义,现分析 HttpFetcher 提供哪些功能。

文件:android/system/update_engine/common/http_fetcher.h

class HttpFetcher {
 public:
  // |proxy_resolver| is the resolver that will be consulted for proxy
  // settings. It may be null, in which case direct connections will
  // be used. Does not take ownership of the resolver.
  explicit HttpFetcher(ProxyResolver* proxy_resolver)
      : post_data_set_(false),
        http_response_code_(0),
        delegate_(nullptr),
        proxies_(1, kNoProxy),
        proxy_resolver_(proxy_resolver),
        callback_(nullptr) {}
  virtual ~HttpFetcher();

--------------------------------------------------------------------------------------------------------------------------------

  // 设置委托类 HttpFetcherDelegate 对象
  void set_delegate(HttpFetcherDelegate* delegate) { delegate_ = delegate; }
  // 获取委托类 HttpFetcherDelegate 对象
  HttpFetcherDelegate* delegate() const { return delegate_; }
  int http_response_code() const { return http_response_code_; }

  // Optional: Post data to the server. The HttpFetcher should make a copy
  // of this data and upload it via HTTP POST during the transfer. The type of
  // the data is necessary for properly setting the Content-Type HTTP header.
  void SetPostData(const void* data, size_t size, HttpContentType type);

  // Same without a specified Content-Type.
  void SetPostData(const void* data, size_t size);

  // Proxy methods to set the proxies, then to pop them off.
  void ResolveProxiesForUrl(const std::string& url,
                            const base::Closure& callback);

  void SetProxies(const std::deque<std::string>& proxies) {
    proxies_ = proxies;
  }
  const std::string& GetCurrentProxy() const { return proxies_.front(); }
  bool HasProxy() const { return !proxies_.empty(); }
  void PopProxy() { proxies_.pop_front(); }

  // Downloading should resume from this offset
  virtual void SetOffset(off_t offset) = 0;
  // Set/unset the length of the range to be downloaded.
  virtual void SetLength(size_t length) = 0;
  virtual void UnsetLength() = 0;

  // Begins the transfer to the specified URL. This fetcher instance should not
  // be destroyed until either TransferComplete, or TransferTerminated is
  // called.
  virtual void BeginTransfer(const std::string& url) = 0;

  // Aborts the transfer. The transfer may not abort right away -- delegate's
  // TransferTerminated() will be called when the transfer is actually done.
  virtual void TerminateTransfer() = 0;

  // Add or update a custom header to be sent with every request. If the same
  // |header_name| is passed twice, the second |header_value| would override the
  // previous value.
  virtual void SetHeader(const std::string& header_name,
                         const std::string& header_value) = 0;

  // If data is coming in too quickly, you can call Pause() to pause the
  // transfer. The delegate will not have ReceivedBytes() called while
  // an HttpFetcher is paused.
  virtual void Pause() = 0;

  // Used to unpause an HttpFetcher and let the bytes stream in again.
  // If a delegate is set, ReceivedBytes() may be called on it before
  // Unpause() returns
  virtual void Unpause() = 0;

  // These two function are overloaded in LibcurlHttp fetcher to speed
  // testing.
  virtual void set_idle_seconds(int seconds) {}
  virtual void set_retry_seconds(int seconds) {}

  // Sets the values used to time out the connection if the transfer
  // rate is less than |low_speed_bps| bytes/sec for more than
  // |low_speed_sec| seconds.
  virtual void set_low_speed_limit(int low_speed_bps, int low_speed_sec) = 0;

  // Sets the connect timeout, e.g. the maximum amount of time willing
  // to wait for establishing a connection to the server.
  virtual void set_connect_timeout(int connect_timeout_seconds) = 0;

  // Sets the number of allowed retries.
  virtual void set_max_retry_count(int max_retry_count) = 0;

  // Get the total number of bytes downloaded by fetcher.
  virtual size_t GetBytesDownloaded() = 0;

  ProxyResolver* proxy_resolver() const { return proxy_resolver_; }

--------------------------------------------------------------------------------------------------------------------------------

 protected:
  // Cancels a proxy resolution in progress. The callback passed to
  // ResolveProxiesForUrl() will not be called. Returns whether there was a
  // pending proxy resolution to be canceled.
  bool CancelProxyResolution();

  // The URL we're actively fetching from
  std::string url_;

  // POST data for the transfer, and whether or not it was ever set
  bool post_data_set_;
  brillo::Blob post_data_;
  HttpContentType post_content_type_;

  // The server's HTTP response code from the last transfer. This
  // field should be set to 0 when a new transfer is initiated, and
  // set to the response code when the transfer is complete.
  int http_response_code_;

  // The delegate; may be null.
  HttpFetcherDelegate* delegate_;

  // Proxy servers
  std::deque<std::string> proxies_;

  ProxyResolver* const proxy_resolver_;

  // The ID of the idle callback, used when we have no proxy resolver.
  brillo::MessageLoop::TaskId no_resolver_idle_id_{
      brillo::MessageLoop::kTaskIdNull};

  // Callback for when we are resolving proxies
  std::unique_ptr<base::Closure> callback_;

--------------------------------------------------------------------------------------------------------------------------------

 private:
  // Callback from the proxy resolver
  void ProxiesResolved(const std::deque<std::string>& proxies);

  // Callback used to run the proxy resolver callback when there is no
  // |proxy_resolver_|.
  void NoProxyResolverCallback();

  // Stores the ongoing proxy request id if there is one, otherwise
  // kProxyRequestIdNull.
  ProxyRequestId proxy_request_{kProxyRequestIdNull};

  DISALLOW_COPY_AND_ASSIGN(HttpFetcher);
};

--------------------------------------------------------------------------------------------------------------------------------
HttpFetcherDelegate 委托类定义

// Interface for delegates
class HttpFetcherDelegate {
 public:
  virtual ~HttpFetcherDelegate() = default;

  // Called every time bytes are received. Returns false if this call causes the
  // transfer be terminated or completed otherwise it returns true.
  virtual bool ReceivedBytes(HttpFetcher* fetcher,
                             const void* bytes,
                             size_t length) = 0;

  // Called if the fetcher seeks to a particular offset.
  virtual void SeekToOffset(off_t offset) {}

  // When a transfer has completed, exactly one of these two methods will be
  // called. TransferTerminated is called when the transfer has been aborted
  // through TerminateTransfer. TransferComplete is called in all other
  // situations. It's OK to destroy the |fetcher| object in this callback.
  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
  virtual void TransferTerminated(HttpFetcher* fetcher) {}
};

MultiRangeHttpFetcher 继承自 HttpFetcher 和 HttpFetcherDelegate,在 DownloadAction 中使用此对象实现具体的下载功能。

class MultiRangeHttpFetcher : public HttpFetcher, public HttpFetcherDelegate {
 public:
  // Takes ownership of the passed in fetcher.
  explicit MultiRangeHttpFetcher(HttpFetcher* base_fetcher)
      : HttpFetcher(base_fetcher->proxy_resolver()),
        base_fetcher_(base_fetcher),
        base_fetcher_active_(false),
        pending_transfer_ended_(false),
        terminating_(false),
        current_index_(0),
        bytes_received_this_range_(0) {}
  ~MultiRangeHttpFetcher() override {}

  void ClearRanges() { ranges_.clear(); }

  void AddRange(off_t offset, size_t size) {
    CHECK_GT(size, static_cast<size_t>(0));
    ranges_.push_back(Range(offset, size));
  }

  void AddRange(off_t offset) { ranges_.push_back(Range(offset)); }

  // HttpFetcher overrides.
  void SetOffset(off_t offset) override;

  void SetLength(size_t length) override {}  // unsupported
  void UnsetLength() override {}

  // Begins the transfer to the specified URL.
  // State change: Stopped -> Downloading
  // (corner case: Stopped -> Stopped for an empty request)
  void BeginTransfer(const std::string& url) override;

  // State change: Downloading -> Pending transfer ended
  void TerminateTransfer() override;

  void SetHeader(const std::string& header_name,
                 const std::string& header_value) override {
    base_fetcher_->SetHeader(header_name, header_value);
  }

  void Pause() override { base_fetcher_->Pause(); }

  void Unpause() override { base_fetcher_->Unpause(); }

  // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
  void set_idle_seconds(int seconds) override {
    base_fetcher_->set_idle_seconds(seconds);
  }
  void set_retry_seconds(int seconds) override {
    base_fetcher_->set_retry_seconds(seconds);
  }
  // TODO(deymo): Determine if this method should be virtual in HttpFetcher so
  // this call is sent to the base_fetcher_.
  virtual void SetProxies(const std::deque<std::string>& proxies) {
    base_fetcher_->SetProxies(proxies);
  }

  inline size_t GetBytesDownloaded() override {
    return base_fetcher_->GetBytesDownloaded();
  }

  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
    base_fetcher_->set_low_speed_limit(low_speed_bps, low_speed_sec);
  }

  void set_connect_timeout(int connect_timeout_seconds) override {
    base_fetcher_->set_connect_timeout(connect_timeout_seconds);
  }

  void set_max_retry_count(int max_retry_count) override {
    base_fetcher_->set_max_retry_count(max_retry_count);
  }
 private:
  // A range object defining the offset and length of a download chunk.  Zero
  // length indicates an unspecified end offset (note that it is impossible to
  // request a zero-length range in HTTP).
  class Range {
   public:
    Range(off_t offset, size_t length) : offset_(offset), length_(length) {}
    explicit Range(off_t offset) : offset_(offset), length_(0) {}

    inline off_t offset() const { return offset_; }
    inline size_t length() const { return length_; }

    inline bool HasLength() const { return (length_ > 0); }

    std::string ToString() const;

   private:
    off_t offset_;
    size_t length_;
  };
  typedef std::vector<Range> RangesVect;

  // State change: Stopped or Downloading -> Downloading
  void StartTransfer();

  // HttpFetcherDelegate overrides.
  // State change: Downloading -> Downloading or Pending transfer ended
  bool ReceivedBytes(HttpFetcher* fetcher,
                     const void* bytes,
                     size_t length) override;

  // State change: Pending transfer ended -> Stopped
  void TransferEnded(HttpFetcher* fetcher, bool successful);
  // These two call TransferEnded():
  void TransferComplete(HttpFetcher* fetcher, bool successful) override;
  void TransferTerminated(HttpFetcher* fetcher) override;

  void Reset();

  std::unique_ptr<HttpFetcher> base_fetcher_;

  // If true, do not send any more data or TransferComplete to the delegate.
  bool base_fetcher_active_;

  // If true, the next fetcher needs to be started when TransferTerminated is
  // received from the current fetcher.
  bool pending_transfer_ended_;

  // True if we are waiting for base fetcher to terminate b/c we are
  // ourselves terminating.
  bool terminating_;

  RangesVect ranges_;

  RangesVect::size_type current_index_;  // index into ranges_
  size_t bytes_received_this_range_;

  DISALLOW_COPY_AND_ASSIGN(MultiRangeHttpFetcher);
};

3.2 PerformAction

PerformAction 是 Action 的入口函数,我们从此函数开始分析

文件:android/system/update_engine/payload_consumer/download_action.cc

void DownloadAction::PerformAction() {
  http_fetcher_->set_delegate(this);

  // Get the InstallPlan and read it
  // 获取 AcionPipe 管道中的输入对象 InstallPlan 结构体,用 install_plan_ 指针表示。
  CHECK(HasInputObject());
  install_plan_ = GetInputObject();
  install_plan_.Dump();

--------------------------------------------------------------------------------------------------------------------------------
下载前数据变量的重置,例如已经接收的字节数清零、计算总字节数等。
  // 定义接收的字节数
  bytes_received_ = 0;
  // 定义接收之前的 payloads 的字节数
  bytes_received_previous_payloads_ = 0;
  // 定义接收到的字节总数
  bytes_total_ = 0;
  // 获取 payload 的字节总数
  for (const auto& payload : install_plan_.payloads)
    bytes_total_ += payload.size;

--------------------------------------------------------------------------------------------------------------------------------
判断本次升级是否是 resume 升级,resume 升级指的就是前一次升级中断,本次升级进行上一次升级的恢复。

  if (install_plan_.is_resume) {
    int64_t payload_index = 0;
    // 获取需要继续更新的 payload 的索引。
    if (prefs_->GetInt64(kPrefsUpdateStatePayloadIndex, &payload_index) &&
        static_cast<size_t>(payload_index) < install_plan_.payloads.size()) {
      // Save the index for the resume payload before downloading any previous
      // payload, otherwise it will be overwritten.
      resume_payload_index_ = payload_index;
      // 获取到了索引后,设置标志说明在该索引之前的数据都已经应用过了
      for (int i = 0; i < payload_index; i++)
        install_plan_.payloads[i].already_applied = true;
    }
  }

--------------------------------------------------------------------------------------------------------------------------------
标记需要升级的分区 target_slot 的分区状态为 unboot。

  // TODO(senj): check that install plan has at least one payload.
  if (!payload_)
    payload_ = &install_plan_.payloads[0];

  LOG(INFO) << "Marking new slot as unbootable";
  // 将 target_slot 标记为 unboot 状态
  if (!boot_control_->MarkSlotUnbootable(install_plan_.target_slot)) {
    LOG(WARNING) << "Unable to mark new slot "
                 << BootControlInterface::SlotName(install_plan_.target_slot)
                 << ". Proceeding with the update anyway.";
  }

--------------------------------------------------------------------------------------------------------------------------------
调用此接口开始下载。
  StartDownloading();
}

总结:DownloadAction 的入口函数完成了如下的工作

  • 获取管道输入对象 InstallPlan 结构体。
  • 下载过程中需要的数据标志的重置,例如接收到的字节、总字节大小等。
  • 对于 resume 升级,需要找到需要更新的索引点 payload_index,标记哪些数据已经应用,哪些数据需要被重写等。
  • 标记 target_slot 需要升级的分区的启动状态为 unboot,不可启动。
  • 调用 StartDownloading() 接口进行下载。

3.3 StartDownloading

开始下载接口

文件:android/system/update_engine/payload_consumer/download_action.cc

void DownloadAction::StartDownloading() {

--------------------------------------------------------------------------------------------------------------------------------
download_active_ 标志 DownloadAction 开始运行;http_fetcher_->ClearRanges() 函数是对下载过程中的缓存空间的重置
  download_active_ = true;
  http_fetcher_->ClearRanges();

--------------------------------------------------------------------------------------------------------------------------------
对于恢复升级场景的处理:
在前面 PerformAction 函数中对是否是 resume 升级场景有过判断,找出了索引点。这里对于 resume 场景进行数据上的处理。

  if (install_plan_.is_resume &&
      payload_ == &install_plan_.payloads[resume_payload_index_]) {  // resume 场景
    // Resuming an update so fetch the update manifest metadata first.
    int64_t manifest_metadata_size = 0;  // 原数据大小初始化
    int64_t manifest_signature_size = 0;  // 签名数据大小初始化
    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);  // 获取 原数据大小,这里的写入是 write 中写入的,如果上一次上级没有进行到 write,那么这里就不会获取到。
    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);  // 获取 签名数据大小
    
    // Range 类是 HttpFetcher 内部类,主要表示所要下载数据的偏移量和长度
    // 这里是将 基础偏移量 和 manifest 长度 作为数据,先构建一个 range 保存。
    http_fetcher_->AddRange(base_offset_,
                            manifest_metadata_size + manifest_signature_size);
    // If there're remaining unprocessed data blobs, fetch them. Be careful not
    // to request data beyond the end of the payload to avoid 416 HTTP response
    // error codes.
    // 获取剩余未处理的的数据 blob。
    int64_t next_data_offset = 0;
    // kPrefsUpdateStateNextDataOffset 中保存的应该是上一次中断下载的偏移量位置,这里保存到 next_data_offset 变量中。
    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset); 
    // 计算恢复位置偏移量
    uint64_t resume_offset =
        manifest_metadata_size + manifest_signature_size + next_data_offset;
    // 将 恢复偏移量 以及 剩余数据量/默认读到文件末尾 作为参数构造 range 保存下载。
    if (!payload_->size) {
      http_fetcher_->AddRange(base_offset_ + resume_offset);
    } else if (resume_offset < payload_->size) {
      http_fetcher_->AddRange(base_offset_ + resume_offset,
                              payload_->size - resume_offset);
    }
  } else {                                                          // 非 resume 场景,全新升级
    // 全新升级情况下,如果 payload 数据文件有大小,那么就将 基本偏移量 和 payload长度作为参数 构造 range 保存下载。
    if (payload_->size) {
      http_fetcher_->AddRange(base_offset_, payload_->size);
    } else {
      // If no payload size is passed we assume we read until the end of the
      // stream.
      // 如果没有传入大小,默认读到文件末尾。
      http_fetcher_->AddRange(base_offset_);
    }
  }
  
--------------------------------------------------------------------------------------------------------------------------------
这里初始化 writer_ 以及 delta_performer_ 对象。
FileWriter writer_
DeltaPerformer delta_performer_
writer_ 是 FileWriter 对象,比较重要,是写入的关键对象。

  if (writer_ && writer_ != delta_performer_.get()) {
    LOG(INFO) << "Using writer for test.";
  } else {
    delta_performer_.reset(new DeltaPerformer(prefs_,
                                              boot_control_,
                                              hardware_,
                                              delegate_,
                                              &install_plan_,
                                              payload_,
                                              interactive_));
    // 将 delta_performer_ 赋值给 writer_
    writer_ = delta_performer_.get();
  }
  
--------------------------------------------------------------------------------------------------------------------------------
这里传入的 system_state 变量为就 null,此间段内容在运行中不会运行到。

  if (system_state_ != nullptr) {
    const PayloadStateInterface* payload_state = system_state_->payload_state();
    string file_id = utils::CalculateP2PFileId(payload_->hash, payload_->size);
    if (payload_state->GetUsingP2PForSharing()) {
      // If we're sharing the update, store the file_id to convey
      // that we should write to the file.
      p2p_file_id_ = file_id;
      LOG(INFO) << "p2p file id: " << p2p_file_id_;
    } else {
      // Even if we're not sharing the update, it could be that
      // there's a partial file from a previous attempt with the same
      // hash. If this is the case, we NEED to clean it up otherwise
      // we're essentially timing out other peers downloading from us
      // (since we're never going to complete the file).
      FilePath path = system_state_->p2p_manager()->FileGetPath(file_id);
      if (!path.empty()) {
        if (unlink(path.value().c_str()) != 0) {
          PLOG(ERROR) << "Error deleting p2p file " << path.value();
        } else {
          LOG(INFO) << "Deleting partial p2p file " << path.value()
                    << " since we're not using p2p to share.";
        }
      }
    }

    // Tweak timeouts on the HTTP fetcher if we're downloading from a
    // local peer.
    if (payload_state->GetUsingP2PForDownloading() &&
        payload_state->GetP2PUrl() == install_plan_.download_url) {
      LOG(INFO) << "Tweaking HTTP fetcher since we're downloading via p2p";
      http_fetcher_->set_low_speed_limit(kDownloadP2PLowSpeedLimitBps,
                                         kDownloadP2PLowSpeedTimeSeconds);
      http_fetcher_->set_max_retry_count(kDownloadP2PMaxRetryCount);
      http_fetcher_->set_connect_timeout(kDownloadP2PConnectTimeoutSeconds);
    }
  }

--------------------------------------------------------------------------------------------------------------------------------
这里调用 HttpFetcher 的 BeginTransfer 函数开始传输。

  http_fetcher_->BeginTransfer(install_plan_.download_url);
}

总结一下

  • 对于恢复场景和正常升级场景进行数据计算
  • 构建 FileWriter 对象
  • 调用 HttpFetcher 类的 BeginTransfer 方法开始传输
3.3.1 MultiRangeHttpFetcher::BeginTransfer()

开始下载的入口函数会调用到 MultiRangeHttpFetcher 的 BeginTransfer 函数里。

文件:system/update_engine/common/multi_range_http_fetcher.cc

void MultiRangeHttpFetcher::BeginTransfer(const std::string& url) {
  CHECK(!base_fetcher_active_) << "BeginTransfer but already active.";
  CHECK(!pending_transfer_ended_) << "BeginTransfer but pending.";
  CHECK(!terminating_) << "BeginTransfer but terminating.";

--------------------------------------------------------------------------------------------------------------------------------
在开始下载前,对一些下载过程中使用的标志变量初始化或清零。最后调用 StartTransfer 函数开始传输。

  if (ranges_.empty()) {
    // Note that after the callback returns this object may be destroyed.
    if (delegate_)
      delegate_->TransferComplete(this, true);
    return;
  }
  url_ = url;
  current_index_ = 0;
  bytes_received_this_range_ = 0;
  LOG(INFO) << "starting first transfer";
  base_fetcher_->set_delegate(this);  // base_fetcher_ 是 FileFetcher 类型变量,设置其委托类。
  // 调用 StartTransfer 开始传输。
  StartTransfer();
}

--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
StartTransfer 函数
这里功能是对下载前的偏移量和下载长度做初始化,并调用 FileFetcher->BeginTransfer 函数开始传输。

void MultiRangeHttpFetcher::StartTransfer() {
  // 如果当前流位置 大于等于 ranges_ 配置的长度,表明下载量已经达到,下载完成,退出运行。
  if (current_index_ >= ranges_.size()) {
    return;
  }

  /*
   * ranges_ 是一个存储 Range 的 Vector 容器。
   * Range 是一个类,定义了两个成员变量 offset 和 length。
   * offset 规定了从文件的开始下载的偏移量位置;length 规定了从偏移量开始下载的长度。
   */
  // ranges_ 中保存的可能不止一个 Range 对象,但是从日志中搜索一般应该只有一个 Range 对象。
  // 日志如下:starting transfer of range 659+2447066254  【659 是偏移量;2447066254 是 payload.bin 的文件大小】
  // 那么 range 也就是描述下载 payload.bin 的数据位置的类。
  Range range = ranges_[current_index_];
  LOG(INFO) << "starting transfer of range " << range.ToString();

  bytes_received_this_range_ = 0;
  base_fetcher_->SetOffset(range.offset());
  if (range.HasLength())
    base_fetcher_->SetLength(range.length());
  else
    base_fetcher_->UnsetLength();
  if (delegate_)
    delegate_->SeekToOffset(range.offset()); // 这里是将 byte_received_ 的值赋值为 range.offset,表示 offset 前面的数据跳过。
  base_fetcher_active_ = true;
  // 调用 file_fetcher 的 BeginTransfer() 开始传输。
  base_fetcher_->BeginTransfer(url_);
}
3.3.2 FileFetcher::BeginTransfer()

MultiRangeHttpFetcher 调用 base_fetcher_ 对象的 BeginTransfer() 函数开始下载,可以发现真正的下载功能是由 FileFetcher 类完成的,MultiRangeHttpFetcher 是起到一个通知的作用。这里进入 file_fetcher.cc 分析如何下载的。

文件:system/update_engine/common/file_fetcher.cc

void FileFetcher::BeginTransfer(const string& url) {
  CHECK(!transfer_in_progress_);

--------------------------------------------------------------------------------------------------------------------------------
判断 URL 路径是否支持,如果不支持,则取消下载。

  if (!SupportedUrl(url)) {
    LOG(ERROR) << "Unsupported file URL: " << url;
    // No HTTP error code when the URL is not supported.
    http_response_code_ = 0;
    CleanUp();
    if (delegate_)
      delegate_->TransferComplete(this, false);
    return;
  }

--------------------------------------------------------------------------------------------------------------------------------
下面是对于文件读写的初始化,以只读的方式打开升级 zip 文件。

  // 获取升级 zip 包文件路径,例如下发路径为“file:///storage/798A-ECED/usb_ota_update.zip”,
  // 则 file_path 为 “/storage/798A-ECED/usb_ota_update.zip”
  string file_path = url.substr(strlen("file://"));
  // 以只读的方式打开 zip 升级包文件,文件读写指针 stream_。
  stream_ =
      brillo::FileStream::Open(base::FilePath(file_path),
                               brillo::Stream::AccessMode::READ,
                               brillo::FileStream::Disposition::OPEN_EXISTING,
                               nullptr);

  // 如果文件没有打开成功,那么需要做一些从之操作,并使用 DownloadActionDelegate 委托对象通知外部文件打开失败,动作执行中止。
  if (!stream_) {
    LOG(ERROR) << "Couldn't open " << file_path;
    http_response_code_ = kHttpResponseNotFound;
    CleanUp();
    if (delegate_)
      delegate_->TransferComplete(this, false);
    return;
  }
  http_response_code_ = kHttpResponseOk;

--------------------------------------------------------------------------------------------------------------------------------
下面是文件成功打开,需要设置偏移量,并调用 ScheduleRead 函数开始读取。

  // 如果设置了偏移量,就设置当前 stream_ 流的位置。从偏移量开始读取。
  if (offset_)
    stream_->SetPosition(offset_, nullptr);
  bytes_copied_ = 0;
  transfer_in_progress_ = true;
  // 调用 ScheduleRead 函数开始读取
  ScheduleRead();
}

--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
ScheduleRead() 函数


void FileFetcher::ScheduleRead() {
  // 先对传输状态进行判断,paused、read、!progress,则返回
  // ongoing_read_ 表示一次下载过程的成功与否,每一次下载成功后,会置为 false,用于表述下一次下载。
  if (transfer_paused_ || ongoing_read_ || !transfer_in_progress_)
    return;

  // 设置 buffer 缓存区的大小,kReadBufferSize 为 16 * 1024 (16K)
  buffer_.resize(kReadBufferSize);
  size_t bytes_to_read = buffer_.size();
  // 对于最后一包,可能不足 16k 数据,计算此时的数据长度。整个数据长度 - 已经下载的数据长度 对比 16K,取较小值。
  if (data_length_ >= 0) {
    bytes_to_read = std::min(static_cast<uint64_t>(bytes_to_read),
                             data_length_ - bytes_copied_);
  }

  // 如果待下载的数据为 0,那么说明已经下载完毕,这里调用 OnReadDoneCallback(0) 退出下载。传入的参数 0,表示下载结束,非 0 则会继续下载。
  if (!bytes_to_read) {
    OnReadDoneCallback(0);
    return;
  }

  // 这里通过 stream_->ReadAsync 函数将下载任务下发,设置回调 OnReadDoneCallback 和 OnReadErrorCallback。
  ongoing_read_ = stream_->ReadAsync(
      buffer_.data(),
      bytes_to_read,
      base::Bind(&FileFetcher::OnReadDoneCallback, base::Unretained(this)),
      base::Bind(&FileFetcher::OnReadErrorCallback, base::Unretained(this)),
      nullptr);

  // 下载出现异常的情况,清除下载数据,并通过委托类上报异常。
  if (!ongoing_read_) {
    LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
    CleanUp();
    if (delegate_)
      delegate_->TransferComplete(this, false);
  }
}

--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
OnReadDoneCallback() 函数
此函数是每次成功读取一次后,都会触发的回调,除了最后一包外,每次下载 16K 的数据。并将下载完成的数据传递回委托对象用于下载后的写入。
这里有两个控制:
	控制下载完成流程
	控制重复下载流程

void FileFetcher::OnReadDoneCallback(size_t bytes_read) {
  // 本次下载成功,ongoing_read_ 置为 false,用于标识下一次下载。
  ongoing_read_ = false;
  
  /*
   * 这里两种情况:byte_read = 0 || byte_read !=0
   * byte_read = 0 :
   *	表示需要下载的长度为 0,那么下载就已经结束了,此时通过委托类的 TransferComplete(this, true) 函数通知外部。true 参数表示下载成功。
   *
   * byte_read !=0 :
   *	表示需要下载的长度,将 bytes_copied 的数量更新,调用 MultiRangeHttpFetcher::ReceivedBytes() 函数将数据传递回去,准备写入。
   *    并调用 scheduleRead() 函数继续下载
   */
  if (bytes_read == 0) {
    CleanUp();
    if (delegate_)
      delegate_->TransferComplete(this, true);
  } else {
    bytes_copied_ += bytes_read;
    if (delegate_ &&
        !delegate_->ReceivedBytes(this, buffer_.data(), bytes_read))
      return;
    ScheduleRead();
  }
}

--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
OnReadErrorCallback() 函数
此函数是每次下载失败后的回调。功能就是清除下载,并调用委托类的 TransferComplete(this, false) 函数通知外部下载失败。false 参数表示下载失败。

void FileFetcher::OnReadErrorCallback(const brillo::Error* error) {
  LOG(ERROR) << "Asynchronous read failed: " << error->GetMessage();
  CleanUp();
  if (delegate_)
    delegate_->TransferComplete(this, false);
}
3.3.3 MultiRangeHttpFetcher::ReceivedBytes

FileFetcher 对象每次读取 16k 的数据,会将数据通过 MultiRangeHttpFetcher::ReceivedBytes() 函数将数据传递回来,接下来分析接收到数据后是如何写入的。

文件:system/update_engine/common/multi_range_http_fetcher.cc


// State change: Downloading -> Downloading or Pending transfer ended
bool MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher,
                                          const void* bytes,
                                          size_t length) {
参数分析:
	HttpFetcher* fetcher:传递的是 FileFetcher,是具体下载的对象
	void* bytes:表示 buffer_ 缓存区的数据,就是下载的数据,除了最后一包外,大小固定为 16K
	size_t length:表示 buffer_ 缓存区的大小,除了最后一包外,大小固定为 16K
--------------------------------------------------------------------------------------------------------------------------------

  CHECK_LT(current_index_, ranges_.size());
  CHECK_EQ(fetcher, base_fetcher_.get());
  CHECK(!pending_transfer_ended_);
  size_t next_size = length;  // next_size 的值实际能理解为 传过来的数据量
  Range range = ranges_[current_index_];
  if (range.HasLength()) {
    next_size =
        std::min(next_size, range.length() - bytes_received_this_range_);  // 获取接收到数据的长度
        // 注意这个时候的 bytes_received_this_range_ 实际是没有更新的,我理解较小值应该是 next_size。
  }
  LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
  // bytes_received_this_range_ needs to be updated regardless of the delegate_
  // result, because it will be used to determine a successful transfer in
  // TransferEnded().
  // 更新已经接收到的数据的总长度。
  bytes_received_this_range_ += length;
  
--------------------------------------------------------------------------------------------------------------------------------
通过委托类 DownloadAction 的 ReceivedBytes() 将数据向外传递。

  if (delegate_ && !delegate_->ReceivedBytes(this, bytes, next_size))
    return false;

--------------------------------------------------------------------------------------------------------------------------------
当 bytes_received_this_range_ >= range.length() 成立时,那么表示下载已经完成。
此时就终止当前的传输。会提前发出传输已经完成的信号。

  if (range.HasLength() && bytes_received_this_range_ >= range.length()) {
    // Terminates the current fetcher. Waits for its TransferTerminated
    // callback before starting the next range so that we don't end up
    // signalling the delegate that the whole multi-transfer is complete
    // before all fetchers are really done and cleaned up.
    pending_transfer_ended_ = true;
    LOG(INFO) << "Terminating transfer.";
    fetcher->TerminateTransfer();
    return false;
  }
  return true;
}
3.3.4 DownloadAction::ReceivedBytes()

每一次接收到数据之后,MultiRangeHttpFetcher 类会将数据通过委托类对象 DownloadAction 向外部传递。

文件:system/update_engine/payload_consumer/download_action.cc

bool DownloadAction::ReceivedBytes(HttpFetcher* fetcher,
                                   const void* bytes,
                                   size_t length) {
参数分析:
	HttpFetcher* fetcher:传递的是 MultiRangeHttpFetcher 对象。
	void* bytes:表示 buffer_ 缓存区的数据,就是下载的数据,除了最后一包外,大小固定为 16K
	size_t length:表示 buffer_ 缓存区的大小,除了最后一包外,大小固定为 16K
--------------------------------------------------------------------------------------------------------------------------------
由于 p2p_file_id_ 没有运行到,这里跳过。

  // Note that bytes_received_ is the current offset.
  if (!p2p_file_id_.empty()) {
    WriteToP2PFile(bytes, length, bytes_received_);
  }

--------------------------------------------------------------------------------------------------------------------------------
更新已经接收到的字节长度 bytes_received_。并调用委托类 UpdateAttempterAndroid 对象的 BytesReceived() 函数将进度传递给外部。

  bytes_received_ += length;
  uint64_t bytes_downloaded_total =
      bytes_received_previous_payloads_ + bytes_received_;
  if (delegate_ && download_active_) {
    delegate_->BytesReceived(length, bytes_downloaded_total, bytes_total_);
  }

--------------------------------------------------------------------------------------------------------------------------------
调用 writer_->Write() 函数将数据写入。
在 StartDownloading() 流程中已经完成了对 writer 对象的初始化。这里是数据写入流程。

  if (writer_ && !writer_->Write(bytes, length, &code_)) {
    if (code_ != ErrorCode::kSuccess) {
      LOG(ERROR) << "Error " << utils::ErrorCodeToString(code_) << " (" << code_
                 << ") in DeltaPerformer's Write method when "
                 << "processing the received payload -- Terminating processing";
    }
    // Delete p2p file, if applicable.
    if (!p2p_file_id_.empty())
      CloseP2PSharingFd(true);
    // Don't tell the action processor that the action is complete until we get
    // the TransferTerminated callback. Otherwise, this and the HTTP fetcher
    // objects may get destroyed before all callbacks are complete.
    TerminateProcessing();
    return false;
  }

--------------------------------------------------------------------------------------------------------------------------------

  // Call p2p_manager_->FileMakeVisible() when we've successfully
  // verified the manifest!
  if (!p2p_visible_ && system_state_ && delta_performer_.get() &&
      delta_performer_->IsManifestValid()) {
    LOG(INFO) << "Manifest has been validated. Making p2p file visible.";
    system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_);
    p2p_visible_ = true;
  }
  return true;
}
3.3.5 UpdateAttempterAndroid::BytesReceived

每一包数据下载完毕之后,DownloadAction 在写入前会通过委托类对象 UpdateAttempterAndroid 的 ByteReceived() 函数将此次下载包的大小、已下载字节大小和数据总大小传递给外部。

文件:system/update_engine/update_attempter_android.cc

void UpdateAttempterAndroid::BytesReceived(uint64_t bytes_progressed,
                                           uint64_t bytes_received,
                                           uint64_t total) {

--------------------------------------------------------------------------------------------------------------------------------
这里是计算下载的进度 progress,并调用 ProgressUpdate() 返回升级进度。如果下载完成,则调用 SetStatusAndNotify() 更新整个升级的状态。

  double progress = 0;
  if (total)
    progress = static_cast<double>(bytes_received) / static_cast<double>(total);
  if (status_ != UpdateStatus::DOWNLOADING || bytes_received == total) {
    download_progress_ = progress;
    SetStatusAndNotify(UpdateStatus::DOWNLOADING);
  } else {
    ProgressUpdate(progress);
  }

--------------------------------------------------------------------------------------------------------------------------------
将已下载的数据量用 prefs 更新到文件中。这里可能跟 resume 升级,恢复下载进度有关联。

  // Update the bytes downloaded in prefs.
  int64_t current_bytes_downloaded =
      metrics_utils::GetPersistedValue(kPrefsCurrentBytesDownloaded, prefs_);
  int64_t total_bytes_downloaded =
      metrics_utils::GetPersistedValue(kPrefsTotalBytesDownloaded, prefs_);
  prefs_->SetInt64(kPrefsCurrentBytesDownloaded,
                   current_bytes_downloaded + bytes_progressed);
  prefs_->SetInt64(kPrefsTotalBytesDownloaded,
                   total_bytes_downloaded + bytes_progressed);
}
3.3.6 DeltaPerformer::Write()

DownloadAction 中每接收到一次数据,就会调用 DeltaPerformer 对象的 Write() 函数将这包数据写入到磁盘中。

文件:system/update_engine/payload_consumer/delta_performer.cc

/*
 * 将数据写入
 * 从整体去分析 Write 函数,可以将整个过程分为两个 While 循环和一个 if 判断。
 */
// Wrapper around write. Returns true if all requested bytes
// were written, or false on any error, regardless of progress
// and stores an action exit code in |error|.
bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode* error) {

参数分析
	void* bytes:表示 buffer_ 缓存区的数据,就是下载的数据,除了最后一包外,大小固定为 16K
	size_t count:表示 buffer_ 缓存区的大小,除了最后一包外,大小固定为 16K
	ErrorCode* error:Write() 函数的运行结果标记
--------------------------------------------------------------------------------------------------------------------------------

  *error = ErrorCode::kSuccess;
  const char* c_bytes = reinterpret_cast<const char*>(bytes);  // 用字符数组 c_bytes 保存缓存区的数据。

  // Update the total byte downloaded count and the progress logs.
  total_bytes_received_ += count;  // total_bytes_received_ 记录已经接收到的数据大小,持续累加
  UpdateOverallProgress(false, "Completed ");  // 更新进度包括已经应用的操作数,下载的数据量以及总的进度。

--------------------------------------------------------------------------------------------------------------------------------
第一个 while 循环,循环条件是 manifest 是否有效,由 manifest_vaild_ 标识,初始化为 false。

  while (!manifest_valid_) {
    // Read data up to the needed limit; this is either maximium payload header
    // size, or the full metadata size (once it becomes known).
    // IsHeaderParsed() 返回的是 metadata_size_ != 0; metadata_size_ 初始值为 0。所以 do_read_header 首次运行应该是 true。
    const bool do_read_header = !IsHeaderParsed();
    /*
     * CopyDataToBuffer 功能:
     * 将 c_bytes 字符数组中的数据的前 x 个字节的数据移动到 buffer 缓冲区中,并且会将 c_bytes 的前 x 个字节数据移除,count 也会减少相应的长度。
     * 
     * kMaxPayloadHeaderSize = 24。这里为什么是 24,由于 Header 数据在 payload.bin 的长度就是前 24 个字节。
     */
    CopyDataToBuffer(
        &c_bytes,
        &count,
        (do_read_header ? kMaxPayloadHeaderSize
                        : metadata_size_ + metadata_signature_size_));

    /*
     * 解析 Metadata 数据
     * 具体如下:
     * 	获取 Payload Metadata 数据
     * 	专门定义 payloadMetadata 类对象去完成校验 metadata 数据的工作
     *	获取公钥,路径在:/etc/update_engine/update-payload-key.pub.pem 下
     *	调用 payloadMetadata->ValidateMetadataSignature() 函数对签名进行校验
     *	调用 payloadMetadata->GetManifest() 函数从 payload 中获取 manifest 数据保存到 manifest_ 中
     *
     * 参数解析: buffer_ 中现保存了 Metadata 数据 24 个字节。但是会在此函数中持续增加数据。
     */
    MetadataParseResult result = ParsePayloadMetadata(buffer_, error);
    if (result == MetadataParseResult::kError)
      return false;
    if (result == MetadataParseResult::kInsufficientData) {  // 这里是在等待 Manifest 的全部数据载入到内存。
      // If we just processed the header, make an attempt on the manifest.
      if (do_read_header && IsHeaderParsed())
        continue;

      return true;
    }

    // Checks the integrity of the payload manifest.
    /*
     * 对 manifest 数据进行完整性校验,Manifest 的结构在 update_metadata.proto 中有定义 DeltaArchiveManifest。
     * ValidateManifest 具体功能如下:
     * 1、判断是全量升级还是差分升级。通过 manifest 中是否包含旧分区的信息判断。
     * 2、检查全量升级/差分升级的版本号是否支持
     * 3、版本的时间戳检测,控制版本回退功能。此项检测已经注释删除,由于项目上会有回退版本的场景
     */
    if ((*error = ValidateManifest()) != ErrorCode::kSuccess)
      return false;
    manifest_valid_ = true;  // 此处退出循环,上面 ValidateManifest() 函数验证 Manifest 清单数据。如果能走到这一步,那么这个循环下一次将会结束

    /*
     * 函数执行到这里,表明升级前的检测已经完成。此 while 循环将会结束
     */
     
    // Clear the download buffer.
    // 计算 payload_hash_calculator_ 和 signed_hash_calculator_ 哈希值。清空缓存区 buffer_ 的空间。
    DiscardBuffer(false, metadata_size_);

    block_size_ = manifest_.block_size();

    // This populates |partitions_| and the |install_plan.partitions| with the
    // list of partitions from the manifest.
    if (!ParseManifestPartitions(error))
      return false;

    // |install_plan.partitions| was filled in, nothing need to be done here if
    // the payload was already applied, returns false to terminate http fetcher,
    // but keep |error| as ErrorCode::kSuccess.
    if (payload_->already_applied)
      return false;

--------------------------------------------------------------------------------------------------------------------------------
为即将的分区下载做准备
    1、遍历所有分区,将每个分区的下载的字节总数计算保存到 acc_num_operations_ 集合中。
    2、调用 PrimeUpdateState() 函数,对于 Resume 场景,需要对已下载数据偏移量进行偏移,然后更新 resume 次数标识;对于 new update 全新升级场景,务处理。
    3、调用 OpenCurrentPartition() 函数打开当前分区。这里实际是从分区表集合 partitions_ 下标为 0 的索引开始打开。

    num_total_operations_ = 0;
      
    // 遍历所有分区,将每个分区的下载的字节总数 operations 操作数计算保存到 acc_num_operations_ 集合中。
    for (const auto& partition : partitions_) {
      num_total_operations_ += partition.operations_size();
      acc_num_operations_.push_back(num_total_operations_);
    }

    LOG_IF(WARNING,
           !prefs_->SetInt64(kPrefsManifestMetadataSize, metadata_size_))
        << "Unable to save the manifest metadata size.";
    LOG_IF(WARNING,
           !prefs_->SetInt64(kPrefsManifestSignatureSize,
                             metadata_signature_size_))
        << "Unable to save the manifest signature size.";

    // 用 PrimeUpdateState() 函数,对于 Resume 场景,需要对已下载数据偏移量进行偏移,然后更新 resume 次数标识;对于 new update 全新升级场景,务处理。
    if (!PrimeUpdateState()) {
      *error = ErrorCode::kDownloadStateInitializationError;
      LOG(ERROR) << "Unable to prime the update state.";
      return false;
    }

    // 调用 OpenCurrentPartition() 函数打开当前分区。这里实际是从分区表集合 partitions_ 下标为 0 的索引开始打开。next_operation_num_ 初始值为 0
    if (next_operation_num_ < acc_num_operations_[current_partition_]) {
      if (!OpenCurrentPartition()) {
        *error = ErrorCode::kInstallDeviceOpenError;
        return false;
      }
    }

    if (next_operation_num_ > 0)
      UpdateOverallProgress(true, "Resuming after ");
    LOG(INFO) << "Starting to apply update payload operations";
  }

--------------------------------------------------------------------------------------------------------------------------------
第二个 while 循环,循环条件是当前分区数小于总分区数,就是遍历所有分区。
  // 1、判断当前分区的 operation 操作数是否满足数量,如果满足,表示当前分区的数据已经全部载入,此时更新操作分区标识,并打开分区以操作下一个分区。
  // 2、
    
  while (next_operation_num_ < num_total_operations_) {
    // Check if we should cancel the current attempt for any reason.
    // In this case, *error will have already been populated with the reason
    // why we're canceling.
    if (download_delegate_ && download_delegate_->ShouldCancel(error))
      return false;

    // We know there are more operations to perform because we didn't reach the
    // |num_total_operations_| limit yet.
    // next_operation_num_ 代表分区operation的数量,但是此时并没有更新,因为累加是在此循环的后面。
    // acc_num_operations_ 数据中存放的是每个分区所需要操作的 operations 的数量。数量达到表示分区数据操作完毕。此时 next_operation_num_ 在循环中一直累加,分区数据一直更新,直到分区数据全部读取才会进入此判断。
    if (next_operation_num_ >= acc_num_operations_[current_partition_]) {
      // 当分区操作数满足时,关闭当前分区。
      CloseCurrentPartition();
      // Skip until there are operations for current_partition_.
      // 当前分区操作完毕后,需要将分区表示指向下一个分区。
      while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
        current_partition_++;
      }
      // 分区标识更新后,打开新分区,准备操作下一个分区。
      if (!OpenCurrentPartition()) {
        *error = ErrorCode::kInstallDeviceOpenError;
        return false;
      }
    }

--------------------------------------------------------------------------------------------------------------------------------

    // partition_operation_num 初始化,标识当前分区所需要的 operation 操作数。
    const size_t partition_operation_num =
        next_operation_num_ -
        (current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0);

    // 获取 InstallOperation 信息
    const InstallOperation& op =
        partitions_[current_partition_].operations(partition_operation_num);

    // 将 c_bytes 的前 op.data_length() 长度的字节移动到 buffer 中,count 对应 c_bytes 保存的数据长度,拷贝过后,c_bytes 的数据长度也会相应的嫌少 op.data_length(),相当于剪切数据到 buffer 中。
    CopyDataToBuffer(&c_bytes, &count, op.data_length());

    // Check whether we received all of the next operation's data payload.检查是否已经缓存了下一个 operation 所需的所有数据
    // 主要通过 operation.data_offset() + operation.data_length() <= buffer_offset_ + buffer_.size() 来判断
    if (!CanPerformInstallOperation(op))
      return true;

--------------------------------------------------------------------------------------------------------------------------------
到这里说明 buffer 中已经保存了一个 operation 所需的所有数据,此时准备将一个 operation 的数据写入到分区。
    
    // Validate the operation only if the metadata signature is present.
    // Otherwise, keep the old behavior. This serves as a knob to disable
    // the validation logic in case we find some regression after rollout.
    // NOTE: If hash checks are mandatory and if metadata_signature is empty,
    // we would have already failed in ParsePayloadMetadata method and thus not
    // even be here. So no need to handle that case again here.
    /*
     * 仅当存在元数据签名时,才验证操作。否则,请保留旧行为。这可以作为一个旋钮来禁用验证逻辑,以防我们在推出后发现一些回归。
     * 注意:如果哈希检查是强制性的,如果metadata_signature为空,那么我们已经在ParsePayloadMetadata方法中失败了,因此甚至不在这里。
     * 所以没有必要在这里再次处理这种情况。
     */
    if (!payload_->metadata_signature.empty()) {
      // Note: Validate must be called only if CanPerformInstallOperation is
      // called. Otherwise, we might be failing operations before even if there
      // isn't sufficient data to compute the proper hash.
      // 这里调用 ValidateOperationHash(InstallOperation) 函数对数据进行有效性校验。
      *error = ValidateOperationHash(op);
      if (*error != ErrorCode::kSuccess) {
        if (install_plan_->hash_checks_mandatory) {
          LOG(ERROR) << "Mandatory operation hash check failed";
          return false;
        }

        // For non-mandatory cases, just send a UMA stat.
        LOG(WARNING) << "Ignoring operation validation errors";
        *error = ErrorCode::kSuccess;
      }
    }

--------------------------------------------------------------------------------------------------------------------------------

    // Makes sure we unblock exit when this operation completes.确保我们在此操作完成时取消阻止退出。
    ScopedTerminatorExitUnblocker exit_unblocker =
        ScopedTerminatorExitUnblocker();  // Avoids a compiler unused var bug.

    // 获取当前时间保存到 op_start_time 变量中
    base::TimeTicks op_start_time = base::TimeTicks::Now();

    // 这里是通过 InstallOperation 的 Type 类型执行对应的操作
    bool op_result;
    switch (op.type()) {
      case InstallOperation::REPLACE:
      case InstallOperation::REPLACE_BZ:
      case InstallOperation::REPLACE_XZ:
        op_result = PerformReplaceOperation(op);
        OP_DURATION_HISTOGRAM("REPLACE", op_start_time);
        break;
      case InstallOperation::ZERO:
      case InstallOperation::DISCARD:
        op_result = PerformZeroOrDiscardOperation(op);
        OP_DURATION_HISTOGRAM("ZERO_OR_DISCARD", op_start_time);
        break;
      case InstallOperation::MOVE:
        op_result = PerformMoveOperation(op);
        OP_DURATION_HISTOGRAM("MOVE", op_start_time);
        break;
      case InstallOperation::BSDIFF:
        op_result = PerformBsdiffOperation(op);
        OP_DURATION_HISTOGRAM("BSDIFF", op_start_time);
        break;
      case InstallOperation::SOURCE_COPY:
        op_result = PerformSourceCopyOperation(op, error);
        OP_DURATION_HISTOGRAM("SOURCE_COPY", op_start_time);
        break;
      case InstallOperation::SOURCE_BSDIFF:
      case InstallOperation::BROTLI_BSDIFF:
        op_result = PerformSourceBsdiffOperation(op, error);
        OP_DURATION_HISTOGRAM("SOURCE_BSDIFF", op_start_time);
        break;
      case InstallOperation::PUFFDIFF:
        op_result = PerformPuffDiffOperation(op, error);
        OP_DURATION_HISTOGRAM("PUFFDIFF", op_start_time);
        break;
      default:
        op_result = false;
    }
    // InstallOperation 操作结果,根据 type 类型有不同的操作,这里是判断操作的结果,结果存储在 op_result 变量中。
    if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error))
      return false;

    // flush() 函数的作用是将缓冲区中的数据立即写入到分区中,而不是等到缓冲区满或者文件关闭时才进行写入。
    if (!target_fd_->Flush()) {
      return false;
    }

    // 分区 operation 标志自增更新
    next_operation_num_++;
    // 打印更新进度
    UpdateOverallProgress(false, "Completed ");
    /*
     * 保存更新进度,将更新时的数据详细进度写入到 /data/misc/update_engine/prefs/ 目录下的文件中,如果 resume 更新时,读取数据以便继续更新。
     * 数据包括:
     * prefs_->SetString(kPrefsUpdateStateSHA256Context, payload_hash_calculator_.GetContext());
     * prefs_->SetString(kPrefsUpdateStateSignedSHA256Context,signed_hash_calculator_.GetContext());
     * prefs_->SetInt64(kPrefsUpdateStateNextDataOffset, buffer_offset_);
     * prefs_->SetInt64(kPrefsUpdateStateNextDataLength, op.data_length());
     * prefs_->SetInt64(kPrefsUpdateStateNextOperation, next_operation_num_);
     */
    CheckpointUpdateProgress(false);
  }

--------------------------------------------------------------------------------------------------------------------------------
到这里已经表示所有的分区数据已经全部写入到 EMMC 中。前面的分区已经遍历完毕,分区数据已经全部载入并且 flush() 到分区文件中。
    
  // In major version 2, we don't add dummy operation to the payload.
  // If we already extracted the signature we should skip this step.
  // 在主要版本 2 中,我们不会向有效负载添加虚拟操作。如果我们已经提取了签名,我们应该跳过此步骤。
  if (major_payload_version_ == kBrilloMajorPayloadVersion &&
      manifest_.has_signatures_offset() && manifest_.has_signatures_size() &&
      signatures_message_data_.empty()) {
    // 这里就是判断 manifest_ 的 签名数据是否已经提取
    if (manifest_.signatures_offset() != buffer_offset_) {
      LOG(ERROR) << "Payload signatures offset points to blob offset "
                 << manifest_.signatures_offset()
                 << " but signatures are expected at offset " << buffer_offset_;
      *error = ErrorCode::kDownloadPayloadVerificationError;
      return false;
    }
    // 将签名数据从 c_bytes 拷贝到 buffer 中
    CopyDataToBuffer(&c_bytes, &count, manifest_.signatures_size());
    // Needs more data to cover entire signature.
    // 如果 buffer 的长度不够签名的长度,那么我们需要更新的数据来获取全部的签名数据。等待下一个循环。
    if (buffer_.size() < manifest_.signatures_size())
      return true;
    // 调用 ExtractSignatureMessage() 函数获取签名数据。
    /* 
     * ExtractSignatureMessage() 内部调用  signatures_message_data_.assign(buffer_.begin(), buffer_.begin() +  manifest_.signatures_size()) 函数。
     * 将签名数据保存到 signatures_message_data_ 变量中。
     */
    if (!ExtractSignatureMessage()) {
      LOG(ERROR) << "Extract payload signature failed.";
      *error = ErrorCode::kDownloadPayloadVerificationError;
      return false;
    }
    
    // 清空缓存区 buffer_ 的空间。
    DiscardBuffer(true, 0);
    // Since we extracted the SignatureMessage we need to advance the
    // checkpoint, otherwise we would reload the signature and try to extract
    // it again.
    // This is the last checkpoint for an update, force this checkpoint to be
    // saved.
    // 由于我们提取了签名消息,因此我们需要推进检查点,否则我们将重新加载签名并尝试再次提取它。这是更新的最后一个检查点,请强制保存此检查点。
    CheckpointUpdateProgress(true);
  }

  return true;
}


--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
UpdateOverallProgress() 函数,用于计算下载的进度以及整体进度


void DeltaPerformer::UpdateOverallProgress(bool force_log,
                                           const char* message_prefix) {
  // Compute our download and overall progress.
  unsigned new_overall_progress = 0;
  static_assert(kProgressDownloadWeight + kProgressOperationsWeight == 100,
                "Progress weights don't add up");
  // Only consider download progress if its total size is known; otherwise
  // adjust the operations weight to compensate for the absence of download
  // progress. Also, make sure to cap the download portion at
  // kProgressDownloadWeight, in case we end up downloading more than we
  // initially expected (this indicates a problem, but could generally happen).
  // TODO(garnold) the correction of operations weight when we do not have the
  // total payload size, as well as the conditional guard below, should both be
  // eliminated once we ensure that the payload_size in the install plan is
  // always given and is non-zero. This currently isn't the case during unit
  // tests (see chromium-os:37969).
  // payload_size 表示需要下载的总长度
  size_t payload_size = payload_->size;
  unsigned actual_operations_weight = kProgressOperationsWeight;
  if (payload_size)
    new_overall_progress +=
        min(static_cast<unsigned>(IntRatio(
                total_bytes_received_, payload_size, kProgressDownloadWeight)),
            kProgressDownloadWeight);
  else
    actual_operations_weight += kProgressDownloadWeight;

  // Only add completed operations if their total number is known; we definitely
  // expect an update to have at least one operation, so the expectation is that
  // this will eventually reach |actual_operations_weight|.
  if (num_total_operations_)
    new_overall_progress += IntRatio(
        next_operation_num_, num_total_operations_, actual_operations_weight);

  // Progress ratio cannot recede, unless our assumptions about the total
  // payload size, total number of operations, or the monotonicity of progress
  // is breached.
  if (new_overall_progress < overall_progress_) {
    LOG(WARNING) << "progress counter receded from " << overall_progress_
                 << "% down to " << new_overall_progress << "%; this is a bug";
    force_log = true;
  }
  overall_progress_ = new_overall_progress;

  // Update chunk index, log as needed: if forced by called, or we completed a
  // progress chunk, or a timeout has expired.
  base::TimeTicks curr_time = base::TimeTicks::Now();
  unsigned curr_progress_chunk =
      overall_progress_ * kProgressLogMaxChunks / 100;
  if (force_log || curr_progress_chunk > last_progress_chunk_ ||
      curr_time > forced_progress_log_time_) {
    forced_progress_log_time_ = curr_time + forced_progress_log_wait_;
    LogProgress(message_prefix);
  }
  last_progress_chunk_ = curr_progress_chunk;
}

--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------

void DeltaPerformer::LogProgress(const char* message_prefix) {
  // Format operations total count and percentage.
  string total_operations_str("?");
  string completed_percentage_str("");
  if (num_total_operations_) {
    total_operations_str = std::to_string(num_total_operations_);
    // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
    completed_percentage_str = base::StringPrintf(
        " (%" PRIu64 "%%)",
        IntRatio(next_operation_num_, num_total_operations_, 100));
  }

  // Format download total count and percentage.
  size_t payload_size = payload_->size;
  string payload_size_str("?");
  string downloaded_percentage_str("");
  if (payload_size) {
    payload_size_str = std::to_string(payload_size);
    // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
    downloaded_percentage_str = base::StringPrintf(
        " (%" PRIu64 "%%)", IntRatio(total_bytes_received_, payload_size, 100));
  }

  LOG(INFO) << (message_prefix ? message_prefix : "") << next_operation_num_
            << "/" << total_operations_str << " operations"
            << completed_percentage_str << ", " << total_bytes_received_ << "/"
            << payload_size_str << " bytes downloaded"
            << downloaded_percentage_str << ", overall progress "
            << overall_progress_ << "%";
}

--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------

ParsePayloadMetadata() 函数:解析 Payload 中的 Metadata 数据

MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
    const brillo::Blob& payload, ErrorCode* error) {
  *error = ErrorCode::kSuccess;

--------------------------------------------------------------------------------------------------------------------------------
这里是会进入下面的 if 判断的,因为 metadata_size_ 的值是在下面赋值的,这里 if 语句可替换为 if(metadata_size_ == 0)

payload_metadata_::ParsePayloadHeader() 函数:
1、通过校验长度判断 buffer 的数据是否包含 Version
2、通过 memcmp() 函数校验 DeltaMagic 是否正确,"CrAU"
3、保存 major_payload_version_ 版本信息。payload 的 [4,11] 8 个字节的数据。此处有切换到大端排序
4、判断版本 major_payload_version_ 是否支持,支持最小版本为 [kMinSupportedMajorPayloadVersion:1] 最大版本为 [kMaxSupportedMajorPayloadVersion:2] 
5、获取 manifest 结束偏移量,就是 manifest 结束字节位置,根据这个长度判断 buffer_ 是否包含了完整的 manifest 数据
6、保存 manifest_size_ manifest长度信息。payload 的 [12,19] 8个字节的数据。此处有切换到大端排序
7、获取 metadata_signature_size_ 偏移量,获取 metadata_signature_size_ 数据。payload 的 [20,23] 4 个字节的数据。此处有切换到大端排序

  if (!IsHeaderParsed()) {
    MetadataParseResult result =
        payload_metadata_.ParsePayloadHeader(payload, error);  // payload 传入的是 payload 文件前 24 字节的 Header 数据的 buffer_
    if (result != MetadataParseResult::kSuccess)
      return result;

--------------------------------------------------------------------------------------------------------------------------------

    // 验证 header 通过以后,说明 header 数据没有问题,Header 数据包含了magic、Delta Version、Manifest_Size、Metadata_signature_size。说明上述的数据没有问题,就可以从 payload_matedata 中获取这些数据
    metadata_size_ = payload_metadata_.GetMetadataSize();
    metadata_signature_size_ = payload_metadata_.GetMetadataSignatureSize();
    major_payload_version_ = payload_metadata_.GetMajorVersion();

--------------------------------------------------------------------------------------------------------------------------------
这部分是对数据 manifest、metadata 进行校验。

    // If the metadata size is present in install plan, check for it immediately
    // even before waiting for that many number of bytes to be downloaded in the
    // payload. This will prevent any attack which relies on us downloading data
    // beyond the expected metadata size.
    if (install_plan_->hash_checks_mandatory) {
      if (payload_->metadata_size != metadata_size_) {
        LOG(ERROR) << "Mandatory metadata size in Omaha response ("
                   << payload_->metadata_size
                   << ") is missing/incorrect, actual = " << metadata_size_;
        *error = ErrorCode::kDownloadInvalidMetadataSize;
        return MetadataParseResult::kError;
      }
    }
  }

  // Now that we have validated the metadata size, we should wait for the full
  // metadata and its signature (if exist) to be read in before we can parse it.
  // 这里是等待所有的 manifest 数据全部载入到内存。每次读取数据的长度实际是固定的,需要多次读取之后,才能将 payload 中所有的 manifest 的数据载入内存。因此这里使用 kInsufficientData 返回等待 manifest 数据载入的状态。
  if (payload.size() < metadata_size_ + metadata_signature_size_)
    return MetadataParseResult::kInsufficientData;  // kInsufficientData 标识数据没有全部被拷贝到 buffer_ 中,数据不足

  // Log whether we validated the size or simply trusting what's in the payload
  // here. This is logged here (after we received the full metadata data) so
  // that we just log once (instead of logging n times) if it takes n
  // DeltaPerformer::Write calls to download the full manifest.
  if (payload_->metadata_size == metadata_size_) {  // payload_ 中也保存了 metadata_size,对比验证是否匹配
    LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
  } else {
    // For mandatory-cases, we'd have already returned a kMetadataParseError
    // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
    LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
                 << payload_->metadata_size
                 << ") in Omaha response as validation is not mandatory. "
                 << "Trusting metadata size in payload = " << metadata_size_;
  }

--------------------------------------------------------------------------------------------------------------------------------
获取 /etc/update_engine/update-payload-key.pub.pem 公钥文件,保存到 public_key 中。用于校验 Metadata Signature 签名数据。

  string public_key;
  if (!GetPublicKey(&public_key)) {
    LOG(ERROR) << "Failed to get public key.";
    *error = ErrorCode::kDownloadMetadataSignatureVerificationError;
    return MetadataParseResult::kError;
  }

  // We have the full metadata in |payload|. Verify its integrity
  // and authenticity based on the information we have in Omaha response.
  // PayloadMetadata::ValidateMetadataSignature 函数用于校验 Metadata 签名
  *error = payload_metadata_.ValidateMetadataSignature(
      payload, payload_->metadata_signature, public_key);
  if (*error != ErrorCode::kSuccess) {
    if (install_plan_->hash_checks_mandatory) {
      // The autoupdate_CatchBadSignatures test checks for this string
      // in log-files. Keep in sync.
      LOG(ERROR) << "Mandatory metadata signature validation failed";
      return MetadataParseResult::kError;
    }

    // For non-mandatory cases, just send a UMA stat.
    LOG(WARNING) << "Ignoring metadata signature validation failures";
    *error = ErrorCode::kSuccess;
  }

--------------------------------------------------------------------------------------------------------------------------------
获取 manifest 数据,这里获取的是 Header 后面的数据。定义为 char manifest[] 数据。长度是 manifest_size_。
  // The payload metadata is deemed valid, it's safe to parse the protobuf.
  if (!payload_metadata_.GetManifest(payload, &manifest_)) {
    LOG(ERROR) << "Unable to parse manifest in update file.";
    *error = ErrorCode::kDownloadManifestParseError;
    return MetadataParseResult::kError;
  }

  manifest_parsed_ = true;
  return MetadataParseResult::kSuccess;
}


--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
解析 manifest 中 partitions 分区信息

bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) {
  if (major_payload_version_ == kBrilloMajorPayloadVersion) {
    partitions_.clear();
    for (const PartitionUpdate& partition : manifest_.partitions()) {
      partitions_.push_back(partition);
    }
    manifest_.clear_partitions();
  } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
    LOG(INFO) << "Converting update information from old format.";
    PartitionUpdate root_part;
    root_part.set_partition_name(kLegacyPartitionNameRoot);
#ifdef __ANDROID__
    LOG(WARNING) << "Legacy payload major version provided to an Android "
                    "build. Assuming no post-install. Please use major version "
                    "2 or newer.";
    root_part.set_run_postinstall(false);
#else
    root_part.set_run_postinstall(true);
#endif  // __ANDROID__
    if (manifest_.has_old_rootfs_info()) {
      *root_part.mutable_old_partition_info() = manifest_.old_rootfs_info();
      manifest_.clear_old_rootfs_info();
    }
    if (manifest_.has_new_rootfs_info()) {
      *root_part.mutable_new_partition_info() = manifest_.new_rootfs_info();
      manifest_.clear_new_rootfs_info();
    }
    *root_part.mutable_operations() = manifest_.install_operations();
    manifest_.clear_install_operations();
    partitions_.push_back(std::move(root_part));

    PartitionUpdate kern_part;
    kern_part.set_partition_name(kLegacyPartitionNameKernel);
    kern_part.set_run_postinstall(false);
    if (manifest_.has_old_kernel_info()) {
      *kern_part.mutable_old_partition_info() = manifest_.old_kernel_info();
      manifest_.clear_old_kernel_info();
    }
    if (manifest_.has_new_kernel_info()) {
      *kern_part.mutable_new_partition_info() = manifest_.new_kernel_info();
      manifest_.clear_new_kernel_info();
    }
    *kern_part.mutable_operations() = manifest_.kernel_install_operations();
    manifest_.clear_kernel_install_operations();
    partitions_.push_back(std::move(kern_part));
  }


--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
ParseManifestPartitions() 函数主要会在下载各分区镜像数据前,对 Manifest 中的 partition 数据进行解析

bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) {

--------------------------------------------------------------------------------------------------------------------------------
从 manifest 结构中将 PartitionUpdate 格式的 partition 数据拿出来,将 PartitionUpdate 各分区标识对象放入到 partitions_ 中保存。

  if (major_payload_version_ == kBrilloMajorPayloadVersion) {
    partitions_.clear();
    for (const PartitionUpdate& partition : manifest_.partitions()) {
      partitions_.push_back(partition);
    }
    manifest_.clear_partitions();
  } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
    LOG(INFO) << "Converting update information from old format.";
    PartitionUpdate root_part;
    root_part.set_partition_name(kPartitionNameRoot);
#ifdef __ANDROID__
    LOG(WARNING) << "Legacy payload major version provided to an Android "
                    "build. Assuming no post-install. Please use major version "
                    "2 or newer.";
    root_part.set_run_postinstall(false);
#else
    root_part.set_run_postinstall(true);
#endif  // __ANDROID__
    if (manifest_.has_old_rootfs_info()) {
      *root_part.mutable_old_partition_info() = manifest_.old_rootfs_info();
      manifest_.clear_old_rootfs_info();
    }
    if (manifest_.has_new_rootfs_info()) {
      *root_part.mutable_new_partition_info() = manifest_.new_rootfs_info();
      manifest_.clear_new_rootfs_info();
    }
    *root_part.mutable_operations() = manifest_.install_operations();
    manifest_.clear_install_operations();
    partitions_.push_back(std::move(root_part));

    PartitionUpdate kern_part;
    kern_part.set_partition_name(kPartitionNameKernel);
    kern_part.set_run_postinstall(false);
    if (manifest_.has_old_kernel_info()) {
      *kern_part.mutable_old_partition_info() = manifest_.old_kernel_info();
      manifest_.clear_old_kernel_info();
    }
    if (manifest_.has_new_kernel_info()) {
      *kern_part.mutable_new_partition_info() = manifest_.new_kernel_info();
      manifest_.clear_new_kernel_info();
    }
    *kern_part.mutable_operations() = manifest_.kernel_install_operations();
    manifest_.clear_kernel_install_operations();
    partitions_.push_back(std::move(kern_part));
  }

--------------------------------------------------------------------------------------------------------------------------------
遍历 partitions_ 集合,将每一个 PartitionUpdate 对象数据填充到 InstallPlan::Partition 对象,初始化 InstallPlan::Partition 对象完毕后,将对象保存到 install_plan_.partitions 集合中。完成安装计划中的分区数据。
  // Fill in the InstallPlan::partitions based on the partitions from the
  // payload.
  for (const auto& partition : partitions_) {
    InstallPlan::Partition install_part;
    install_part.name = partition.partition_name();
    install_part.run_postinstall =
        partition.has_run_postinstall() && partition.run_postinstall();
    if (install_part.run_postinstall) {
      install_part.postinstall_path =
          (partition.has_postinstall_path() ? partition.postinstall_path()
                                            : kPostinstallDefaultScript);
      install_part.filesystem_type = partition.filesystem_type();
      install_part.postinstall_optional = partition.postinstall_optional();
    }

    // 是否包含旧分区信息,如果包含,保存就分区大小和 hash 值为 source_size 和 source_hash
    if (partition.has_old_partition_info()) {
      const PartitionInfo& info = partition.old_partition_info();
      install_part.source_size = info.size();
      install_part.source_hash.assign(info.hash().begin(), info.hash().end());
    }

    // 如果分区信息中没有新分区相关信息,那么是有问题的,此时需要反馈异常,升级包存在问题,没有新分区的长度、hash 值信息。
    if (!partition.has_new_partition_info()) {
      LOG(ERROR) << "Unable to get new partition hash info on partition "
                 << install_part.name << ".";
      *error = ErrorCode::kDownloadNewPartitionInfoError;
      return false;
    }
    // 获取新分区相关信息,保存新分区长度和 hash 值到 target_size 和 target_hash
    const PartitionInfo& info = partition.new_partition_info();
    install_part.target_size = info.size();
    install_part.target_hash.assign(info.hash().begin(), info.hash().end());

    install_part.block_size = block_size_;
    if (partition.has_hash_tree_extent()) {
      Extent extent = partition.hash_tree_data_extent();
      install_part.hash_tree_data_offset = extent.start_block() * block_size_;
      install_part.hash_tree_data_size = extent.num_blocks() * block_size_;
      extent = partition.hash_tree_extent();
      install_part.hash_tree_offset = extent.start_block() * block_size_;
      install_part.hash_tree_size = extent.num_blocks() * block_size_;
      uint64_t hash_tree_data_end =
          install_part.hash_tree_data_offset + install_part.hash_tree_data_size;
      if (install_part.hash_tree_offset < hash_tree_data_end) {
        LOG(ERROR) << "Invalid hash tree extents, hash tree data ends at "
                   << hash_tree_data_end << ", but hash tree starts at "
                   << install_part.hash_tree_offset;
        *error = ErrorCode::kDownloadNewPartitionInfoError;
        return false;
      }
      install_part.hash_tree_algorithm = partition.hash_tree_algorithm();
      install_part.hash_tree_salt.assign(partition.hash_tree_salt().begin(),
                                         partition.hash_tree_salt().end());
    }
    if (partition.has_fec_extent()) {
      Extent extent = partition.fec_data_extent();
      install_part.fec_data_offset = extent.start_block() * block_size_;
      install_part.fec_data_size = extent.num_blocks() * block_size_;
      extent = partition.fec_extent();
      install_part.fec_offset = extent.start_block() * block_size_;
      install_part.fec_size = extent.num_blocks() * block_size_;
      uint64_t fec_data_end =
          install_part.fec_data_offset + install_part.fec_data_size;
      if (install_part.fec_offset < fec_data_end) {
        LOG(ERROR) << "Invalid fec extents, fec data ends at " << fec_data_end
                   << ", but fec starts at " << install_part.fec_offset;
        *error = ErrorCode::kDownloadNewPartitionInfoError;
        return false;
      }
      install_part.fec_roots = partition.fec_roots();
    }

    // 一个 install_part 代表一个分区信息对象,将 install_part 保存到列表中
    install_plan_->partitions.push_back(install_part);
  }

  if (install_plan_->target_slot != BootControlInterface::kInvalidSlot) {
    // 这里调用了 boot_control_->InitPartitionMetadata() 函数
    if (!InitPartitionMetadata()) {
      *error = ErrorCode::kInstallDeviceOpenError;
      return false;
    }
  }

  // 这里主要是调用了 boot_control->GetPartitionDevice() 函数
  if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) {
    LOG(ERROR) << "Unable to determine all the partition devices.";
    *error = ErrorCode::kInstallDeviceOpenError;
    return false;
  }
  // 打印所有要处理的分区信息
  LogPartitionInfo(partitions_);
  return true;
}
    
--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
此函数功能是打开当前分区,注意此函数没有参数,表明当前有全局变量表示当前的分区对象。

bool DeltaPerformer::OpenCurrentPartition() {
  // 如果当前的分区下标已经超过了 partitions_ 分区集合的长度,那么当前分区也就没有了,return。
  if (current_partition_ >= partitions_.size())
    return false;
  
--------------------------------------------------------------------------------------------------------------------------------
// partition 是 PartitionUpdate 数据类型的
// install_part 是 InstallPlan::Partition 数据类型的
// 两者都可以用来描述 partition 分区信息

  // 从 partitions_ 集合中取出当前分区。
  const PartitionUpdate& partition = partitions_[current_partition_];
  // install_plan_->partitions 是一个集合,存储的是所有分区的信息,数据内部结构体 partition 定义在 install_plan.h 中
  // partitions_ 是 manifest_.partitions 的数据,是通过 PayloadMetadata::GetManifest() 函数从数据包中获取的
  size_t num_previous_partitions = 
      install_plan_->partitions.size() - partitions_.size();
    
  const InstallPlan::Partition& install_part =
      install_plan_->partitions[num_previous_partitions + current_partition_];
    
--------------------------------------------------------------------------------------------------------------------------------
// 下面的分区打开包含 2 种情况,对于差分增量升级,需要打开 source_path 分区,表示为旧分区,保存到 source_fd 中。
// 打开 target_path 分区,保存到 target_fd 中。日志表示为 “Opening /dev/block/by-name/dtbo_b partition without O_DSYNC”
// 分区的打开通过 OpenFile() 函数实现,内部调用 FileDescriptorPtr() 函数实现。

  // Open source fds if we have a delta payload with minor version >= 2.
  if (payload_->type == InstallPayloadType::kDelta &&
      GetMinorVersion() != kInPlaceMinorPayloadVersion &&
      // With dynamic partitions we could create a new partition in a
      // delta payload, and we shouldn't open source partition in that case.
      install_part.source_size > 0) {
    source_path_ = install_part.source_path;
    int err;
    source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, false, &err);
    if (!source_fd_) {
      LOG(ERROR) << "Unable to open source partition "
                 << partition.partition_name() << " on slot "
                 << BootControlInterface::SlotName(install_plan_->source_slot)
                 << ", file " << source_path_;
      return false; 
    }
  }
    
  target_path_ = install_part.target_path;
  int err;
  
  int flags = O_RDWR;
  if (!interactive_)
    flags |= O_DSYNC;
    
  LOG(INFO) << "Opening " << target_path_ << " partition with"
            << (interactive_ ? "out" : "") << " O_DSYNC";
            
  target_fd_ = OpenFile(target_path_.c_str(), flags, true, &err);
  if (!target_fd_) {
    LOG(ERROR) << "Unable to open target partition "
               << partition.partition_name() << " on slot "
               << BootControlInterface::SlotName(install_plan_->target_slot)
               << ", file " << target_path_;
    return false; 
  }

  LOG(INFO) << "Applying " << partition.operations().size()
            << " operations to partition \"" << partition.partition_name()
            << "\"";
            
  // Discard the end of the partition, but ignore failures.
  // 从函数解释来看,此函数的作用是将 fd 的 target_size 到文件末尾的数据删除,此处的理解为对于新分区长度的重置,新分区的长度已经固定了,此时需要将旧分区的超出部分删除,以确保新分区的长度。
  DiscardPartitionTail(target_fd_, install_part.target_size);
  
  return true;
}
    
--------------------------------------------------------------------------------------------------------------------------------
在分区操作 operation 数据写入时,会通过 InstallOperation 的 Type 类型,执行不同的操作。这里以 TYPE::MOVE 类型举例,调用 performMoveOperation() 函数完成数据的写入
    
bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation) {
  // Calculate buffer size. Note, this function doesn't do a sliding
  // window to copy in case the source and destination blocks overlap.
  // If we wanted to do a sliding window, we could program the server
  // to generate deltas that effectively did a sliding window.

  // blocks_to_read 标识操作数据的 blocks 数量
  uint64_t blocks_to_read = 0;
  for (int i = 0; i < operation.src_extents_size(); i++)
    blocks_to_read += operation.src_extents(i).num_blocks();

  // blocks_to_write 标识 dts 附加数据的 blocks 数量
  uint64_t blocks_to_write = 0;
  for (int i = 0; i < operation.dst_extents_size(); i++)
    blocks_to_write += operation.dst_extents(i).num_blocks();

  DCHECK_EQ(blocks_to_write, blocks_to_read);
  // 初始化一块 buffer 区域,用于保存要写入的数据
  brillo::Blob buf(blocks_to_write * block_size_);

  // Read in bytes.
  // 从 target_fd 分区中读取数据出来,保存到 buf[] 中。读取的长度保存在 bytes_read 变量中。
  ssize_t bytes_read = 0;
  for (int i = 0; i < operation.src_extents_size(); i++) {
    ssize_t bytes_read_this_iteration = 0;
    const Extent& extent = operation.src_extents(i);
    const size_t bytes = extent.num_blocks() * block_size_;
    TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
    // utils::PReadAll() 函数是一个工具函数,用于从文件描述符中读取所有数据并返回。它的功能是将文件描述符中的数据全部读取到一个字符串中,并返回该字符串。
    TEST_AND_RETURN_FALSE(utils::PReadAll(target_fd_,
                                          &buf[bytes_read],
                                          bytes,
                                          extent.start_block() * block_size_,
                                          &bytes_read_this_iteration));
    TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
                          static_cast<ssize_t>(bytes));
    bytes_read += bytes_read_this_iteration;
  }

  // Write bytes out.
  // 将数据从 buf[] 数据中写入到 target_fd 分区中。写入的数据长度保存到 bytes_written 变量中。
  ssize_t bytes_written = 0;
  for (int i = 0; i < operation.dst_extents_size(); i++) {
    const Extent& extent = operation.dst_extents(i);
    const size_t bytes = extent.num_blocks() * block_size_;
    TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
    // utils::PWriteAll() 函数是一个工具函数,用于将数据写入到文件描述符中。
    TEST_AND_RETURN_FALSE(utils::PWriteAll(target_fd_,
                                           &buf[bytes_written],
                                           bytes,
                                           extent.start_block() * block_size_));
    bytes_written += bytes;
  }
  DCHECK_EQ(bytes_written, bytes_read);
  DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.size()));
  return true;
}

4 filesystem_verifier_action

在前面的步骤中,Download Action 已经将分区数据载入到车机分区中,更新完毕后需要对分区数据进行校验,确保数据的正确性和完整性。

filesystem_verifier_action 定义在 “android/system/update_engine/payload_consumer/filesystem_verifier_action.h” 文件中,实现在同路径下的 “filesystem_verifier_action.cc” 中。

我们先来简单看一下 filesystem_verifier_action.h 的定义,了解此 Action 的简要功能

// The step FilesystemVerifier is on. On kVerifyTargetHash it computes the hash
// on the target partitions based on the already populated size and verifies it
// matches the one in the target_hash in the InstallPlan.
// If the hash matches, then we skip the kVerifySourceHash step, otherwise we
// need to check if the source is the root cause of the mismatch.
/*
 * 在 FilesystemVerifier 文件中打开,在 kVerifyTargetHash 上,它根据已填充的大小计算目标分区上的哈希,并验证它是否与安装计划中target_hash中的哈希相
 * 匹配。如果哈希匹配,那么我们跳过 kVerifySourceHash 步骤,否则我们需要检查源是否是不匹配的根本原因。
 * 就是说此 Action 会先计算已填充的目标分区上的哈希,对比 manifest 中记录的 target_hash,确保写入数据的完整性,如果能够匹配,表明升级数据已经完全写入,
 * 跳过对 source 源数据的校验,如果不能匹配,表明写入数据有问题,则需要对 source 源数据进行校验以判断原因。
 */
enum class VerifierStep {
  kVerifyTargetHash,
  kVerifySourceHash,
};

class FilesystemVerifierAction : public InstallPlanAction {
 public:
  FilesystemVerifierAction()
      : verity_writer_(verity_writer::CreateVerityWriter()) {}
  ~FilesystemVerifierAction() override = default;

  // Action 的入口函数
  void PerformAction() override;
  // Action 的结束函数
  void TerminateProcessing() override;

  // Debugging/logging
  static std::string StaticType() { return "FilesystemVerifierAction"; }
  std::string Type() const override { return StaticType(); }

 private:
  friend class FilesystemVerifierActionTestDelegate;
  // Starts the hashing of the current partition. If there aren't any partitions
  // remaining to be hashed, it finishes the action.
  // 启动当前分区的哈希处理,如果没有剩余要处理的分区,结束运行
  void StartPartitionHashing();

  // Schedules the asynchronous read of the filesystem.
  // 对于文件系统的异步读取
  void ScheduleRead();

  // Called from the main loop when a single read from |src_stream_| succeeds or
  // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively.
  // 读取分区数据成功或失败的回调。用于通知分区数据的读取情况
  void OnReadDoneCallback(size_t bytes_read);
  void OnReadErrorCallback(const brillo::Error* error);

  // When the read is done, finalize the hash checking of the current partition
  // and continue checking the next one.
  // 完成当前分区的哈希检查并继续检查下一个分区
  void FinishPartitionHashing();

  // Cleans up all the variables we use for async operations and tells the
  // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
  // true if TerminateProcessing() was called.
  // 清理用于异步操作的所有变量,并告诉 ActionProcessor 已经完成了code 作为传入。如果需要调用 TerminateProcessing(),则 cancelled 应该为 true。
  void Cleanup(ErrorCode code);

  // The type of the partition that we are verifying.
  // 表示当前验证的分区类型,是 target 分区还是 source 分区
  VerifierStep verifier_step_ = VerifierStep::kVerifyTargetHash;

  // The index in the install_plan_.partitions vector of the partition currently
  // being hashed.
  // 当前正在散列的分区的 install_plan.partitions 向量中的索引
  size_t partition_index_{0};

  // If not null, the FileStream used to read from the device.
  // 如果不为空,则用于从设备读取数据的文件流
  brillo::StreamPtr src_stream_;

  // Buffer for storing data we read.
  // 用于存储我们读取的数据的缓冲区
  brillo::Blob buffer_;

  bool cancelled_{false};  // true if the action has been cancelled. 如果 action 操作被取消则为 true

  // The install plan we're passed in via the input pipe.
  // 通过管道中传入的安装计划
  InstallPlan install_plan_;

  // Calculates the hash of the data.
  // 计算数据的哈希值
  std::unique_ptr<HashCalculator> hasher_;

  // Write verity data of the current partition.
  // 写入当前分区的验证数据
  std::unique_ptr<VerityWriterInterface> verity_writer_;

  // Reads and hashes this many bytes from the head of the input stream. When
  // the partition starts to be hashed, this field is initialized from the
  // corresponding InstallPlan::Partition size which is the total size
  // update_engine is expected to write, and may be smaller than the size of the
  // partition in gpt.
  // 从输入流前面读取和哈希计算许多字节,当分区开始散列时,此字段从相应的 InstallPlan::Partition size 初始化,这是 update_engine 预期写入的总大小,并且可能小于 gpt 中分区的大小。
  uint64_t partition_size_{0};

  // The byte offset that we are reading in the current partition.
  // 我们在当前分区中读取字节的偏移量
  uint64_t offset_{0};

  DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
};

4.1 PerformAction

PerformAction() 函数是 Action 的入口,我们来分析下 Filesystem Verifier Action 的功能入口函数。

代码路径:android/system/update_engine/payload_consumer/filesystem_verifier_action.cc

void FilesystemVerifierAction::PerformAction() {
  // Will tell the ActionProcessor we've failed if we return.
  ScopedActionCompleter abort_action_completer(processor_, this);

  if (!HasInputObject()) {
    LOG(ERROR) << "FilesystemVerifierAction missing input object.";
    return;
  }
  // 获取数据管道中的 InputObject InstallPlan 对象
  install_plan_ = GetInputObject();

  if (install_plan_.partitions.empty()) {
    LOG(INFO) << "No partitions to verify.";
    if (HasOutputPipe())
      SetOutputObject(install_plan_);
    abort_action_completer.set_code(ErrorCode::kSuccess);
    return;
  }

  // PerformAction() 函数主要调用 StartPartitionHashing() 去完成分区的校验
  StartPartitionHashing();
  abort_action_completer.set_should_complete(false);
}

4.2 StartPartitionHashing

代码路径:android/system/update_engine/payload_consumer/filesystem_verifier_action.cc

void FilesystemVerifierAction::StartPartitionHashing() {

  // 此判断用于校验分区是否检查完毕,partition_index_ 标识当前分区下标,install_plan.partitions.size() 是所有分区的数量
  // 当 partition_index_ 等于 install_plan.partitions.size() 的时候,表明所有的分区均已校验完毕。应该 return 校验结果
  // StartPartitionHashing() 函数会循环调用,因此这里需要有判断结束的逻辑
  if (partition_index_ == install_plan_.partitions.size()) {
    Cleanup(ErrorCode::kSuccess);
    return;
  }
  // 获取 Partition 结构体。partition_index_ 的初始值为 0
  const InstallPlan::Partition& partition =
      install_plan_.partitions[partition_index_];

  string part_path;
  // 这里判断是校验分区的类型,是 target 分区还是 source 分区。verifier_step_ 初始值是 Target Hash。
  switch (verifier_step_) {
    case VerifierStep::kVerifySourceHash:
      part_path = partition.source_path;
      partition_size_ = partition.source_size;
      break;
    case VerifierStep::kVerifyTargetHash:
      part_path = partition.target_path;
      partition_size_ = partition.target_size;
      break;
  }

  // 对于分区路径为空的情况判断。
  if (part_path.empty()) {
    // 如果分区大小为0,说明此分区没有数据,跳过此分区的检测,partition_index_ 下标索引 +1,并且调用本函数执行下一个分区的校验
    if (partition_size_ == 0) {
      LOG(INFO) << "Skip hashing partition " << partition_index_ << " ("
                << partition.name << ") because size is 0.";
      partition_index_++;
      StartPartitionHashing();
      return;
    }
    // 如果分区大小不为0,说明分区异常,没有分区路径。
    LOG(ERROR) << "Cannot hash partition " << partition_index_ << " ("
               << partition.name
               << ") because its device path cannot be determined.";
    Cleanup(ErrorCode::kFilesystemVerifierError);
    return;
  }

  // 将当前分区信息日志打印出来,例如
  // Hashing partition 0 (dtbo) on device /dev/block/by-name/dtbo_b
  // Hashing partition 1 (boot) on device /dev/block/by-name/boot_b
  LOG(INFO) << "Hashing partition " << partition_index_ << " ("
            << partition.name << ") on device " << part_path;

  brillo::ErrorPtr error;
  // 使用 FileStream 打开当前设备的分区文件。当前分区数据在 Download 阶段已经写入到设备中。
  src_stream_ =
      brillo::FileStream::Open(base::FilePath(part_path),
                               brillo::Stream::AccessMode::READ,
                               brillo::FileStream::Disposition::OPEN_EXISTING,
                               &error);

  // 判断打开失败的异常情况
  if (!src_stream_) {
    LOG(ERROR) << "Unable to open " << part_path << " for reading";
    Cleanup(ErrorCode::kFilesystemVerifierError);
    return;
  }

  // 重设 buffer 的长度为 128k。const off_t kReadFileBufferSize = 128 * 1024
  buffer_.resize(kReadFileBufferSize);
  // 初始化 HashCalculator 对象 hasher_,准备进行 Hash 计算。
  hasher_ = std::make_unique<HashCalculator>();

  offset_ = 0;
  // write_verity 默认为 true
  if (verifier_step_ == VerifierStep::kVerifyTargetHash &&
      install_plan_.write_verity) {
    // verity_writter_->Init() 函数是计算 Manifest 中保存的 partition 分区的 Hash 值
    if (!verity_writer_->Init(partition)) {
      Cleanup(ErrorCode::kVerityCalculationError);
      return;
    }
  }

  // Start the first read.
  ScheduleRead();
}

4.3 ScheduleRead

代码路径:android/system/update_engine/payload_consumer/filesystem_verifier_action.cc

void FilesystemVerifierAction::ScheduleRead() {
  const InstallPlan::Partition& partition =
      install_plan_.partitions[partition_index_];

  // We can only start reading anything past |hash_tree_offset| after we have
  // already read all the data blocks that the hash tree covers. The same
  // applies to FEC.
  // read_end 标识读取结束位置,读分区大小的数据
  uint64_t read_end = partition_size_;
  if (partition.hash_tree_size != 0 &&
      offset_ < partition.hash_tree_data_offset + partition.hash_tree_data_size)
    read_end = std::min(read_end, partition.hash_tree_offset);
  if (partition.fec_size != 0 &&
      offset_ < partition.fec_data_offset + partition.fec_data_size)
    read_end = std::min(read_end, partition.fec_offset);
  // bytes_to_read 标识需要读取的数据的长度
  size_t bytes_to_read =
      std::min(static_cast<uint64_t>(buffer_.size()), read_end - offset_);
  if (!bytes_to_read) {
    FinishPartitionHashing();
    return;
  }

  // 通过 src_stream_->ReadAsync() 函数读取设备分区 bytes_to_read 长度的数据保存到 buffer_ 中
  // 读取成功回调 OnReadDoneCallback,读取失败回调 OnReadErrorCallback
  bool read_async_ok = src_stream_->ReadAsync(
      buffer_.data(),
      bytes_to_read,
      base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
                 base::Unretained(this)),
      base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
                 base::Unretained(this)),
      nullptr);

  if (!read_async_ok) {
    LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
    Cleanup(ErrorCode::kError);
  }
}

4.4 OnReadDoneCallback

代码路径:android/system/update_engine/payload_consumer/filesystem_verifier_action.cc

void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
  if (cancelled_) {
    Cleanup(ErrorCode::kError);
    return;
  }

  // bytes_read 表示读取字节的长度
  if (bytes_read == 0) {
    LOG(ERROR) << "Failed to read the remaining " << partition_size_ - offset_
               << " bytes from partition "
               << install_plan_.partitions[partition_index_].name;
    Cleanup(ErrorCode::kFilesystemVerifierError);
    return;
  }

  // 计算设备分区内数据的 Hash 值(持续计算)
  if (!hasher_->Update(buffer_.data(), bytes_read)) {
    LOG(ERROR) << "Unable to update the hash.";
    Cleanup(ErrorCode::kError);
    return;
  }

  if (verifier_step_ == VerifierStep::kVerifyTargetHash &&
      install_plan_.write_verity) {
    if (!verity_writer_->Update(offset_, buffer_.data(), bytes_read)) {
      Cleanup(ErrorCode::kVerityCalculationError);
      return;
    }
  }

  // 更新读取位置
  offset_ += bytes_read;

  // 分区数据读取完毕之后,调用 FinishPartitionHashing() 函数
  if (offset_ == partition_size_) {
    FinishPartitionHashing();
    return;
  }

  // 如果没有计算完成,继续读取计算
  ScheduleRead();
}

4.5 OnReadErrorCallback

代码路径:android/system/update_engine/payload_consumer/filesystem_verifier_action.cc

// 读取失败返回异常码
void FilesystemVerifierAction::OnReadErrorCallback(const brillo::Error* error) {
  // TODO(deymo): Transform the read-error into an specific ErrorCode.
  LOG(ERROR) << "Asynchronous read failed.";
  Cleanup(ErrorCode::kError);
}

4.6 FinishPartitionHashing

代码路径:android/system/update_engine/payload_consumer/filesystem_verifier_action.cc

// 分区 Hash 计算完毕后调用
void FilesystemVerifierAction::FinishPartitionHashing() {
  // 判断 hasher_ 是否完成
  if (!hasher_->Finalize()) {
    LOG(ERROR) << "Unable to finalize the hash.";
    Cleanup(ErrorCode::kError);
    return;
  }
  InstallPlan::Partition& partition =
      install_plan_.partitions[partition_index_];
  // 打印 Manifest 中的分区信息,例如
  // Hash of dtbo: PzWyx0IY+E3UNOSlurGoopOoABPl/YdxpdGgPtorAOI=
  LOG(INFO) << "Hash of " << partition.name << ": "
            << Base64Encode(hasher_->raw_hash());

  switch (verifier_step_) {
    case VerifierStep::kVerifyTargetHash:
      // 如果 Manifest 中记录的 分区 Hash 值跟计算出来的设备中分区的 HASH 值不一致
      if (partition.target_hash != hasher_->raw_hash()) {
        LOG(ERROR) << "New '" << partition.name
                   << "' partition verification failed.";
        if (partition.source_hash.empty()) {
          // No need to verify source if it is a full payload.
          Cleanup(ErrorCode::kNewRootfsVerificationError);
          return;
        }
        // If we have not verified source partition yet, now that the target
        // partition does not match, and it's not a full payload, we need to
        // switch to kVerifySourceHash step to check if it's because the source
        // partition does not match either.
        // 计算 Source Hash 校验是否是 Source 源分区问题
        verifier_step_ = VerifierStep::kVerifySourceHash;
      } else {
        // 如果 Manifest 中记录的 分区 Hash 值跟计算出来的设备中分区的 Hash 值一致,则校验成功,进行下一个分区的校验
        partition_index_++;
      }
      break;
    case VerifierStep::kVerifySourceHash:
      if (partition.source_hash != hasher_->raw_hash()) {
        LOG(ERROR) << "Old '" << partition.name
                   << "' partition verification failed.";
        LOG(ERROR) << "This is a server-side error due to mismatched delta"
                   << " update image!";
        LOG(ERROR) << "The delta I've been given contains a " << partition.name
                   << " delta update that must be applied over a "
                   << partition.name << " with a specific checksum, but the "
                   << partition.name
                   << " we're starting with doesn't have that checksum! This"
                      " means that the delta I've been given doesn't match my"
                      " existing system. The "
                   << partition.name << " partition I have has hash: "
                   << Base64Encode(hasher_->raw_hash())
                   << " but the update expected me to have "
                   << Base64Encode(partition.source_hash) << " .";
        LOG(INFO) << "To get the checksum of the " << partition.name
                  << " partition run this command: dd if="
                  << partition.source_path
                  << " bs=1M count=" << partition.source_size
                  << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 "
                     "-binary | openssl base64";
        LOG(INFO) << "To get the checksum of partitions in a bin file, "
                  << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
        Cleanup(ErrorCode::kDownloadStateInitializationError);
        return;
      }
      // The action will skip kVerifySourceHash step if target partition hash
      // matches, if we are in this step, it means target hash does not match,
      // and now that the source partition hash matches, we should set the error
      // code to reflect the error in target partition.
      // We only need to verify the source partition which the target hash does
      // not match, the rest of the partitions don't matter.
      Cleanup(ErrorCode::kNewRootfsVerificationError);
      return;
  }
  // Start hashing the next partition, if any.
  // 对 Hash 计算所用到的对象、buffer、文件流的重置
  hasher_.reset();
  buffer_.clear();
  src_stream_->CloseBlocking(nullptr);
  // 继续进行分区的校验
  StartPartitionHashing();
}

FilesystemVerifierAction 大致流程如上,遍历所有需要升级的分区的 Hash 值,先读取更新分区的 hash 值,与升级包中存放的升级后的分区 hash 值做对比,如果没有问题,那么继续进行下一个分区的校验,直到结束。如果匹配失败,说明分区写入数据异常,升级异常。会去进行 Source 分区的校验排查是否是源分区异常导致,Source 分区的对比不能改变 Target 分区对比失败的结果,如果 Target 分区正常,则不会校验 Source 分区。

5 postinstall_runner_action

下面来分析下 Action 流程中的最后一步,PostinstallRunnerAction。

我们先来简要了解下 PostinstallRunnerAction 中的定义

路径:android/system/update_engine/payload_consumer/postinstall_runner_action.h

class PostinstallRunnerAction : public InstallPlanAction {
 public:
  PostinstallRunnerAction(BootControlInterface* boot_control,
                          HardwareInterface* hardware)
      : boot_control_(boot_control), hardware_(hardware) {}

--------------------------------------------------------------------------------------------------------------------------------
// 下面重写的方法是控制 Action 的运行流程。

  // InstallPlanAction overrides.
  void PerformAction() override;
  void SuspendAction() override;
  void ResumeAction() override;
  void TerminateProcessing() override;

--------------------------------------------------------------------------------------------------------------------------------

  class DelegateInterface {
   public:
    virtual ~DelegateInterface() = default;

    // Called whenever there is an overall progress update from the postinstall
    // programs.
    virtual void ProgressUpdate(double progress) = 0;
  };

  void set_delegate(DelegateInterface* delegate) { delegate_ = delegate; }

  // Debugging/logging
  static std::string StaticType() { return "PostinstallRunnerAction"; }
  std::string Type() const override { return StaticType(); }

--------------------------------------------------------------------------------------------------------------------------------

 private:
  friend class PostinstallRunnerActionTest;
  FRIEND_TEST(PostinstallRunnerActionTest, ProcessProgressLineTest);

  void PerformPartitionPostinstall();

  // Called whenever the |progress_fd_| has data available to read.
  // 当 progress_fd_ 存在有效数据可供读取时被调用
  void OnProgressFdReady();

  // Updates the action progress according to the |line| passed from the
  // postinstall program. Valid lines are:
  //     global_progress <frac>
  //         <frac> should be between 0.0 and 1.0; sets the progress to the
  //         <frac> value.
  // 根据 line 参数更新操作进度,line 有效值应介于 0.0 到 1.0 之间
  bool ProcessProgressLine(const std::string& line);

  // Report the progress to the delegate given that the postinstall operation
  // for |current_partition_| has a current progress of |frac|, a value between
  // 0 and 1 for that step.
  // 想代理报告进度。进度 frac 介于 0 和 1 之间
  void ReportProgress(double frac);

  // Cleanup the setup made when running postinstall for a given partition.
  // Unmount and remove the mountpoint directory if needed and cleanup the
  // status file descriptor and message loop task watching for it.
  // 清理在为给定分区 postinstall 后配置时所做的设置。如果需要,写在并删除挂载点和目录,并清理状态文件描述符和监视它的消息循环任务
  void Cleanup();

  // Subprocess::Exec callback.
  // 子进程,执行回调
  void CompletePartitionPostinstall(int return_code, const std::string& output);

  // Complete the Action with the passed |error_code| and mark the new slot as
  // ready. Called when the post-install script was run for all the partitions.
  // 使用传递的 error_code 完成操作并将新插槽标记为就绪。为所有分区运行 postinstall 脚本时调用
  void CompletePostinstall(ErrorCode error_code);

  InstallPlan install_plan_;

  // The path where the filesystem will be mounted during post-install.
  // 安装后将挂载文件系统的路径
  std::string fs_mount_dir_;

  // The partition being processed on the list of partitions specified in the
  // InstallPlan.
  // 在安装计划中指定的分区列表中正在处理的分区
  size_t current_partition_{0};

  // A non-negative value representing the estimated weight of each partition
  // passed in the install plan. The weight is used to predict the overall
  // progress from the individual progress of each partition and should
  // correspond to the time it takes to run it.
  // 标识 InstallPlan 中传递的每个分区的权重。权重用于根据每个分区的各个进度预测总进度,并且对应于分区运行所需的时间
  std::vector<double> partition_weight_;

  // The sum of all the weights in |partition_weight_|.
  // partition_weight_ 中所有权重的总和
  double total_weight_{0};

  // The sum of all the weights in |partition_weight_| up to but not including
  // the |current_partition_|.
  // partition_weight_ 中所有权重的总和,最多但不包括 current_partition_
  double accumulated_weight_{0};

  // The delegate used to notify of progress updates, if any.
  // 用于通知进度更新的对象
  DelegateInterface* delegate_{nullptr};

  // The BootControlInerface used to mark the new slot as ready.
  // 用于将新 Slot 标记为就绪的 BootControlInterface 对象
  BootControlInterface* boot_control_;

  // HardwareInterface used to signal powerwash.
  // 用于发出信号电源清洗的硬件接口
  HardwareInterface* hardware_;

  // Whether the Powerwash was scheduled before invoking post-install script.
  // Used for cleaning up if post-install fails.
  // 是否在调用 PostInstall 脚本之前进行 Powerwash。用于在安装失败后进行清理
  bool powerwash_scheduled_{false};

  // Postinstall command currently running, or 0 if no program running.
  // 当前正在执行的 PostInstall 命令。如果没有运行,则为 0
  pid_t current_command_{0};

  // True if |current_command_| has been suspended by SuspendAction().
  // 如果 current_command 已经被暂停,则为 true
  bool is_current_command_suspended_{false};

  // The parent progress file descriptor used to watch for progress reports from
  // the postinstall program and the task watching for them.
  // 父进度文件描述符,用于监视来自 PostInstall 的进度报告已经监视它们的任务
  int progress_fd_{-1};
  brillo::MessageLoop::TaskId progress_task_{brillo::MessageLoop::kTaskIdNull};

  // A buffer of a partial read line from the progress file descriptor.
  // 进度文件描述符中读取部分行的缓冲区
  std::string progress_buffer_;

  DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
};

5.1 PerformAction

下面简单过一下 PostInstallRunnerAction 的入口函数

路径:android/system/update_engine/payload_consumer/postinstall_runner_action.cc

void PostinstallRunnerAction::PerformAction() {
  // 获取输入管道中的输入对象 InstallPlan
  CHECK(HasInputObject());
  install_plan_ = GetInputObject();

  // Currently we're always powerwashing when rolling back.
  // 是否需要进行数据的擦除。当我们在进行回滚的时候。
  // powerwash_required 默认为 false。is_rollback 默认为 false。
  if (install_plan_.powerwash_required || install_plan_.is_rollback) {
    if (hardware_->SchedulePowerwash(install_plan_.is_rollback)) {
      powerwash_scheduled_ = true;
    } else {
      return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
    }
  }

  // Initialize all the partition weights.
  // partition_weight_ 重设长度为 Manifest 中分区的数量
  partition_weight_.resize(install_plan_.partitions.size());
  // 总长度初始化为 0
  total_weight_ = 0;
  // 遍历分区,获取分区是否携带带有安装后程序的我呢见系统,必须运行改程序才能完成更新过程。对应保存到 partition_weight_ 中,并更新总长度
  for (size_t i = 0; i < install_plan_.partitions.size(); ++i) {
    // TODO(deymo): This code sets the weight to all the postinstall commands,
    // but we could remember how long they took in the past and use those
    // values.
    // run_postinstall 是记录在 Manifest 中,用于标识分区是否携带有 PostInstall 程序。必须运行程序才能完成更新过程
    // 相关参数还有 postinstall_path 和 filesystem_type
    partition_weight_[i] = install_plan_.partitions[i].run_postinstall;
    total_weight_ += partition_weight_[i];
  }
  accumulated_weight_ = 0;
  // 报告进度
  ReportProgress(0);

  // PerformPartitionPostinstall() 函数是真正执行的过程
  PerformPartitionPostinstall();
}

5.2 PerformPartitionPostinstall

路径:android/system/update_engine/payload_consumer/postinstall_runner_action.cc

void PostinstallRunnerAction::PerformPartitionPostinstall() {
  // run_post_install 默认为 true
  if (!install_plan_.run_post_install) {
    LOG(INFO) << "Skipping post-install according to install plan.";
    return CompletePostinstall(ErrorCode::kSuccess);
  }

  if (install_plan_.download_url.empty()) {
    LOG(INFO) << "Skipping post-install during rollback";
    return CompletePostinstall(ErrorCode::kSuccess);
  }

  // Skip all the partitions that don't have a post-install step.
  // 跳过所有没有 postinstall 阶段的分区
  while (current_partition_ < install_plan_.partitions.size() &&
         !install_plan_.partitions[current_partition_].run_postinstall) {
    VLOG(1) << "Skipping post-install on partition "
            << install_plan_.partitions[current_partition_].name;
    current_partition_++;
  }
  if (current_partition_ == install_plan_.partitions.size())
    return CompletePostinstall(ErrorCode::kSuccess);

  // 获取当前需要进行 postinstall 步骤的分区
  const InstallPlan::Partition& partition =
      install_plan_.partitions[current_partition_];

  // 传入一个分区设备,挂载改设备,返回一个适合挂载的名称,出错时返回空字符串
  const string mountable_device =
      utils::MakePartitionNameForMount(partition.target_path);
  if (mountable_device.empty()) {
    LOG(ERROR) << "Cannot make mountable device from " << partition.target_path;
    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
  }

  // Perform post-install for the current_partition_ partition. At this point we
  // need to call CompletePartitionPostinstall to complete the operation and
  // cleanup.
  // 对当前分区进行 postinstall 配置
#ifdef __ANDROID__
  fs_mount_dir_ = "/postinstall";
#else   // __ANDROID__
  base::FilePath temp_dir;
  TEST_AND_RETURN(base::CreateNewTempDirectory("au_postint_mount", &temp_dir));
  fs_mount_dir_ = temp_dir.value();
#endif  // __ANDROID__

  // Double check that the fs_mount_dir is not busy with a previous mounted
  // filesystem from a previous crashed postinstall step.
  if (utils::IsMountpoint(fs_mount_dir_)) {
    LOG(INFO) << "Found previously mounted filesystem at " << fs_mount_dir_;
    utils::UnmountFilesystem(fs_mount_dir_);
  }

  base::FilePath postinstall_path(partition.postinstall_path);
  if (postinstall_path.IsAbsolute()) {
    LOG(ERROR) << "Invalid absolute path passed to postinstall, use a relative"
                  "path instead: "
               << partition.postinstall_path;
    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
  }

  string abs_path =
      base::FilePath(fs_mount_dir_).Append(postinstall_path).value();
  if (!base::StartsWith(
          abs_path, fs_mount_dir_, base::CompareCase::SENSITIVE)) {
    LOG(ERROR) << "Invalid relative postinstall path: "
               << partition.postinstall_path;
    return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
  }

#ifdef __ANDROID__
  // In Chromium OS, the postinstall step is allowed to write to the block
  // device on the target image, so we don't mark it as read-only and should
  // be read-write since we just wrote to it during the update.

  // Mark the block device as read-only before mounting for post-install.
  if (!utils::SetBlockDeviceReadOnly(mountable_device, true)) {
    return CompletePartitionPostinstall(
        1, "Error marking the device " + mountable_device + " read only.");
  }
#endif  // __ANDROID__

  // 调用 MountFilesystem() 函数同步挂载文件系统,成功后返回 true。挂载时,它将尝试将设备挂载为传递的文件系统类型 type,并传递 flag 选项
  // 如果 type 为空,将尝试 "ext2"、"ext3"、"ext4" 和 "squashfs"
  if (!utils::MountFilesystem(mountable_device,
                              fs_mount_dir_,
                              MS_RDONLY,
                              partition.filesystem_type,
                              constants::kPostinstallMountOptions)) {
    return CompletePartitionPostinstall(
        1, "Error mounting the device " + mountable_device);
  }

  /* 日志如下:
   * Performing postinst (system/bin/otapreopt_script at /postinstall/system/bin/otapreopt_script) installed on device /dev/block/by-name/system_b and mountable device /dev/block/by-name/system_b
   */
  LOG(INFO) << "Performing postinst (" << partition.postinstall_path << " at "
            << abs_path << ") installed on device " << partition.target_path
            << " and mountable device " << mountable_device;

  // Logs the file format of the postinstall script we are about to run. This
  // will help debug when the postinstall script doesn't match the architecture
  // of our build.
  LOG(INFO) << "Format file for new " << partition.postinstall_path
            << " is: " << utils::GetFileFormat(abs_path);

  // Runs the postinstall script asynchronously to free up the main loop while
  // it's running.
  // 异步运行 postinstall 脚本,以便在主循环运行时释放主循环
  vector<string> command = {abs_path};
#ifdef __ANDROID__
  // In Brillo and Android, we pass the slot number and status fd.
  command.push_back(std::to_string(install_plan_.target_slot));
  command.push_back(std::to_string(kPostinstallStatusFd));
#else
  // Chrome OS postinstall expects the target rootfs as the first parameter.
  command.push_back(partition.target_path);
#endif  // __ANDROID__

  // 异步执行当前命令
  current_command_ = Subprocess::Get().ExecFlags(
      command,
      Subprocess::kRedirectStderrToStdout,
      {kPostinstallStatusFd},
      base::Bind(&PostinstallRunnerAction::CompletePartitionPostinstall,
                 base::Unretained(this)));
  // Subprocess::Exec should never return a negative process id.
  CHECK_GE(current_command_, 0);

  if (!current_command_) {
    CompletePartitionPostinstall(1, "Postinstall didn't launch");
    return;
  }

  // Monitor the status file descriptor.
  // 监视状态文件描述符
  progress_fd_ =
      Subprocess::Get().GetPipeFd(current_command_, kPostinstallStatusFd);
  int fd_flags = fcntl(progress_fd_, F_GETFL, 0) | O_NONBLOCK;
  if (HANDLE_EINTR(fcntl(progress_fd_, F_SETFL, fd_flags)) < 0) {
    PLOG(ERROR) << "Unable to set non-blocking I/O mode on fd " << progress_fd_;
  }

  progress_task_ = MessageLoop::current()->WatchFileDescriptor(
      FROM_HERE,
      progress_fd_,
      MessageLoop::WatchMode::kWatchRead,
      true,
      base::Bind(&PostinstallRunnerAction::OnProgressFdReady,
                 base::Unretained(this)));
}

5.3 CompletePostinstall

CompletePostinstall 函数主要是在所有分区的 postinstall 脚本都执行完毕之后,将新 Slot 标记为 active。

路径:android/system/update_engine/payload_consumer/postinstall_runner_action.cc

void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
  // We only attempt to mark the new slot as active if all the postinstall
  // steps succeeded.
  // 当所有安装后步骤都成功执行时,我们才会将新 Slot 标记为 active
  if (error_code == ErrorCode::kSuccess) {
    if (install_plan_.switch_slot_on_reboot) {
      if (!boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {
        error_code = ErrorCode::kPostinstallRunnerError;
      }
    } else {
      error_code = ErrorCode::kUpdatedButNotActive;
    }
  }

  ScopedActionCompleter completer(processor_, this);
  completer.set_code(error_code);

  if (error_code != ErrorCode::kSuccess &&
      error_code != ErrorCode::kUpdatedButNotActive) {
    LOG(ERROR) << "Postinstall action failed.";

    // Undo any changes done to trigger Powerwash.
    if (powerwash_scheduled_)
      hardware_->CancelPowerwash();

    return;
  }

  LOG(INFO) << "All post-install commands succeeded";
  if (HasOutputPipe()) {
    SetOutputObject(install_plan_);
  }
}

到这里基本上已经完成所有 UpdateEngine 升级过程中所有 Action 的流程。这里做个简要总结:

  • InstallPlanAction

    构建一个 InstallPlan 对象参数 install_plan_ 并作为输出参数传递给 DownloadAction

  • DownloadAction

    获取输入对象 InstallPlan,构建 http_fetcher_ 用于数据下载,并同步将数据通过 DeltaPerform 对象的 write() 方法将数据更新到对应分区

  • FilesystemVerifierAction

    循环遍历校验需要升级的分区。将更新到分区的数据 Hash 值与 Manifest 中记录的 Hash 值对比

  • PostinstallRunnerAction

    对升级后分区进行 postinstall 脚本,并将升级成功后的分区设置为可启动 active 状态

;
#else
// Chrome OS postinstall expects the target rootfs as the first parameter.
command.push_back(partition.target_path);
#endif // ANDROID

// 异步执行当前命令
current_command_ = Subprocess::Get().ExecFlags(
command,
Subprocess::kRedirectStderrToStdout,
{kPostinstallStatusFd},
base::Bind(&PostinstallRunnerAction::CompletePartitionPostinstall,
base::Unretained(this)));
// Subprocess::Exec should never return a negative process id.
CHECK_GE(current_command_, 0);

if (!current_command_) {
CompletePartitionPostinstall(1, “Postinstall didn’t launch”);
return;
}

// Monitor the status file descriptor.
// 监视状态文件描述符
progress_fd_ =
Subprocess::Get().GetPipeFd(current_command_, kPostinstallStatusFd);
int fd_flags = fcntl(progress_fd_, F_GETFL, 0) | O_NONBLOCK;
if (HANDLE_EINTR(fcntl(progress_fd_, F_SETFL, fd_flags)) < 0) {
PLOG(ERROR) << "Unable to set non-blocking I/O mode on fd " << progress_fd_;
}

progress_task_ = MessageLoop::current()->WatchFileDescriptor(
FROM_HERE,
progress_fd_,
MessageLoop::WatchMode::kWatchRead,
true,
base::Bind(&PostinstallRunnerAction::OnProgressFdReady,
base::Unretained(this)));
}


### 5.3 CompletePostinstall

CompletePostinstall 函数主要是在所有分区的 postinstall 脚本都执行完毕之后,将新 Slot 标记为 active。

```cpp
路径:android/system/update_engine/payload_consumer/postinstall_runner_action.cc

void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
  // We only attempt to mark the new slot as active if all the postinstall
  // steps succeeded.
  // 当所有安装后步骤都成功执行时,我们才会将新 Slot 标记为 active
  if (error_code == ErrorCode::kSuccess) {
    if (install_plan_.switch_slot_on_reboot) {
      if (!boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {
        error_code = ErrorCode::kPostinstallRunnerError;
      }
    } else {
      error_code = ErrorCode::kUpdatedButNotActive;
    }
  }

  ScopedActionCompleter completer(processor_, this);
  completer.set_code(error_code);

  if (error_code != ErrorCode::kSuccess &&
      error_code != ErrorCode::kUpdatedButNotActive) {
    LOG(ERROR) << "Postinstall action failed.";

    // Undo any changes done to trigger Powerwash.
    if (powerwash_scheduled_)
      hardware_->CancelPowerwash();

    return;
  }

  LOG(INFO) << "All post-install commands succeeded";
  if (HasOutputPipe()) {
    SetOutputObject(install_plan_);
  }
}

到这里基本上已经完成所有 UpdateEngine 升级过程中所有 Action 的流程。这里做个简要总结:

  • InstallPlanAction

    构建一个 InstallPlan 对象参数 install_plan_ 并作为输出参数传递给 DownloadAction

  • DownloadAction

    获取输入对象 InstallPlan,构建 http_fetcher_ 用于数据下载,并同步将数据通过 DeltaPerform 对象的 write() 方法将数据更新到对应分区

  • FilesystemVerifierAction

    循环遍历校验需要升级的分区。将更新到分区的数据 Hash 值与 Manifest 中记录的 Hash 值对比

  • PostinstallRunnerAction

    对升级后分区进行 postinstall 脚本,并将升级成功后的分区设置为可启动 active 状态

以上是 UpdateEngine 升级过程中的基本执行过程。对于函数大致运行过程已有分析,但是对于细节部分没有详细展开。欢迎大家补充指点。

猜你喜欢

转载自blog.csdn.net/Yang_Mao_Shan/article/details/131638005