1 // Copyright 2017 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "bsdiff/patch_writer.h" 6 7 #include <string.h> 8 #include <limits> 9 10 #include "bsdiff/brotli_compressor.h" 11 #include "bsdiff/bz2_compressor.h" 12 #include "bsdiff/constants.h" 13 #include "bsdiff/control_entry.h" 14 #include "bsdiff/logging.h" 15 16 namespace { 17 18 void EncodeInt64(int64_t x, uint8_t* buf) { 19 uint64_t y = x < 0 ? (1ULL << 63ULL) - x : x; 20 for (int i = 0; i < 8; ++i) { 21 buf[i] = y & 0xff; 22 y /= 256; 23 } 24 } 25 26 } // namespace 27 28 namespace bsdiff { 29 30 BsdiffPatchWriter::BsdiffPatchWriter(const std::string& patch_filename) 31 : patch_filename_(patch_filename), 32 format_(BsdiffFormat::kLegacy), 33 brotli_quality_(-1) { 34 types_.emplace_back(CompressorType::kBZ2); 35 } 36 37 38 BsdiffPatchWriter::BsdiffPatchWriter(const std::string& patch_filename, 39 const std::vector<CompressorType>& types, 40 int brotli_quality) 41 : patch_filename_(patch_filename), 42 format_(BsdiffFormat::kBsdf2), 43 types_(types), 44 brotli_quality_(brotli_quality) {} 45 46 bool BsdiffPatchWriter::InitializeCompressorList( 47 std::vector<std::unique_ptr<bsdiff::CompressorInterface>>* 48 compressor_list) { 49 if (types_.empty()) { 50 LOG(ERROR) << "Patch writer expects at least one compressor."; 51 return false; 52 } 53 54 for (const auto& type : types_) { 55 switch (type) { 56 case CompressorType::kBZ2: 57 compressor_list->emplace_back(new BZ2Compressor()); 58 break; 59 case CompressorType::kBrotli: 60 compressor_list->emplace_back(new BrotliCompressor(brotli_quality_)); 61 break; 62 case CompressorType::kNoCompression: 63 LOG(ERROR) << "Unsupported compression type " << static_cast<int>(type); 64 return false; 65 } 66 } 67 68 for (const auto& compressor : *compressor_list) { 69 if (!compressor) { 70 return false; 71 } 72 } 73 74 return true; 75 } 76 77 bool BsdiffPatchWriter::Init(size_t /* new_size */) { 78 if (!InitializeCompressorList(&ctrl_stream_list_)) { 79 LOG(ERROR) << "Failed to initialize control stream compressors."; 80 return false; 81 } 82 83 if (!InitializeCompressorList(&diff_stream_list_)) { 84 LOG(ERROR) << "Failed to initialize diff stream compressors."; 85 return false; 86 } 87 88 if (!InitializeCompressorList(&extra_stream_list_)) { 89 LOG(ERROR) << "Failed to initialize extra stream compressors."; 90 return false; 91 } 92 93 fp_ = fopen(patch_filename_.c_str(), "w"); 94 if (!fp_) { 95 LOG(ERROR) << "Opening " << patch_filename_; 96 return false; 97 } 98 return true; 99 } 100 101 bool BsdiffPatchWriter::WriteDiffStream(const uint8_t* data, size_t size) { 102 for (const auto& compressor : diff_stream_list_) { 103 if (!compressor->Write(data, size)) { 104 return false; 105 } 106 } 107 108 return true; 109 } 110 111 bool BsdiffPatchWriter::WriteExtraStream(const uint8_t* data, size_t size) { 112 for (const auto& compressor : extra_stream_list_) { 113 if (!compressor->Write(data, size)) { 114 return false; 115 } 116 } 117 118 return true; 119 } 120 121 bool BsdiffPatchWriter::AddControlEntry(const ControlEntry& entry) { 122 // Generate the 24 byte control entry. 123 uint8_t buf[24]; 124 EncodeInt64(entry.diff_size, buf); 125 EncodeInt64(entry.extra_size, buf + 8); 126 EncodeInt64(entry.offset_increment, buf + 16); 127 128 for (const auto& compressor : ctrl_stream_list_) { 129 if (!compressor->Write(buf, sizeof(buf))) { 130 return false; 131 } 132 } 133 134 written_output_ += entry.diff_size + entry.extra_size; 135 return true; 136 } 137 138 bool BsdiffPatchWriter::SelectSmallestResult( 139 const std::vector<std::unique_ptr<CompressorInterface>>& compressor_list, 140 CompressorInterface** smallest_compressor) { 141 size_t min_size = std::numeric_limits<size_t>::max(); 142 for (const auto& compressor : compressor_list) { 143 if (!compressor->Finish()) { 144 LOG(ERROR) << "Failed to finalize compressed streams."; 145 return false; 146 } 147 148 // Update |smallest_compressor| if the current compressor produces a 149 // smaller result. 150 if (compressor->GetCompressedData().size() < min_size) { 151 min_size = compressor->GetCompressedData().size(); 152 *smallest_compressor = compressor.get(); 153 } 154 } 155 156 return true; 157 } 158 159 bool BsdiffPatchWriter::Close() { 160 if (!fp_) { 161 LOG(ERROR) << "File not open."; 162 return false; 163 } 164 165 CompressorInterface* ctrl_stream = nullptr; 166 if (!SelectSmallestResult(ctrl_stream_list_, &ctrl_stream) || !ctrl_stream) { 167 return false; 168 } 169 170 CompressorInterface* diff_stream = nullptr; 171 if (!SelectSmallestResult(diff_stream_list_, &diff_stream) || !diff_stream) { 172 return false; 173 } 174 175 CompressorInterface* extra_stream = nullptr; 176 if (!SelectSmallestResult(extra_stream_list_, &extra_stream) || 177 !extra_stream) { 178 return false; 179 } 180 181 auto ctrl_data = ctrl_stream->GetCompressedData(); 182 auto diff_data = diff_stream->GetCompressedData(); 183 auto extra_data = extra_stream->GetCompressedData(); 184 185 uint8_t types[3] = {static_cast<uint8_t>(ctrl_stream->Type()), 186 static_cast<uint8_t>(diff_stream->Type()), 187 static_cast<uint8_t>(extra_stream->Type())}; 188 189 if (!WriteHeader(types, ctrl_data.size(), diff_data.size())) 190 return false; 191 192 if (fwrite(ctrl_data.data(), 1, ctrl_data.size(), fp_) != ctrl_data.size()) { 193 LOG(ERROR) << "Writing ctrl_data."; 194 return false; 195 } 196 if (fwrite(diff_data.data(), 1, diff_data.size(), fp_) != diff_data.size()) { 197 LOG(ERROR) << "Writing diff_data."; 198 return false; 199 } 200 if (fwrite(extra_data.data(), 1, extra_data.size(), fp_) != 201 extra_data.size()) { 202 LOG(ERROR) << "Writing extra_data."; 203 return false; 204 } 205 if (fclose(fp_) != 0) { 206 LOG(ERROR) << "Closing the patch file."; 207 return false; 208 } 209 fp_ = nullptr; 210 return true; 211 } 212 213 bool BsdiffPatchWriter::WriteHeader(uint8_t types[3], 214 uint64_t ctrl_size, 215 uint64_t diff_size) { 216 /* Header format is 217 * 0 8 magic header 218 * 8 8 length of compressed ctrl block 219 * 16 8 length of compressed diff block 220 * 24 8 length of new file 221 * 222 * File format is 223 * 0 32 Header 224 * 32 ?? compressed ctrl block 225 * ?? ?? compressed diff block 226 * ?? ?? compressed extra block 227 */ 228 uint8_t header[32]; 229 if (format_ == BsdiffFormat::kLegacy) { 230 // The magic header is "BSDIFF40" for legacy format. 231 memcpy(header, kLegacyMagicHeader, 8); 232 } else if (format_ == BsdiffFormat::kBsdf2) { 233 // The magic header for BSDF2 format: 234 // 0 5 BSDF2 235 // 5 1 compressed type for control stream 236 // 6 1 compressed type for diff stream 237 // 7 1 compressed type for extra stream 238 memcpy(header, kBSDF2MagicHeader, 5); 239 memcpy(header + 5, types, 3); 240 } else { 241 LOG(ERROR) << "Unsupported bsdiff format."; 242 return false; 243 } 244 245 EncodeInt64(ctrl_size, header + 8); 246 EncodeInt64(diff_size, header + 16); 247 EncodeInt64(written_output_, header + 24); 248 if (fwrite(header, sizeof(header), 1, fp_) != 1) { 249 LOG(ERROR) << "writing to the patch file"; 250 return false; 251 } 252 return true; 253 } 254 255 } // namespace bsdiff 256