OLD | NEW |
| (Empty) |
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/linuxresourcesandlocalize
dstrings | |
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 = static_cast<uint8>(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 | |
OLD | NEW |