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.
5 #include "ui/base/resource/data_pack.h"
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"
16 // For details of the file layout, see
17 // http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
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);
26 struct DataPackEntry {
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) {
36 } else if (key > entry->resource_id) {
45 COMPILE_ASSERT(sizeof(DataPackEntry) == 6, size_of_entry_must_be_six);
47 // We're crashing when trying to load a pak file on Windows. Add some error
49 // http://crbug.com/58056
57 INIT_FAILED_FROM_FILE,
66 DataPack::DataPack(ui::ScaleFactor scale_factor)
68 text_encoding_type_(BINARY),
69 scale_factor_(scale_factor) {
72 DataPack::~DataPack() {
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,
87 bool DataPack::LoadFromFile(base::File file) {
88 mmap_.reset(new base::MemoryMappedFile);
89 if (!mmap_->Initialize(file.Pass())) {
90 DLOG(ERROR) << "Failed to mmap datapack";
91 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INIT_FAILED_FROM_FILE,
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,
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,
121 resource_count_ = ptr[1];
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,
136 // Sanity check the file.
137 // 1) Check we have enough entries. There's an extra entry after the last item
138 // which gives the length of the last item.
139 if (kHeaderLength + (resource_count_ + 1) * sizeof(DataPackEntry) >
141 LOG(ERROR) << "Data pack file corruption: too short for number of "
142 "entries specified.";
143 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INDEX_TRUNCATED,
148 // 2) Verify the entries are within the appropriate bounds. There's an extra
149 // entry after the last item which gives us the length of the last item.
150 for (size_t i = 0; i < resource_count_ + 1; ++i) {
151 const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>(
152 mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry)));
153 if (entry->file_offset > mmap_->length()) {
154 LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. "
155 << "Was the file corrupted?";
156 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", ENTRY_NOT_FOUND,
166 bool DataPack::HasResource(uint16 resource_id) const {
167 return !!bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
168 sizeof(DataPackEntry), DataPackEntry::CompareById);
171 bool DataPack::GetStringPiece(uint16 resource_id,
172 base::StringPiece* data) const {
173 // It won't be hard to make this endian-agnostic, but it's not worth
174 // bothering to do right now.
175 #if defined(__BYTE_ORDER)
177 COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
178 datapack_assumes_little_endian);
179 #elif defined(__BIG_ENDIAN__)
181 #error DataPack assumes little endian
184 const DataPackEntry* target = reinterpret_cast<const DataPackEntry*>(
185 bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
186 sizeof(DataPackEntry), DataPackEntry::CompareById));
191 const DataPackEntry* next_entry = target + 1;
192 // If the next entry points beyond the end of the file this data pack's entry
193 // table is corrupt. Log an error and return false. See
194 // http://crbug.com/371301.
195 if (next_entry->file_offset > mmap_->length()) {
196 size_t entry_index = target -
197 reinterpret_cast<const DataPackEntry*>(mmap_->data() + kHeaderLength);
198 LOG(ERROR) << "Entry #" << entry_index << " in data pack points off end "
199 << "of file. This should have been caught when loading. Was the "
204 size_t length = next_entry->file_offset - target->file_offset;
205 data->set(reinterpret_cast<const char*>(mmap_->data() + target->file_offset),
210 base::RefCountedStaticMemory* DataPack::GetStaticMemory(
211 uint16 resource_id) const {
212 base::StringPiece piece;
213 if (!GetStringPiece(resource_id, &piece))
216 return new base::RefCountedStaticMemory(piece.data(), piece.length());
219 ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const {
220 return text_encoding_type_;
223 ui::ScaleFactor DataPack::GetScaleFactor() const {
224 return scale_factor_;
228 bool DataPack::WritePack(const base::FilePath& path,
229 const std::map<uint16, base::StringPiece>& resources,
230 TextEncodingType textEncodingType) {
231 FILE* file = base::OpenFile(path, "wb");
235 if (fwrite(&kFileFormatVersion, sizeof(kFileFormatVersion), 1, file) != 1) {
236 LOG(ERROR) << "Failed to write file version";
237 base::CloseFile(file);
241 // Note: the python version of this function explicitly sorted keys, but
242 // std::map is a sorted associative container, we shouldn't have to do that.
243 uint32 entry_count = resources.size();
244 if (fwrite(&entry_count, sizeof(entry_count), 1, file) != 1) {
245 LOG(ERROR) << "Failed to write entry count";
246 base::CloseFile(file);
250 if (textEncodingType != UTF8 && textEncodingType != UTF16 &&
251 textEncodingType != BINARY) {
252 LOG(ERROR) << "Invalid text encoding type, got " << textEncodingType
253 << ", expected between " << BINARY << " and " << UTF16;
254 base::CloseFile(file);
258 uint8 write_buffer = textEncodingType;
259 if (fwrite(&write_buffer, sizeof(uint8), 1, file) != 1) {
260 LOG(ERROR) << "Failed to write file text resources encoding";
261 base::CloseFile(file);
265 // Each entry is a uint16 + a uint32. We have an extra entry after the last
266 // item so we can compute the size of the list item.
267 uint32 index_length = (entry_count + 1) * sizeof(DataPackEntry);
268 uint32 data_offset = kHeaderLength + index_length;
269 for (std::map<uint16, base::StringPiece>::const_iterator it =
271 it != resources.end(); ++it) {
272 uint16 resource_id = it->first;
273 if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
274 LOG(ERROR) << "Failed to write id for " << resource_id;
275 base::CloseFile(file);
279 if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
280 LOG(ERROR) << "Failed to write offset for " << resource_id;
281 base::CloseFile(file);
285 data_offset += it->second.length();
288 // We place an extra entry after the last item that allows us to read the
289 // size of the last item.
290 uint16 resource_id = 0;
291 if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
292 LOG(ERROR) << "Failed to write extra resource id.";
293 base::CloseFile(file);
297 if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
298 LOG(ERROR) << "Failed to write extra offset.";
299 base::CloseFile(file);
303 for (std::map<uint16, base::StringPiece>::const_iterator it =
305 it != resources.end(); ++it) {
306 if (fwrite(it->second.data(), it->second.length(), 1, file) != 1) {
307 LOG(ERROR) << "Failed to write data for " << it->first;
308 base::CloseFile(file);
313 base::CloseFile(file);