update_engine-DownloadAction(二)


在update_engine-DownloadAction(一)中對DownloadAction介紹到了DeltaPerformer的Write方法。下面開始介紹Write方法。

src/system/update_engine/payload_consumer/delta_performer.cc

  1 bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode *error) { 2   *error = ErrorCode::kSuccess; 3 
  4   const char* c_bytes = reinterpret_cast<const char*>(bytes); 5 
  6   // Update the total byte downloaded count and the progress logs.
  7   total_bytes_received_ += count; 8   UpdateOverallProgress(false, "Completed ");       //更新進度包括了已經應用的操作數,下載的數據量,以及總的進度 
  9 
 10   while (!manifest_valid_) {                     //manifest_valid_的初始值為false
 11     // Read data up to the needed limit; this is either maximium payload header
 12     // size, or the full metadata size (once it becomes known).
 13     const bool do_read_header = !IsHeaderParsed();      //是否解析過Header
 14     CopyDataToBuffer(&c_bytes, &count,                  //將數據拷貝到緩存區server中
 15                      (do_read_header ? kMaxPayloadHeaderSize : 16                       metadata_size_ + metadata_signature_size_)); 17 
 18     MetadataParseResult result = ParsePayloadMetadata(buffer_, error);  //解析元數據
 19     if (result == kMetadataParseError) 20       return false; 21     if (result == kMetadataParseInsufficientData) { 22       // If we just processed the header, make an attempt on the manifest.
 23       if (do_read_header && IsHeaderParsed()) 24         continue; 25 
 26       return true; 27 } 28 
 29     // Checks the integrity of the payload manifest.
 30     if ((*error = ValidateManifest()) != ErrorCode::kSuccess)     //驗證Manifest
 31       return false; 32     manifest_valid_ = true; 33 
 34     // Clear the download buffer.
 35     DiscardBuffer(false, metadata_size_);                 //清除緩存區
 36 
 37     // This populates |partitions_| and the |install_plan.partitions| with the
 38     // list of partitions from the manifest.
 39     if (!ParseManifestPartitions(error))                 //解析Manifest中的Partitions的信息
 40       return false; 41 
 42     // |install_plan.partitions| was filled in, nothing need to be done here if
 43     // the payload was already applied, returns false to terminate http fetcher,
 44     // but keep |error| as ErrorCode::kSuccess.
 45     if (payload_->already_applied)                       //檢查當前payload_是否已經被應用
 46       return false; 47 
 48     num_total_operations_ = 0; 49     for (const auto& partition : partitions_) { 50       num_total_operations_ += partition.operations_size();   //計算總的操作數
 51       acc_num_operations_.push_back(num_total_operations_);    //將每次計算的操作數放入到集合中,這樣做的意義有能夠根據操作數來判斷是哪個
 52     }                                                          //分區要進行操作,以及是該分區的第幾個操作
 53 
 54     LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestMetadataSize, 55 metadata_size_)) 56         << "Unable to save the manifest metadata size."; 57     LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestSignatureSize, 58 metadata_signature_size_)) 59         << "Unable to save the manifest signature size."; 60 
 61     if (!PrimeUpdateState()) {                                     /更新主要的狀態,包含了block_size_,next_operation_num等 62       *error = ErrorCode::kDownloadStateInitializationError; 63       LOG(ERROR) << "Unable to prime the update state."; 64       return false; 65 } 66 
 67     if (!OpenCurrentPartition()) {                       //打開當前的分區,包括source_slot和target_slot的,為升級做准備
 68       *error = ErrorCode::kInstallDeviceOpenError; 69       return false; 70 } 71 
 72     if (next_operation_num_ > 0) 73       UpdateOverallProgress(true, "Resuming after "); 74     LOG(INFO) << "Starting to apply update payload operations"; 75 } 76 
 77   while (next_operation_num_ < num_total_operations_) {             //開始進行更新
 78     // Check if we should cancel the current attempt for any reason.
 79     // In this case, *error will have already been populated with the reason
 80     // why we're canceling.
 81     if (download_delegate_ && download_delegate_->ShouldCancel(error))  //目前什么都沒做,直接返回了false
 82       return false; 83 
 84     // We know there are more operations to perform because we didn't reach the
 85     // |num_total_operations_| limit yet.
 86     while (next_operation_num_ >= acc_num_operations_[current_partition_]) { //說明了當前分區已經更新完成,需要更新下一個分區
 87       CloseCurrentPartition();   //關閉當前分區
 88       current_partition_++;      //切換到下一個分區
 89       if (!OpenCurrentPartition()) {  //打開
 90         *error = ErrorCode::kInstallDeviceOpenError; 91         return false; 92 } 93 } 94     const size_t partition_operation_num = next_operation_num_ - ( 95         current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0);  //計算出當前分區將要應用的操作數
 96 
 97     const InstallOperation& op =
 98         partitions_[current_partition_].operations(partition_operation_num);  //獲取到操作的類型
 99 
100     CopyDataToBuffer(&c_bytes, &count, op.data_length());   //將該操作對應的數據放到緩存區中
101 
102     // Check whether we received all of the next operation's data payload.
103     if (!CanPerformInstallOperation(op))            //驗證該操作是否能夠進行,主要就是看該操作對應的數據是否已經全部都下載完了
104       return true; 105 
106     // Validate the operation only if the metadata signature is present.
107     // Otherwise, keep the old behavior. This serves as a knob to disable
108     // the validation logic in case we find some regression after rollout.
109     // NOTE: If hash checks are mandatory and if metadata_signature is empty,
110     // we would have already failed in ParsePayloadMetadata method and thus not
111     // even be here. So no need to handle that case again here.
112     if (!payload_->metadata_signature.empty()) { 113       // Note: Validate must be called only if CanPerformInstallOperation is
114       // called. Otherwise, we might be failing operations before even if there
115       // isn't sufficient data to compute the proper hash.
116       *error = ValidateOperationHash(op);         //校驗操作對應數據的hash值是否正確
117       if (*error != ErrorCode::kSuccess) { 118         if (install_plan_->hash_checks_mandatory) { 119           LOG(ERROR) << "Mandatory operation hash check failed"; 120           return false; 121 } 122 
123         // For non-mandatory cases, just send a UMA stat.
124         LOG(WARNING) << "Ignoring operation validation errors"; 125         *error = ErrorCode::kSuccess; 126 } 127 } 128 
129     // Makes sure we unblock exit when this operation completes.
130     ScopedTerminatorExitUnblocker exit_unblocker =
131         ScopedTerminatorExitUnblocker();  // Avoids a compiler unused var bug.
132 
133     bool op_result; 134     switch (op.type()) {          //根據操作的類型執行對應的操作
135       case InstallOperation::REPLACE: 136       case InstallOperation::REPLACE_BZ: 137       case InstallOperation::REPLACE_XZ: 138         op_result = PerformReplaceOperation(op); 139         break; 140       case InstallOperation::ZERO: 141       case InstallOperation::DISCARD: 142         op_result = PerformZeroOrDiscardOperation(op); 143         break; 144       case InstallOperation::MOVE: 145         op_result = PerformMoveOperation(op); 146         break; 147       case InstallOperation::BSDIFF: 148         op_result = PerformBsdiffOperation(op); 149         break; 150       case InstallOperation::SOURCE_COPY: 151         op_result = PerformSourceCopyOperation(op, error); 152         break; 153       case InstallOperation::SOURCE_BSDIFF: 154         op_result = PerformSourceBsdiffOperation(op, error); 155         break; 156       case InstallOperation::IMGDIFF: 157         // TODO(deymo): Replace with PUFFIN operation.
158         op_result = false; 159         break; 160       default: 161         op_result = false; 162 } 163     if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error))  //對處理結果進行打印
164       return false; 165 
166     next_operation_num_++; 167     UpdateOverallProgress(false, "Completed "); 168     CheckpointUpdateProgress();                //保存更新進度,類似於斷點能夠進行保存
169 } 170 
171   // In major version 2, we don't add dummy operation to the payload.
172   // If we already extracted the signature we should skip this step. 

173   if (major_payload_version_ == kBrilloMajorPayloadVersion &&                 
174       manifest_.has_signatures_offset() && manifest_.has_signatures_size() &&
175 signatures_message_data_.empty()) { 176     if (manifest_.signatures_offset() != buffer_offset_) { 177       LOG(ERROR) << "Payload signatures offset points to blob offset "
178                  << manifest_.signatures_offset() 179                  << " but signatures are expected at offset "
180                  << buffer_offset_; 181       *error = ErrorCode::kDownloadPayloadVerificationError; 182       return false; 183 } 184     CopyDataToBuffer(&c_bytes, &count, manifest_.signatures_size()); 185     // Needs more data to cover entire signature.
186     if (buffer_.size() < manifest_.signatures_size()) 187       return true; 188     if (!ExtractSignatureMessage()) {                             //獲取升級文件中數據區域的簽名
189       LOG(ERROR) << "Extract payload signature failed."; 190       *error = ErrorCode::kDownloadPayloadVerificationError; 191       return false; 192 } 193     DiscardBuffer(true, 0); 194     // Since we extracted the SignatureMessage we need to advance the
195     // checkpoint, otherwise we would reload the signature and try to extract
196     // it again.
197 CheckpointUpdateProgress(); 198 } 199 
200   return true; 201 }

 這個方法乍一看上去內容特別的多,而且如果對升級文件沒有一個了解的情況下分析這段代碼會有一點點困難,但是當跨過這個困難的時候就會對升級文件的結構有一個了解。要想了解升級文件的結構可以直接分析升級文件,但是在android引入A/B升級之后,升級文件是純二進制的文件,而且還被加了密。分析起來難度也比較大,當然我們也可以分析代碼中是如何解析的,根據解析我們就能夠獲取到升級文件的結構。另外在A/B升級中,它應用更新的流程就是下載->解析->驗證->應用。這里的下載指的就是將數據加載到內存中,並且是邊下載邊更新,更新完之后就會把數據從內存中移除。下面是分析代碼所得到升級文件的結構。

升級文件的結構

magic:是用於校驗數據在內存中的地址偏移量是否正確。假設我們預期從地址0到3存放A,B,C,D可是當計算存在問題時,應該得到0的時候我們得到的是1,那么就會在1到4存放A,B,C,D,而我們再從0開始訪問就會有問題。如果在內存的開始部分加入一個magic,當我們從0開始訪問的時候,就先根據定義好的magic判斷數據在內存中是否發生偏移錯誤。也就存放數據的時候我們存放magic,A,B,C,D正確的結果是0到4,但是卻放到了1到5,這個時候我們依然去用0開始訪問,但是我們首先會檢驗在0上的magic和預期的一樣,如果一樣則說明沒有發生偏移錯誤,可以繼續訪問,如果不一樣則說明偏移錯誤,之后可以進行相應的處理。

delta version:是差分版本,也就是update_engine的版本號

manifest_size: 代表manifest的大小,manifest意為清單文件,系統如何升級也是根據manifest來做的。

metadata_signaturesize:代表了元數據簽名的大小。可以將magic,dleta version,manifest_size,metadata_signaturesize以及manifest[]稱為元數據。

manifest[]: 主要的清單文件,記錄了各個分區的更新的操作,以及數據信息等

metadata_signaturesize_message : 元數據的簽名,而且也是經過加密的

data: 用於更新的數據

data_messgage:為data的簽名信息。在kBrilloMajorPayloadVersion 這個版本中才會有。在A/B更新出現后,一共出現了兩個版本一個kChromeOSMajorPayloadVersion一個kBrilloMajorPayloadVersion,kBrilloMajorPayloadVersion這個版本為新版本,也是Android8.0中使用的。

當有了這些了解后再來分析Write方法是就會簡單很多。在Write中主要做的事情為:

1. ParsePayloadMetadata解析元數據。來看一下是如何解析的。

  1 DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata( 2     const brillo::Blob& payload, ErrorCode* error) { 3   *error = ErrorCode::kSuccess; 4 uint64_t manifest_offset; 5 
  6   if (!IsHeaderParsed()) {       //沒有解析過
  7     // Ensure we have data to cover the major payload version.
  8     if (payload.size() < kDeltaManifestSizeOffset)   //kDeltaManifestSizeOffset=kDeltaVersionOffset + kDeltaVersionSize
  9       return kMetadataParseInsufficientData;      //沒有將magic和delta version加載完 
 10 
 11     // Validate the magic string.
 12     if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) {   //校驗magic,
 13       LOG(ERROR) << "Bad payload format -- invalid delta magic."; 14       *error = ErrorCode::kDownloadInvalidMetadataMagicString; 15       return kMetadataParseError; 16 } 17 
 18     // Extract the payload version from the metadata.
 19     static_assert(sizeof(major_payload_version_) == kDeltaVersionSize, 20                   "Major payload version size mismatch"); 21     memcpy(&major_payload_version_, 22            &payload[kDeltaVersionOffset],     //保存DeltaVesion
 23 kDeltaVersionSize); 24     // switch big endian to host
 25     major_payload_version_ = be64toh(major_payload_version_);   //轉換為主機字節序
 26 
 27     if (major_payload_version_ != supported_major_version_ &&         //判斷版本號是否正確
 28         major_payload_version_ != kChromeOSMajorPayloadVersion) { 29       LOG(ERROR) << "Bad payload format -- unsupported payload version: "
 30           << major_payload_version_; 31       *error = ErrorCode::kUnsupportedMajorPayloadVersion; 32       return kMetadataParseError; 33 } 34 
 35     // Get the manifest offset now that we have payload version.
 36     if (!GetManifestOffset(&manifest_offset)) {                   //獲取指向manifest的地址偏移量
 37       *error = ErrorCode::kUnsupportedMajorPayloadVersion; 38       return kMetadataParseError; 39 } 40     // Check again with the manifest offset.
 41     if (payload.size() < manifest_offset)           //判斷manifset之前的數據是否都已經加載到了內存中
 42       return kMetadataParseInsufficientData; 43 
 44     // Next, parse the manifest size.
 45     static_assert(sizeof(manifest_size_) == kDeltaManifestSizeSize, 46                   "manifest_size size mismatch"); 47     memcpy(&manifest_size_,                       //保存manifest的大小
 48            &payload[kDeltaManifestSizeOffset], 49 kDeltaManifestSizeSize); 50     manifest_size_ = be64toh(manifest_size_);  // 轉換為主機字節序
 51 
 52     if (GetMajorVersion() == kBrilloMajorPayloadVersion) {      //如果是新版本
 53       // Parse the metadata signature size.
 54       static_assert(sizeof(metadata_signature_size_) ==
 55 kDeltaMetadataSignatureSizeSize, 56                     "metadata_signature_size size mismatch"); 57 uint64_t metadata_signature_size_offset; 58       if (!GetMetadataSignatureSizeOffset(&metadata_signature_size_offset)) { //獲取metadata_signature_size數據的偏移量
 59         *error = ErrorCode::kError; 60         return kMetadataParseError; 61 } 62       memcpy(&metadata_signature_size_,                                //保存元數據的大小
 63              &payload[metadata_signature_size_offset], 64 kDeltaMetadataSignatureSizeSize); 65       metadata_signature_size_ = be32toh(metadata_signature_size_);    //轉換為主機字節序
 66 } 67 
 68     // If the metadata size is present in install plan, check for it immediately
 69     // even before waiting for that many number of bytes to be downloaded in the
 70     // payload. This will prevent any attack which relies on us downloading data
 71     // beyond the expected metadata size.
 72     metadata_size_ = manifest_offset + manifest_size_;                //計算元數據的大小
 73     if (install_plan_->hash_checks_mandatory) {            //進行強制性檢查,增加安全性
 74       if (payload_->metadata_size != metadata_size_) { 75         LOG(ERROR) << "Mandatory metadata size in Omaha response ("
 76                    << payload_->metadata_size 77                    << ") is missing/incorrect, actual = " << metadata_size_; 78         *error = ErrorCode::kDownloadInvalidMetadataSize; 79         return kMetadataParseError; 80 } 81 } 82 } 83 
 84   // Now that we have validated the metadata size, we should wait for the full
 85   // metadata and its signature (if exist) to be read in before we can parse it.
 86   if (payload.size() < metadata_size_ + metadata_signature_size_)    //檢查metadata_signature_message是否已經加載到了內存
 87     return kMetadataParseInsufficientData; 88 
 89   // Log whether we validated the size or simply trusting what's in the payload
 90   // here. This is logged here (after we received the full metadata data) so
 91   // that we just log once (instead of logging n times) if it takes n
 92   // DeltaPerformer::Write calls to download the full manifest.
 93   if (payload_->metadata_size == metadata_size_) {   //payload_中也保存了metadata_size,進行比對一下
 94     LOG(INFO) << "Manifest size in payload matches expected value from Omaha"; 95   } else { 96     // For mandatory-cases, we'd have already returned a kMetadataParseError
 97     // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
 98     LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
 99                  << payload_->metadata_size 100                  << ") in Omaha response as validation is not mandatory. "
101                  << "Trusting metadata size in payload = " << metadata_size_; 102 } 103 
104   // We have the full metadata in |payload|. Verify its integrity
105   // and authenticity based on the information we have in Omaha response.
106   *error = ValidateMetadataSignature(payload);    //驗證元數據的簽名
107   if (*error != ErrorCode::kSuccess) { 108     if (install_plan_->hash_checks_mandatory) { 109       // The autoupdate_CatchBadSignatures test checks for this string
110       // in log-files. Keep in sync.
111       LOG(ERROR) << "Mandatory metadata signature validation failed"; 112       return kMetadataParseError; 113 } 114 
115     // For non-mandatory cases, just send a UMA stat.
116     LOG(WARNING) << "Ignoring metadata signature validation failures"; 117     *error = ErrorCode::kSuccess; 118 } 119 
120   if (!GetManifestOffset(&manifest_offset)) {   //獲取manifest_offset
121     *error = ErrorCode::kUnsupportedMajorPayloadVersion; 122     return kMetadataParseError; 123 } 124   // The payload metadata is deemed valid, it's safe to parse the protobuf.
125   if (!manifest_.ParseFromArray(&payload[manifest_offset], manifest_size_)) {    //解析manifest
126     LOG(ERROR) << "Unable to parse manifest in update file."; 127     *error = ErrorCode::kDownloadManifestParseError; 128     return kMetadataParseError; 129 } 130 
131   manifest_parsed_ = true; 132   return kMetadataParseSuccess; 133 }

可以看到整個解析的過程也比較簡單了。接下來着重看一下ValidateMetadataSignature的實現

 1 ErrorCode DeltaPerformer::ValidateMetadataSignature( 2     const brillo::Blob& payload) { 3   if (payload.size() < metadata_size_ + metadata_signature_size_) 4     return ErrorCode::kDownloadMetadataSignatureError;             //判斷簽名是否已經加載到了內存中
 5 
 6 brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob; 7   if (!payload_->metadata_signature.empty()) {   //payload_中已經保存了metadata_signature
 8     // Convert base64-encoded signature to raw bytes.
 9     if (!brillo::data_encoding::Base64Decode(payload_->metadata_signature, 10                                              &metadata_signature_blob)) {  //先對簽名進行Base64的簡碼
11       LOG(ERROR) << "Unable to decode base64 metadata signature: "
12                  << payload_->metadata_signature; 13       return ErrorCode::kDownloadMetadataSignatureError; 14 } 15   } else if (major_payload_version_ == kBrilloMajorPayloadVersion) { 16     metadata_signature_protobuf_blob.assign(             //沒有保存就從內存中加載
17         payload.begin() + metadata_size_, 18         payload.begin() + metadata_size_ + metadata_signature_size_); 19 } 20 
21   if (metadata_signature_blob.empty() &&
22       metadata_signature_protobuf_blob.empty()) {   //沒有metadata_signature
23     if (install_plan_->hash_checks_mandatory) { 24       LOG(ERROR) << "Missing mandatory metadata signature in both Omaha "
25                  << "response and payload."; 26       return ErrorCode::kDownloadMetadataSignatureMissingError; 27 } 28 
29     LOG(WARNING) << "Cannot validate metadata as the signature is empty"; 30     return ErrorCode::kSuccess; 31 } 32 
33   // See if we should use the public RSA key in the Omaha response.
34   base::FilePath path_to_public_key(public_key_path_); 35   base::FilePath tmp_key; 36   if (GetPublicKeyFromResponse(&tmp_key))   //檢查install_plan_中是否已經帶了公鑰
37     path_to_public_key = tmp_key; 38 ScopedPathUnlinker tmp_key_remover(tmp_key.value()); 39   if (tmp_key.empty()) 40     tmp_key_remover.set_should_remove(false); 41 
42   LOG(INFO) << "Verifying metadata hash signature using public key: "
43             << path_to_public_key.value(); 44 
45 brillo::Blob calculated_metadata_hash; 46   if (!HashCalculator::RawHashOfBytes(                   //根據元數據計算一個hash
47           payload.data(), metadata_size_, &calculated_metadata_hash)) { 48     LOG(ERROR) << "Unable to compute actual hash of manifest"; 49     return ErrorCode::kDownloadMetadataSignatureVerificationError; 50 } 51 
52   PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);  //對hash進行填充
53   if (calculated_metadata_hash.empty()) { 54     LOG(ERROR) << "Computed actual hash of metadata is empty."; 55     return ErrorCode::kDownloadMetadataSignatureVerificationError; 56 } 57 
58   if (!metadata_signature_blob.empty()) {        //payload_中已經保存了簽名 
59 brillo::Blob expected_metadata_hash; 60     if (!PayloadVerifier::GetRawHashFromSignature(metadata_signature_blob,   //使用公鑰對其進行解密
61 path_to_public_key.value(), 62                                                   &expected_metadata_hash)) { 63       LOG(ERROR) << "Unable to compute expected hash from metadata signature"; 64       return ErrorCode::kDownloadMetadataSignatureError; 65 } 66     if (calculated_metadata_hash != expected_metadata_hash) {         //判斷保存的和自己算出來的簽名是否相同
67       LOG(ERROR) << "Manifest hash verification failed. Expected hash = "; 68 utils::HexDumpVector(expected_metadata_hash); 69       LOG(ERROR) << "Calculated hash = "; 70 utils::HexDumpVector(calculated_metadata_hash); 71       return ErrorCode::kDownloadMetadataSignatureMismatch; 72 } 73   } else {   //在升級數據中含有簽名信息時,對簽名的校驗
74     if (!PayloadVerifier::VerifySignature(metadata_signature_protobuf_blob, 75 path_to_public_key.value(), 76 calculated_metadata_hash)) { 77       LOG(ERROR) << "Manifest hash verification failed."; 78       return ErrorCode::kDownloadMetadataSignatureMismatch; 79 } 80 } 81 
82   // The autoupdate_CatchBadSignatures test checks for this string in
83   // log-files. Keep in sync.
84   LOG(INFO) << "Metadata hash signature matches value in Omaha response."; 85   return ErrorCode::kSuccess; 86 }

這個方法主要說明了metadata_signature簽名的驗證機制,其中有一個payload_,是在DeltaPerformer構造函數中賦的值。接下來在分析manifest_.ParseFromArray(&payload[manifest_offset], manifest_size_),在初看到這行代碼的時候,花了很長時間也沒有找到ParseFromArray的實現。DeltaArchiveManifest類也沒有找到對應的C++類,但是卻找到了update_metadata_pb2.py和update_metadata.proto。update_metadata.proto的內容如下

src/system/update_engine/update_metadata.proto

  1 message Extent { 2   optional uint64 start_block = 1; 3   optional uint64 num_blocks = 2; 4 } 5 
  6 message Signatures { 7 message Signature { 8     optional uint32 version = 1; 9     optional bytes data = 2; 10 } 11   repeated Signature signatures = 1; 12 } 13 
 14 message PartitionInfo { 15   optional uint64 size = 1; 16   optional bytes hash = 2; 17 } 18 
 19 // Describe an image we are based on in a human friendly way.
 20 // Examples:
 21 // dev-channel, x86-alex, 1.2.3, mp-v3
 22 // nplusone-channel, x86-alex, 1.2.4, mp-v3, dev-channel, 1.2.3
 23 //  24 // All fields will be set, if this message is present.
 25 message ImageInfo { 26   optional string board = 1; 27   optional string key = 2; 28   optional string channel = 3; 29   optional string version = 4; 30 
 31   // If these values aren't present, they should be assumed to match
 32   // the equivalent value above. They are normally only different for
 33   // special image types such as nplusone images.
 34   optional string build_channel = 5; 35   optional string build_version = 6; 36 } 37 
 38 message InstallOperation { 39   enum Type { 40     REPLACE = 0;  // Replace destination extents w/ attached data
 41     REPLACE_BZ = 1;  // Replace destination extents w/ attached bzipped data
 42     MOVE = 2;  // Move source extents to destination extents
 43     BSDIFF = 3;  // The data is a bsdiff binary diff
 44 
 45     // On minor version 2 or newer, these operations are supported:
 46     SOURCE_COPY = 4; // Copy from source to target partition
 47     SOURCE_BSDIFF = 5; // Like BSDIFF, but read from source partition
 48 
 49     // On minor version 3 or newer and on major version 2 or newer, these
 50     // operations are supported:
 51     ZERO = 6;  // Write zeros in the destination.
 52     DISCARD = 7;  // Discard the destination blocks, reading as undefined.
 53     REPLACE_XZ = 8; // Replace destination extents w/ attached xz data.
 54 
 55     // On minor version 4 or newer, these operations are supported:
 56     IMGDIFF = 9; // The data is in imgdiff format.
 57 } 58   required Type type = 1; 59   // The offset into the delta file (after the protobuf)
 60   // where the data (if any) is stored
 61   optional uint32 data_offset = 2; 62   // The length of the data in the delta file
 63   optional uint32 data_length = 3; 64 
 65   // Ordered list of extents that are read from (if any) and written to.
 66   repeated Extent src_extents = 4; 67   // Byte length of src, equal to the number of blocks in src_extents *
 68   // block_size. It is used for BSDIFF, because we need to pass that
 69   // external program the number of bytes to read from the blocks we pass it.
 70   // This is not used in any other operation.
 71   optional uint64 src_length = 5; 72 
 73   repeated Extent dst_extents = 6; 74   // Byte length of dst, equal to the number of blocks in dst_extents *
 75   // block_size. Used for BSDIFF, but not in any other operation.
 76   optional uint64 dst_length = 7; 77 
 78   // Optional SHA 256 hash of the blob associated with this operation.
 79   // This is used as a primary validation for http-based downloads and
 80   // as a defense-in-depth validation for https-based downloads. If
 81   // the operation doesn't refer to any blob, this field will have
 82   // zero bytes.
 83   optional bytes data_sha256_hash = 8; 84 
 85   // Indicates the SHA 256 hash of the source data referenced in src_extents at
 86   // the time of applying the operation. If present, the update_engine daemon
 87   // MUST read and verify the source data before applying the operation.
 88   optional bytes src_sha256_hash = 9; 89 } 90 
 91 // Describes the update to apply to a single partition.
 92 message PartitionUpdate { 93   // A platform-specific name to identify the partition set being updated. For
 94   // example, in Chrome OS this could be "ROOT" or "KERNEL".
 95   required string partition_name = 1; 96 
 97   // Whether this partition carries a filesystem with post-install program that
 98   // must be run to finalize the update process. See also |postinstall_path| and
 99   // |filesystem_type|.
100   optional bool run_postinstall = 2; 101 
102   // The path of the executable program to run during the post-install step,
103   // relative to the root of this filesystem. If not set, the default "postinst"
104   // will be used. This setting is only used when |run_postinstall| is set and
105   // true.
106   optional string postinstall_path = 3; 107 
108   // The filesystem type as passed to the mount(2) syscall when mounting the new
109   // filesystem to run the post-install program. If not set, a fixed list of
110   // filesystems will be attempted. This setting is only used if
111   // |run_postinstall| is set and true.
112   optional string filesystem_type = 4; 113 
114   // If present, a list of signatures of the new_partition_info.hash signed with
115   // different keys. If the update_engine daemon requires vendor-signed images
116   // and has its public key installed, one of the signatures should be valid
117   // for /postinstall to run.
118   repeated Signatures.Signature new_partition_signature = 5; 119 
120   optional PartitionInfo old_partition_info = 6; 121   optional PartitionInfo new_partition_info = 7; 122 
123   // The list of operations to be performed to apply this PartitionUpdate. The
124   // associated operation blobs (in operations[i].data_offset, data_length)
125   // should be stored contiguously and in the same order.
126   repeated InstallOperation operations = 8; 127 
128   // Whether a failure in the postinstall step for this partition should be
129   // ignored.
130   optional bool postinstall_optional = 9; 131 } 132 
133 message DeltaArchiveManifest { 134   // Only present in major version = 1. List of install operations for the
135   // kernel and rootfs partitions. For major version = 2 see the |partitions|
136   // field.
137   repeated InstallOperation install_operations = 1; 138   repeated InstallOperation kernel_install_operations = 2; 139 
140   // (At time of writing) usually 4096
141   optional uint32 block_size = 3 [default = 4096]; 142 
143   // If signatures are present, the offset into the blobs, generally
144   // tacked onto the end of the file, and the length. We use an offset
145   // rather than a bool to allow for more flexibility in future file formats.
146   // If either is absent, it means signatures aren't supported in this
147   // file.
148   optional uint64 signatures_offset = 4; 149   optional uint64 signatures_size = 5; 150 
151   // Only present in major version = 1. Partition metadata used to validate the
152   // update. For major version = 2 see the |partitions| field.
153   optional PartitionInfo old_kernel_info = 6; 154   optional PartitionInfo new_kernel_info = 7; 155   optional PartitionInfo old_rootfs_info = 8; 156   optional PartitionInfo new_rootfs_info = 9; 157 
158   // old_image_info will only be present for delta images.
159   optional ImageInfo old_image_info = 10; 160 
161   optional ImageInfo new_image_info = 11; 162 
163   // The minor version, also referred as "delta version", of the payload.
164   optional uint32 minor_version = 12 [default = 0]; 165 
166   // Only present in major version >= 2. List of partitions that will be
167   // updated, in the order they will be updated. This field replaces the
168   // |install_operations|, |kernel_install_operations| and the
169   // |{old,new}_{kernel,rootfs}_info| fields used in major version = 1. This
170   // array can have more than two partitions if needed, and they are identified
171   // by the partition name.
172   repeated PartitionUpdate partitions = 13; 173 
174   // The maximum timestamp of the OS allowed to apply this payload.
175   // Can be used to prevent downgrading the OS.
176   optional int64 max_timestamp = 14; 177 }

可以看出它應該就是由update_metadata_pb2.py這個腳本解析的manifest的數據格式。后來了解到這是Protobuf數據格式,是比xml和json更加高效的數據格式,采用了二進制的存儲。那么其實根據DeltaArchiveManifest我們就能大體推斷出Manifest中所包含的數據類型。主要就是安裝更新操作的類型,數據的簽名,新舊內核,rootfs,ImageInfo,分區更新等。到此ParsePayloadMetadata這個方法就算是分析完了。回到Write中繼續分析,當解析完成了Manifest之后,就調用了ValidateManifest()來驗證manifest。

2.ValidateManifest()來驗證manifest

 1 ErrorCode DeltaPerformer::ValidateManifest() { 2   // Perform assorted checks to sanity check the manifest, make sure it
 3   // matches data from other sources, and that it is a supported version.
 4 
 5   bool has_old_fields =
 6       (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info()); 7   for (const PartitionUpdate& partition : manifest_.partitions()) { 8     has_old_fields = has_old_fields || partition.has_old_partition_info(); 9 } 10 
11   // The presence of an old partition hash is the sole indicator for a delta
12   // update.
13   InstallPayloadType actual_payload_type =             
14       has_old_fields ? InstallPayloadType::kDelta : InstallPayloadType::kFull;    //獲取升級的類型
15 
16   if (payload_->type == InstallPayloadType::kUnknown) {          //payload_->type的默認值是KUnknown
17     LOG(INFO) << "Detected a '"
18               << InstallPayloadTypeToString(actual_payload_type) 19               << "' payload."; 20     payload_->type = actual_payload_type; 21   } else if (payload_->type != actual_payload_type) { 22     LOG(ERROR) << "InstallPlan expected a '"
23                << InstallPayloadTypeToString(payload_->type) 24                << "' payload but the downloaded manifest contains a '"
25                << InstallPayloadTypeToString(actual_payload_type) 26                << "' payload."; 27     return ErrorCode::kPayloadMismatchedType; 28 } 29 
30   // Check that the minor version is compatible.
31   if (actual_payload_type == InstallPayloadType::kFull) {         //進行更加安全性檢測
32     if (manifest_.minor_version() != kFullPayloadMinorVersion) { 33       LOG(ERROR) << "Manifest contains minor version "
34                  << manifest_.minor_version() 35                  << ", but all full payloads should have version "
36                  << kFullPayloadMinorVersion << "."; 37       return ErrorCode::kUnsupportedMinorPayloadVersion; 38 } 39   } else { 40     if (manifest_.minor_version() != supported_minor_version_) { 41       LOG(ERROR) << "Manifest contains minor version "
42                  << manifest_.minor_version() 43                  << " not the supported "
44                  << supported_minor_version_; 45       return ErrorCode::kUnsupportedMinorPayloadVersion; 46 } 47 } 48 
49   if (major_payload_version_ != kChromeOSMajorPayloadVersion) { 50     if (manifest_.has_old_rootfs_info() ||               //這些字段只應該在kChromeOSMajorPayloadVersion中有
51         manifest_.has_new_rootfs_info() ||
52         manifest_.has_old_kernel_info() ||
53         manifest_.has_new_kernel_info() ||
54         manifest_.install_operations_size() != 0 ||
55         manifest_.kernel_install_operations_size() != 0) { 56       LOG(ERROR) << "Manifest contains deprecated field only supported in "
57                  << "major payload version 1, but the payload major version is "
58                  << major_payload_version_; 59       return ErrorCode::kPayloadMismatchedType; 60 } 61 } 62 
63   if (manifest_.max_timestamp() < hardware_->GetBuildTimestamp()) {   //對時間戳的檢測
64     LOG(ERROR) << "The current OS build timestamp ("
65                << hardware_->GetBuildTimestamp() 66                << ") is newer than the maximum timestamp in the manifest ("
67                << manifest_.max_timestamp() << ")"; 68     return ErrorCode::kPayloadTimestampError; 69 } 70 
71   // TODO(garnold) we should be adding more and more manifest checks, such as
72   // partition boundaries etc (see chromium-os:37661).
73 
74   return ErrorCode::kSuccess; 75 }

這個方法主要驗證的了升級的類型,已經升級程序版本的正確性,最后對時間戳進行了一次校驗,理論上升級包中新版本的時間戳應該比系統中當前版本的時間戳更新一些,才允許升級。對manifest進行了校驗之后,在Write方法中標記manifest_valid_為true,清空緩存區后,開始對分區信息進行解析。

3.解析Manifest中的Partitions的信息

 1 bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) { 2   if (major_payload_version_ == kBrilloMajorPayloadVersion) { 3 partitions_.clear(); 4     for (const PartitionUpdate& partition : manifest_.partitions()) { 5       partitions_.push_back(partition);          //將partitons的信息保存到partitions中
 6 } 7     manifest_.clear_partitions();         //將manifest_中的分區信息進行刪除
 8   } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) { 9     LOG(INFO) << "Converting update information from old format."; 10     //這部分是老版本的在使用,就先不進行分析了
11 } 12 
13   // Fill in the InstallPlan::partitions based on the partitions from the
14   // payload.
15   for (const auto& partition : partitions_) { 16 InstallPlan::Partition install_part; 17     install_part.name = partition.partition_name();              //分區的name
18     install_part.run_postinstall =                                        //postinstall
19         partition.has_run_postinstall() && partition.run_postinstall(); 20     if (install_part.run_postinstall) { 21       install_part.postinstall_path =
22           (partition.has_postinstall_path() ? partition.postinstall_path() 23 : kPostinstallDefaultScript); 24       install_part.filesystem_type = partition.filesystem_type(); 25       install_part.postinstall_optional = partition.postinstall_optional(); 26 } 27 
28     if (partition.has_old_partition_info()) {      //獲取old 分區中的信息
29       const PartitionInfo& info = partition.old_partition_info(); 30       install_part.source_size = info.size(); 31 install_part.source_hash.assign(info.hash().begin(), info.hash().end()); 32 } 33 
34     if (!partition.has_new_partition_info()) { 35       LOG(ERROR) << "Unable to get new partition hash info on partition "
36                  << install_part.name << "."; 37       *error = ErrorCode::kDownloadNewPartitionInfoError; 38       return false; 39 } 40     const PartitionInfo& info = partition.new_partition_info(); 41     install_part.target_size = info.size();                     //新分區的信息
42 install_part.target_hash.assign(info.hash().begin(), info.hash().end()); 43 
44     install_plan_->partitions.push_back(install_part); //保存到install_plan_
45 } 46 
47   if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) {    //根據分區name,slot,獲取分區的路徑
48     LOG(ERROR) << "Unable to determine all the partition devices."; 49     *error = ErrorCode::kInstallDeviceOpenError; 50     return false; 51 } 52   LogPartitionInfo(partitions_);   //打印分區信息
53   return true; 54 }

其實解析分區主要就是將分區信息從manifest_中轉移到install_plan_。在Write中最后做的就是獲取操作數,獲取操作類型,根據操作類型執行對應的操作,驗證payload中數據的簽名。其中需要注意的是關於操作數的計算和更新數據的校驗。

4.關於操作數的計算,可以看下面相關的部分

 1 num_total_operations_ = 0; 2     for (const auto& partition : partitions_) { 3       num_total_operations_ += partition.operations_size(); 4 acc_num_operations_.push_back(num_total_operations_); 5 } 6 
 7     while (next_operation_num_ >= acc_num_operations_[current_partition_]) { 8 CloseCurrentPartition(); 9       current_partition_++; 10       if (!OpenCurrentPartition()) { 11         *error = ErrorCode::kInstallDeviceOpenError; 12         return false; 13 } 14 } 15 
16     const size_t partition_operation_num = next_operation_num_ - ( 17         current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0);

假設有分區A,B,C對應的操作數為2,4,6。那么num_total_operations_ =12,acc_num_operations_.中存放的元素為2,6,12,此時執行到了第2個操作,next_operation_num_ =2,而2是等於acc_num_operations_[0]的,而存放操作的數組是從0開始的,也就是說,當next_operation_num_等於acc_num_operations_時也就是說當前分區的操作已經執行完了,應該切換到下一個分區了,最后根據next_operation_num_和acc_num_operations_計算出操作類型的索引,獲取對應的操作類型。最后對於更新數據的校驗是指每當應用所下載的數據的時候,都會對其進行校驗,首先是保存了數據的hash值之后再根據所下載的數據計算一個hash指,進行比對,驗證數據是否正確。

到這里DownloadAction的核心部分已經分析完成,下面一篇文章會分析FilesystemVerifierAction,PostinstallRunnerAction。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM