1 // Copyright (c) 2012 The Chromium 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 "ui/base/resource/data_pack.h" 6 7 #include <errno.h> 8 9 #include "base/files/file_util.h" 10 #include "base/files/memory_mapped_file.h" 11 #include "base/logging.h" 12 #include "base/memory/ref_counted_memory.h" 13 #include "base/metrics/histogram.h" 14 #include "base/strings/string_piece.h" 15 16 // For details of the file layout, see 17 // http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings 18 19 namespace { 20 21 static const uint32 kFileFormatVersion = 4; 22 // Length of file header: version, entry count and text encoding type. 23 static const size_t kHeaderLength = 2 * sizeof(uint32) + sizeof(uint8); 24 25 #pragma pack(push,2) 26 struct DataPackEntry { 27 uint16 resource_id; 28 uint32 file_offset; 29 30 static int CompareById(const void* void_key, const void* void_entry) { 31 uint16 key = *reinterpret_cast<const uint16*>(void_key); 32 const DataPackEntry* entry = 33 reinterpret_cast<const DataPackEntry*>(void_entry); 34 if (key < entry->resource_id) { 35 return -1; 36 } else if (key > entry->resource_id) { 37 return 1; 38 } else { 39 return 0; 40 } 41 } 42 }; 43 #pragma pack(pop) 44 45 COMPILE_ASSERT(sizeof(DataPackEntry) == 6, size_of_entry_must_be_six); 46 47 // We're crashing when trying to load a pak file on Windows. Add some error 48 // codes for logging. 49 // http://crbug.com/58056 50 enum LoadErrors { 51 INIT_FAILED = 1, 52 BAD_VERSION, 53 INDEX_TRUNCATED, 54 ENTRY_NOT_FOUND, 55 HEADER_TRUNCATED, 56 WRONG_ENCODING, 57 INIT_FAILED_FROM_FILE, 58 59 LOAD_ERRORS_COUNT, 60 }; 61 62 } // namespace 63 64 namespace ui { 65 66 DataPack::DataPack(ui::ScaleFactor scale_factor) 67 : resource_count_(0), 68 text_encoding_type_(BINARY), 69 scale_factor_(scale_factor) { 70 } 71 72 DataPack::~DataPack() { 73 } 74 75 bool DataPack::LoadFromPath(const base::FilePath& path) { 76 mmap_.reset(new base::MemoryMappedFile); 77 if (!mmap_->Initialize(path)) { 78 DLOG(ERROR) << "Failed to mmap datapack"; 79 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INIT_FAILED, 80 LOAD_ERRORS_COUNT); 81 mmap_.reset(); 82 return false; 83 } 84 return LoadImpl(); 85 } 86 87 bool DataPack::LoadFromFile(base::File file) { 88 return LoadFromFileRegion(file.Pass(), 89 base::MemoryMappedFile::Region::kWholeFile); 90 } 91 92 bool DataPack::LoadFromFileRegion( 93 base::File file, 94 const base::MemoryMappedFile::Region& region) { 95 mmap_.reset(new base::MemoryMappedFile); 96 if (!mmap_->Initialize(file.Pass(), region)) { 97 DLOG(ERROR) << "Failed to mmap datapack"; 98 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INIT_FAILED_FROM_FILE, 99 LOAD_ERRORS_COUNT); 100 mmap_.reset(); 101 return false; 102 } 103 return LoadImpl(); 104 } 105 106 bool DataPack::LoadImpl() { 107 // Sanity check the header of the file. 108 if (kHeaderLength > mmap_->length()) { 109 DLOG(ERROR) << "Data pack file corruption: incomplete file header."; 110 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", HEADER_TRUNCATED, 111 LOAD_ERRORS_COUNT); 112 mmap_.reset(); 113 return false; 114 } 115 116 // Parse the header of the file. 117 // First uint32: version; second: resource count; 118 const uint32* ptr = reinterpret_cast<const uint32*>(mmap_->data()); 119 uint32 version = ptr[0]; 120 if (version != kFileFormatVersion) { 121 LOG(ERROR) << "Bad data pack version: got " << version << ", expected " 122 << kFileFormatVersion; 123 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", BAD_VERSION, 124 LOAD_ERRORS_COUNT); 125 mmap_.reset(); 126 return false; 127 } 128 resource_count_ = ptr[1]; 129 130 // third: text encoding. 131 const uint8* ptr_encoding = reinterpret_cast<const uint8*>(ptr + 2); 132 text_encoding_type_ = static_cast<TextEncodingType>(*ptr_encoding); 133 if (text_encoding_type_ != UTF8 && text_encoding_type_ != UTF16 && 134 text_encoding_type_ != BINARY) { 135 LOG(ERROR) << "Bad data pack text encoding: got " << text_encoding_type_ 136 << ", expected between " << BINARY << " and " << UTF16; 137 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", WRONG_ENCODING, 138 LOAD_ERRORS_COUNT); 139 mmap_.reset(); 140 return false; 141 } 142 143 // Sanity check the file. 144 // 1) Check we have enough entries. There's an extra entry after the last item 145 // which gives the length of the last item. 146 if (kHeaderLength + (resource_count_ + 1) * sizeof(DataPackEntry) > 147 mmap_->length()) { 148 LOG(ERROR) << "Data pack file corruption: too short for number of " 149 "entries specified."; 150 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INDEX_TRUNCATED, 151 LOAD_ERRORS_COUNT); 152 mmap_.reset(); 153 return false; 154 } 155 // 2) Verify the entries are within the appropriate bounds. There's an extra 156 // entry after the last item which gives us the length of the last item. 157 for (size_t i = 0; i < resource_count_ + 1; ++i) { 158 const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>( 159 mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry))); 160 if (entry->file_offset > mmap_->length()) { 161 LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. " 162 << "Was the file corrupted?"; 163 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", ENTRY_NOT_FOUND, 164 LOAD_ERRORS_COUNT); 165 mmap_.reset(); 166 return false; 167 } 168 } 169 170 return true; 171 } 172 173 bool DataPack::HasResource(uint16 resource_id) const { 174 return !!bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_, 175 sizeof(DataPackEntry), DataPackEntry::CompareById); 176 } 177 178 bool DataPack::GetStringPiece(uint16 resource_id, 179 base::StringPiece* data) const { 180 // It won't be hard to make this endian-agnostic, but it's not worth 181 // bothering to do right now. 182 #if defined(__BYTE_ORDER) 183 // Linux check 184 COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, 185 datapack_assumes_little_endian); 186 #elif defined(__BIG_ENDIAN__) 187 // Mac check 188 #error DataPack assumes little endian 189 #endif 190 191 const DataPackEntry* target = reinterpret_cast<const DataPackEntry*>( 192 bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_, 193 sizeof(DataPackEntry), DataPackEntry::CompareById)); 194 if (!target) { 195 return false; 196 } 197 198 const DataPackEntry* next_entry = target + 1; 199 // If the next entry points beyond the end of the file this data pack's entry 200 // table is corrupt. Log an error and return false. See 201 // http://crbug.com/371301. 202 if (next_entry->file_offset > mmap_->length()) { 203 size_t entry_index = target - 204 reinterpret_cast<const DataPackEntry*>(mmap_->data() + kHeaderLength); 205 LOG(ERROR) << "Entry #" << entry_index << " in data pack points off end " 206 << "of file. This should have been caught when loading. Was the " 207 << "file modified?"; 208 return false; 209 } 210 211 size_t length = next_entry->file_offset - target->file_offset; 212 data->set(reinterpret_cast<const char*>(mmap_->data() + target->file_offset), 213 length); 214 return true; 215 } 216 217 base::RefCountedStaticMemory* DataPack::GetStaticMemory( 218 uint16 resource_id) const { 219 base::StringPiece piece; 220 if (!GetStringPiece(resource_id, &piece)) 221 return NULL; 222 223 return new base::RefCountedStaticMemory(piece.data(), piece.length()); 224 } 225 226 ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const { 227 return text_encoding_type_; 228 } 229 230 ui::ScaleFactor DataPack::GetScaleFactor() const { 231 return scale_factor_; 232 } 233 234 // static 235 bool DataPack::WritePack(const base::FilePath& path, 236 const std::map<uint16, base::StringPiece>& resources, 237 TextEncodingType textEncodingType) { 238 FILE* file = base::OpenFile(path, "wb"); 239 if (!file) 240 return false; 241 242 if (fwrite(&kFileFormatVersion, sizeof(kFileFormatVersion), 1, file) != 1) { 243 LOG(ERROR) << "Failed to write file version"; 244 base::CloseFile(file); 245 return false; 246 } 247 248 // Note: the python version of this function explicitly sorted keys, but 249 // std::map is a sorted associative container, we shouldn't have to do that. 250 uint32 entry_count = resources.size(); 251 if (fwrite(&entry_count, sizeof(entry_count), 1, file) != 1) { 252 LOG(ERROR) << "Failed to write entry count"; 253 base::CloseFile(file); 254 return false; 255 } 256 257 if (textEncodingType != UTF8 && textEncodingType != UTF16 && 258 textEncodingType != BINARY) { 259 LOG(ERROR) << "Invalid text encoding type, got " << textEncodingType 260 << ", expected between " << BINARY << " and " << UTF16; 261 base::CloseFile(file); 262 return false; 263 } 264 265 uint8 write_buffer = textEncodingType; 266 if (fwrite(&write_buffer, sizeof(uint8), 1, file) != 1) { 267 LOG(ERROR) << "Failed to write file text resources encoding"; 268 base::CloseFile(file); 269 return false; 270 } 271 272 // Each entry is a uint16 + a uint32. We have an extra entry after the last 273 // item so we can compute the size of the list item. 274 uint32 index_length = (entry_count + 1) * sizeof(DataPackEntry); 275 uint32 data_offset = kHeaderLength + index_length; 276 for (std::map<uint16, base::StringPiece>::const_iterator it = 277 resources.begin(); 278 it != resources.end(); ++it) { 279 uint16 resource_id = it->first; 280 if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) { 281 LOG(ERROR) << "Failed to write id for " << resource_id; 282 base::CloseFile(file); 283 return false; 284 } 285 286 if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) { 287 LOG(ERROR) << "Failed to write offset for " << resource_id; 288 base::CloseFile(file); 289 return false; 290 } 291 292 data_offset += it->second.length(); 293 } 294 295 // We place an extra entry after the last item that allows us to read the 296 // size of the last item. 297 uint16 resource_id = 0; 298 if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) { 299 LOG(ERROR) << "Failed to write extra resource id."; 300 base::CloseFile(file); 301 return false; 302 } 303 304 if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) { 305 LOG(ERROR) << "Failed to write extra offset."; 306 base::CloseFile(file); 307 return false; 308 } 309 310 for (std::map<uint16, base::StringPiece>::const_iterator it = 311 resources.begin(); 312 it != resources.end(); ++it) { 313 if (fwrite(it->second.data(), it->second.length(), 1, file) != 1) { 314 LOG(ERROR) << "Failed to write data for " << it->first; 315 base::CloseFile(file); 316 return false; 317 } 318 } 319 320 base::CloseFile(file); 321 322 return true; 323 } 324 325 } // namespace ui 326