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 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");
242 if (fwrite(&kFileFormatVersion
, sizeof(kFileFormatVersion
), 1, file
) != 1) {
243 LOG(ERROR
) << "Failed to write file version";
244 base::CloseFile(file
);
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
);
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
);
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
);
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
=
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
);
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
);
292 data_offset
+= it
->second
.length();
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
);
304 if (fwrite(&data_offset
, sizeof(data_offset
), 1, file
) != 1) {
305 LOG(ERROR
) << "Failed to write extra offset.";
306 base::CloseFile(file
);
310 for (std::map
<uint16
, base::StringPiece
>::const_iterator it
=
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
);
320 base::CloseFile(file
);