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/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"
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 static_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 return LoadFromFileRegion(file
.Pass(),
89 base::MemoryMappedFile::Region::kWholeFile
);
92 bool DataPack::LoadFromFileRegion(
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
,
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
,
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
,
128 resource_count_
= ptr
[1];
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
,
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
) >
148 LOG(ERROR
) << "Data pack file corruption: too short for number of "
149 "entries specified.";
150 UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INDEX_TRUNCATED
,
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
,
173 bool DataPack::HasResource(uint16 resource_id
) const {
174 return !!bsearch(&resource_id
, mmap_
->data() + kHeaderLength
, resource_count_
,
175 sizeof(DataPackEntry
), DataPackEntry::CompareById
);
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)
184 static_assert(__BYTE_ORDER
== __LITTLE_ENDIAN
,
185 "datapack assumes little endian");
186 #elif defined(__BIG_ENDIAN__)
188 #error DataPack assumes little endian
191 const DataPackEntry
* target
= reinterpret_cast<const DataPackEntry
*>(
192 bsearch(&resource_id
, mmap_
->data() + kHeaderLength
, resource_count_
,
193 sizeof(DataPackEntry
), DataPackEntry::CompareById
));
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 "
211 size_t length
= next_entry
->file_offset
- target
->file_offset
;
212 data
->set(reinterpret_cast<const char*>(mmap_
->data() + target
->file_offset
),
217 base::RefCountedStaticMemory
* DataPack::GetStaticMemory(
218 uint16 resource_id
) const {
219 base::StringPiece piece
;
220 if (!GetStringPiece(resource_id
, &piece
))
223 return new base::RefCountedStaticMemory(piece
.data(), piece
.length());
226 ResourceHandle::TextEncodingType
DataPack::GetTextEncodingType() const {
227 return text_encoding_type_
;
230 ui::ScaleFactor
DataPack::GetScaleFactor() const {
231 return scale_factor_
;
235 void DataPack::CheckForDuplicateResources(
236 const ScopedVector
<ResourceHandle
>& packs
) {
237 for (size_t i
= 0; i
< resource_count_
+ 1; ++i
) {
238 const DataPackEntry
* entry
= reinterpret_cast<const DataPackEntry
*>(
239 mmap_
->data() + kHeaderLength
+ (i
* sizeof(DataPackEntry
)));
240 const uint16 resource_id
= entry
->resource_id
;
241 const float resource_scale
= GetScaleForScaleFactor(scale_factor_
);
242 for (const ResourceHandle
* handle
: packs
) {
244 GetScaleForScaleFactor(handle
->GetScaleFactor()) == resource_scale
,
245 !handle
->HasResource(resource_id
));
249 #endif // DCHECK_IS_ON()
252 bool DataPack::WritePack(const base::FilePath
& path
,
253 const std::map
<uint16
, base::StringPiece
>& resources
,
254 TextEncodingType textEncodingType
) {
255 FILE* file
= base::OpenFile(path
, "wb");
259 if (fwrite(&kFileFormatVersion
, sizeof(kFileFormatVersion
), 1, file
) != 1) {
260 LOG(ERROR
) << "Failed to write file version";
261 base::CloseFile(file
);
265 // Note: the python version of this function explicitly sorted keys, but
266 // std::map is a sorted associative container, we shouldn't have to do that.
267 uint32 entry_count
= resources
.size();
268 if (fwrite(&entry_count
, sizeof(entry_count
), 1, file
) != 1) {
269 LOG(ERROR
) << "Failed to write entry count";
270 base::CloseFile(file
);
274 if (textEncodingType
!= UTF8
&& textEncodingType
!= UTF16
&&
275 textEncodingType
!= BINARY
) {
276 LOG(ERROR
) << "Invalid text encoding type, got " << textEncodingType
277 << ", expected between " << BINARY
<< " and " << UTF16
;
278 base::CloseFile(file
);
282 uint8 write_buffer
= static_cast<uint8
>(textEncodingType
);
283 if (fwrite(&write_buffer
, sizeof(uint8
), 1, file
) != 1) {
284 LOG(ERROR
) << "Failed to write file text resources encoding";
285 base::CloseFile(file
);
289 // Each entry is a uint16 + a uint32. We have an extra entry after the last
290 // item so we can compute the size of the list item.
291 uint32 index_length
= (entry_count
+ 1) * sizeof(DataPackEntry
);
292 uint32 data_offset
= kHeaderLength
+ index_length
;
293 for (std::map
<uint16
, base::StringPiece
>::const_iterator it
=
295 it
!= resources
.end(); ++it
) {
296 uint16 resource_id
= it
->first
;
297 if (fwrite(&resource_id
, sizeof(resource_id
), 1, file
) != 1) {
298 LOG(ERROR
) << "Failed to write id for " << resource_id
;
299 base::CloseFile(file
);
303 if (fwrite(&data_offset
, sizeof(data_offset
), 1, file
) != 1) {
304 LOG(ERROR
) << "Failed to write offset for " << resource_id
;
305 base::CloseFile(file
);
309 data_offset
+= it
->second
.length();
312 // We place an extra entry after the last item that allows us to read the
313 // size of the last item.
314 uint16 resource_id
= 0;
315 if (fwrite(&resource_id
, sizeof(resource_id
), 1, file
) != 1) {
316 LOG(ERROR
) << "Failed to write extra resource id.";
317 base::CloseFile(file
);
321 if (fwrite(&data_offset
, sizeof(data_offset
), 1, file
) != 1) {
322 LOG(ERROR
) << "Failed to write extra offset.";
323 base::CloseFile(file
);
327 for (std::map
<uint16
, base::StringPiece
>::const_iterator it
=
329 it
!= resources
.end(); ++it
) {
330 if (fwrite(it
->second
.data(), it
->second
.length(), 1, file
) != 1) {
331 LOG(ERROR
) << "Failed to write data for " << it
->first
;
332 base::CloseFile(file
);
337 base::CloseFile(file
);