前言
上一篇分析了 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等。
下面分析 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 升级过程中的基本执行过程。对于函数大致运行过程已有分析,但是对于细节部分没有详细展开。欢迎大家补充指点。