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/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::PlatformFile file) { 88 mmap_.reset(new base::MemoryMappedFile); 89 if (!mmap_->Initialize(file)) { 90 DLOG(ERROR) << "Failed to mmap datapack"; 91 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INIT_FAILED_FROM_FILE, 92 LOAD_ERRORS_COUNT); 93 mmap_.reset(); 94 return false; 95 } 96 return LoadImpl(); 97 } 98 99 bool DataPack::LoadImpl() { 100 // Sanity check the header of the file. 101 if (kHeaderLength > mmap_->length()) { 102 DLOG(ERROR) << "Data pack file corruption: incomplete file header."; 103 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", HEADER_TRUNCATED, 104 LOAD_ERRORS_COUNT); 105 mmap_.reset(); 106 return false; 107 } 108 109 // Parse the header of the file. 110 // First uint32: version; second: resource count; 111 const uint32* ptr = reinterpret_cast<const uint32*>(mmap_->data()); 112 uint32 version = ptr[0]; 113 if (version != kFileFormatVersion) { 114 LOG(ERROR) << "Bad data pack version: got " << version << ", expected " 115 << kFileFormatVersion; 116 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", BAD_VERSION, 117 LOAD_ERRORS_COUNT); 118 mmap_.reset(); 119 return false; 120 } 121 resource_count_ = ptr[1]; 122 123 // third: text encoding. 124 const uint8* ptr_encoding = reinterpret_cast<const uint8*>(ptr + 2); 125 text_encoding_type_ = static_cast<TextEncodingType>(*ptr_encoding); 126 if (text_encoding_type_ != UTF8 && text_encoding_type_ != UTF16 && 127 text_encoding_type_ != BINARY) { 128 LOG(ERROR) << "Bad data pack text encoding: got " << text_encoding_type_ 129 << ", expected between " << BINARY << " and " << UTF16; 130 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", WRONG_ENCODING, 131 LOAD_ERRORS_COUNT); 132 mmap_.reset(); 133 return false; 134 } 135 136 // Sanity check the file. 137 // 1) Check we have enough entries. 138 if (kHeaderLength + resource_count_ * sizeof(DataPackEntry) > 139 mmap_->length()) { 140 LOG(ERROR) << "Data pack file corruption: too short for number of " 141 "entries specified."; 142 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INDEX_TRUNCATED, 143 LOAD_ERRORS_COUNT); 144 mmap_.reset(); 145 return false; 146 } 147 // 2) Verify the entries are within the appropriate bounds. There's an extra 148 // entry after the last item which gives us the length of the last item. 149 for (size_t i = 0; i < resource_count_ + 1; ++i) { 150 const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>( 151 mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry))); 152 if (entry->file_offset > mmap_->length()) { 153 LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. " 154 << "Was the file corrupted?"; 155 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", ENTRY_NOT_FOUND, 156 LOAD_ERRORS_COUNT); 157 mmap_.reset(); 158 return false; 159 } 160 } 161 162 return true; 163 } 164 165 bool DataPack::HasResource(uint16 resource_id) const { 166 return !!bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_, 167 sizeof(DataPackEntry), DataPackEntry::CompareById); 168 } 169 170 bool DataPack::GetStringPiece(uint16 resource_id, 171 base::StringPiece* data) const { 172 // It won't be hard to make this endian-agnostic, but it's not worth 173 // bothering to do right now. 174 #if defined(__BYTE_ORDER) 175 // Linux check 176 COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, 177 datapack_assumes_little_endian); 178 #elif defined(__BIG_ENDIAN__) 179 // Mac check 180 #error DataPack assumes little endian 181 #endif 182 183 const DataPackEntry* target = reinterpret_cast<const DataPackEntry*>( 184 bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_, 185 sizeof(DataPackEntry), DataPackEntry::CompareById)); 186 if (!target) { 187 return false; 188 } 189 190 const DataPackEntry* next_entry = target + 1; 191 size_t length = next_entry->file_offset - target->file_offset; 192 193 data->set(mmap_->data() + target->file_offset, length); 194 return true; 195 } 196 197 base::RefCountedStaticMemory* DataPack::GetStaticMemory( 198 uint16 resource_id) const { 199 base::StringPiece piece; 200 if (!GetStringPiece(resource_id, &piece)) 201 return NULL; 202 203 return new base::RefCountedStaticMemory( 204 reinterpret_cast<const unsigned char*>(piece.data()), piece.length()); 205 } 206 207 ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const { 208 return text_encoding_type_; 209 } 210 211 ui::ScaleFactor DataPack::GetScaleFactor() const { 212 return scale_factor_; 213 } 214 215 // static 216 bool DataPack::WritePack(const base::FilePath& path, 217 const std::map<uint16, base::StringPiece>& resources, 218 TextEncodingType textEncodingType) { 219 FILE* file = base::OpenFile(path, "wb"); 220 if (!file) 221 return false; 222 223 if (fwrite(&kFileFormatVersion, sizeof(kFileFormatVersion), 1, file) != 1) { 224 LOG(ERROR) << "Failed to write file version"; 225 base::CloseFile(file); 226 return false; 227 } 228 229 // Note: the python version of this function explicitly sorted keys, but 230 // std::map is a sorted associative container, we shouldn't have to do that. 231 uint32 entry_count = resources.size(); 232 if (fwrite(&entry_count, sizeof(entry_count), 1, file) != 1) { 233 LOG(ERROR) << "Failed to write entry count"; 234 base::CloseFile(file); 235 return false; 236 } 237 238 if (textEncodingType != UTF8 && textEncodingType != UTF16 && 239 textEncodingType != BINARY) { 240 LOG(ERROR) << "Invalid text encoding type, got " << textEncodingType 241 << ", expected between " << BINARY << " and " << UTF16; 242 base::CloseFile(file); 243 return false; 244 } 245 246 uint8 write_buffer = textEncodingType; 247 if (fwrite(&write_buffer, sizeof(uint8), 1, file) != 1) { 248 LOG(ERROR) << "Failed to write file text resources encoding"; 249 base::CloseFile(file); 250 return false; 251 } 252 253 // Each entry is a uint16 + a uint32. We have an extra entry after the last 254 // item so we can compute the size of the list item. 255 uint32 index_length = (entry_count + 1) * sizeof(DataPackEntry); 256 uint32 data_offset = kHeaderLength + index_length; 257 for (std::map<uint16, base::StringPiece>::const_iterator it = 258 resources.begin(); 259 it != resources.end(); ++it) { 260 uint16 resource_id = it->first; 261 if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) { 262 LOG(ERROR) << "Failed to write id for " << resource_id; 263 base::CloseFile(file); 264 return false; 265 } 266 267 if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) { 268 LOG(ERROR) << "Failed to write offset for " << resource_id; 269 base::CloseFile(file); 270 return false; 271 } 272 273 data_offset += it->second.length(); 274 } 275 276 // We place an extra entry after the last item that allows us to read the 277 // size of the last item. 278 uint16 resource_id = 0; 279 if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) { 280 LOG(ERROR) << "Failed to write extra resource id."; 281 base::CloseFile(file); 282 return false; 283 } 284 285 if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) { 286 LOG(ERROR) << "Failed to write extra offset."; 287 base::CloseFile(file); 288 return false; 289 } 290 291 for (std::map<uint16, base::StringPiece>::const_iterator it = 292 resources.begin(); 293 it != resources.end(); ++it) { 294 if (fwrite(it->second.data(), it->second.length(), 1, file) != 1) { 295 LOG(ERROR) << "Failed to write data for " << it->first; 296 base::CloseFile(file); 297 return false; 298 } 299 } 300 301 base::CloseFile(file); 302 303 return true; 304 } 305 306 } // namespace ui 307