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 #include <algorithm>
9 #include <set>
10 #include <utility>
11 
12 #include "base/command_line.h"
13 #include "base/files/file_util.h"
14 #include "base/files/memory_mapped_file.h"
15 #include "base/logging.h"
16 #include "base/macros.h"
17 #include "base/memory/ref_counted_memory.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/stl_util.h"
20 #include "base/strings/string_piece.h"
21 #include "base/synchronization/lock.h"
22 #include "base/sys_byteorder.h"
23 #include "build/build_config.h"
24 #include "net/filter/gzip_header.h"
25 #include "third_party/zlib/google/compression_utils.h"
26 
27 // For details of the file layout, see
28 // http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
29 
30 namespace {
31 
32 static const uint32_t kFileFormatV4 = 4;
33 static const uint32_t kFileFormatV5 = 5;
34 // uint32(version), uint32(resource_count), uint8(encoding)
35 static const size_t kHeaderLengthV4 = 2 * sizeof(uint32_t) + sizeof(uint8_t);
36 // uint32(version), uint8(encoding), 3 bytes padding,
37 // uint16(resource_count), uint16(alias_count)
38 static const size_t kHeaderLengthV5 =
39     sizeof(uint32_t) + sizeof(uint8_t) * 4 + sizeof(uint16_t) * 2;
40 
41 // We're crashing when trying to load a pak file on Windows.  Add some error
42 // codes for logging.
43 // http://crbug.com/58056
44 enum LoadErrors {
45   INIT_FAILED = 1,
46   BAD_VERSION,
47   INDEX_TRUNCATED,
48   ENTRY_NOT_FOUND,
49   HEADER_TRUNCATED,
50   WRONG_ENCODING,
51   INIT_FAILED_FROM_FILE,
52   UNZIP_FAILED,
53 
54   LOAD_ERRORS_COUNT,
55 };
56 
LogDataPackError(LoadErrors error)57 void LogDataPackError(LoadErrors error) {
58   UMA_HISTOGRAM_ENUMERATION("DataPack.Load", error, LOAD_ERRORS_COUNT);
59 }
60 
61 // Prints the given resource id the first time it's loaded if Chrome has been
62 // started with --print-resource-ids. This output is then used to generate a
63 // more optimal resource renumbering to improve startup speed. See
64 // tools/gritsettings/README.md for more info.
MaybePrintResourceId(uint16_t resource_id)65 void MaybePrintResourceId(uint16_t resource_id) {
66   // This code is run in other binaries than Chrome which do not initialize the
67   // CommandLine object. Early return in those cases.
68   if (!base::CommandLine::InitializedForCurrentProcess())
69     return;
70 
71   // Note: This switch isn't in ui/base/ui_base_switches.h because ui/base
72   // depends on ui/base/resource and thus it would cause a circular dependency.
73   static bool print_resource_ids =
74       base::CommandLine::ForCurrentProcess()->HasSwitch("print-resource-ids");
75   if (!print_resource_ids)
76     return;
77 
78   // Note: These are leaked intentionally. However, it's only allocated if the
79   // above command line is specified, so it shouldn't affect regular users.
80   static std::set<uint16_t>* resource_ids_logged = new std::set<uint16_t>();
81   // DataPack doesn't require single-threaded access, so use a lock.
82   static base::Lock* lock = new base::Lock;
83   base::AutoLock auto_lock(*lock);
84   if (!base::Contains(*resource_ids_logged, resource_id)) {
85     printf("Resource=%d\n", resource_id);
86     resource_ids_logged->insert(resource_id);
87   }
88 }
89 
90 // Convenience class to write data to a file. Usage is the following:
91 // 1) Create a new instance, passing a base::FilePath.
92 // 2) Call Write() repeatedly to write all desired data to the file.
93 // 3) Call valid() whenever you want to know if something failed.
94 // 4) The file is closed automatically on destruction. Though it is possible
95 //    to call the Close() method before that.
96 //
97 // If an I/O error happens, a PLOG(ERROR) message will be generated, and
98 // a flag will be set in the writer, telling it to ignore future Write()
99 // requests. This allows the caller to ignore error handling until the
100 // very end, as in:
101 //
102 //   {
103 //     base::ScopedFileWriter  writer(<some-path>);
104 //     writer.Write(&foo, sizeof(foo));
105 //     writer.Write(&bar, sizeof(bar));
106 //     ....
107 //     writer.Write(&zoo, sizeof(zoo));
108 //     if (!writer.valid()) {
109 //        // An error happened.
110 //     }
111 //   }   // closes the file.
112 //
113 class ScopedFileWriter {
114  public:
115   // Constructor takes a |path| parameter and tries to open the file.
116   // Call valid() to check if the operation was succesful.
ScopedFileWriter(const base::FilePath & path)117   explicit ScopedFileWriter(const base::FilePath& path)
118       : valid_(true), file_(base::OpenFile(path, "wb")) {
119     if (!file_) {
120       PLOG(ERROR) << "Could not open pak file for writing";
121       valid_ = false;
122     }
123   }
124 
125   // Destructor.
~ScopedFileWriter()126   ~ScopedFileWriter() { Close(); }
127 
128   // Return true if the last i/o operation was succesful.
valid() const129   bool valid() const { return valid_; }
130 
131   // Try to write |data_size| bytes from |data| into the file, if a previous
132   // operation didn't already failed.
Write(const void * data,size_t data_size)133   void Write(const void* data, size_t data_size) {
134     if (valid_ && fwrite(data, data_size, 1, file_) != 1) {
135       PLOG(ERROR) << "Could not write to pak file";
136       valid_ = false;
137     }
138   }
139 
140   // Close the file explicitly. Return true if all previous operations
141   // succeeded, including the close, or false otherwise.
Close()142   bool Close() {
143     if (file_) {
144       valid_ = (fclose(file_) == 0);
145       file_ = nullptr;
146       if (!valid_) {
147         PLOG(ERROR) << "Could not close pak file";
148       }
149     }
150     return valid_;
151   }
152 
153  private:
154   bool valid_ = false;
155   FILE* file_ = nullptr;  // base::ScopedFILE doesn't check errors on close.
156 
157   DISALLOW_COPY_AND_ASSIGN(ScopedFileWriter);
158 };
159 
MmapHasGzipHeader(const base::MemoryMappedFile * mmap)160 bool MmapHasGzipHeader(const base::MemoryMappedFile* mmap) {
161   net::GZipHeader header;
162   const char* header_end = nullptr;
163   net::GZipHeader::Status header_status = header.ReadMore(
164       reinterpret_cast<const char*>(mmap->data()), mmap->length(), &header_end);
165   return header_status == net::GZipHeader::COMPLETE_HEADER;
166 }
167 
byteswap(uint16_t v)168 static inline uint16_t byteswap(uint16_t v) { return __builtin_bswap16(v); }
byteswap(uint32_t v)169 static inline uint32_t byteswap(uint32_t v) { return __builtin_bswap32(v); }
170 
171 template<class T> class byteswapped_t {
172 private:
173   T value;
174 public:
byteswapped_t(T v)175   inline byteswapped_t(T v) : value(byteswap(v)) { }
operator =(T v)176   inline T operator=(T v) { value = byteswap(v); return v; }
operator +=(T v)177   inline T operator+=(T v) { v += byteswap(value); value = byteswap(v); return v; }
operator T() const178   inline operator T() const { return byteswap(value); }
179 };
180 
181 #if !defined(ARCH_CPU_LITTLE_ENDIAN)
182 
183 typedef byteswapped_t<uint16_t> uint16le_t;
184 typedef byteswapped_t<uint32_t> uint32le_t;
185 
186 #else
187 
188 typedef uint16_t uint16le_t;
189 typedef uint32_t uint32le_t;
190 
191 #endif
192 
193 }  // namespace
194 
195 namespace ui {
196 
197 #pragma pack(push, 2)
198 struct DataPack::Entry {
199   uint16le_t resource_id;
200   uint32le_t file_offset;
201 
CompareByIdui::DataPack::Entry202   static int CompareById(const void* void_key, const void* void_entry) {
203     // Key is host endian
204     uint16_t key = *reinterpret_cast<const uint16_t*>(void_key);
205     const Entry* entry = reinterpret_cast<const Entry*>(void_entry);
206     return key - entry->resource_id;
207   }
208 };
209 
210 struct DataPack::Alias {
211   uint16le_t resource_id;
212   uint16le_t entry_index;
213 
CompareByIdui::DataPack::Alias214   static int CompareById(const void* void_key, const void* void_entry) {
215     // Key is host endian
216     uint16_t key = *reinterpret_cast<const uint16_t*>(void_key);
217     const Alias* entry = reinterpret_cast<const Alias*>(void_entry);
218     return key - entry->resource_id;
219   }
220 };
221 #pragma pack(pop)
222 
223 // Abstraction of a data source (memory mapped file or in-memory buffer).
224 class DataPack::DataSource {
225  public:
~DataSource()226   virtual ~DataSource() {}
227 
228   virtual size_t GetLength() const = 0;
229   virtual const uint8_t* GetData() const = 0;
230 };
231 
232 class DataPack::MemoryMappedDataSource : public DataPack::DataSource {
233  public:
MemoryMappedDataSource(std::unique_ptr<base::MemoryMappedFile> mmap)234   explicit MemoryMappedDataSource(std::unique_ptr<base::MemoryMappedFile> mmap)
235       : mmap_(std::move(mmap)) {}
236 
~MemoryMappedDataSource()237   ~MemoryMappedDataSource() override {}
238 
239   // DataPack::DataSource:
GetLength() const240   size_t GetLength() const override { return mmap_->length(); }
GetData() const241   const uint8_t* GetData() const override { return mmap_->data(); }
242 
243  private:
244   std::unique_ptr<base::MemoryMappedFile> mmap_;
245 
246   DISALLOW_COPY_AND_ASSIGN(MemoryMappedDataSource);
247 };
248 
249 // Takes ownership of a string of uncompressed pack data.
250 class DataPack::StringDataSource : public DataPack::DataSource {
251  public:
StringDataSource(std::string && data)252   explicit StringDataSource(std::string&& data) : data_(std::move(data)) {}
253 
~StringDataSource()254   ~StringDataSource() override {}
255 
256   // DataPack::DataSource:
GetLength() const257   size_t GetLength() const override { return data_.size(); }
GetData() const258   const uint8_t* GetData() const override {
259     return reinterpret_cast<const uint8_t*>(data_.c_str());
260   }
261 
262  private:
263   const std::string data_;
264 
265   DISALLOW_COPY_AND_ASSIGN(StringDataSource);
266 };
267 
268 class DataPack::BufferDataSource : public DataPack::DataSource {
269  public:
BufferDataSource(base::StringPiece buffer)270   explicit BufferDataSource(base::StringPiece buffer) : buffer_(buffer) {}
271 
~BufferDataSource()272   ~BufferDataSource() override {}
273 
274   // DataPack::DataSource:
GetLength() const275   size_t GetLength() const override { return buffer_.length(); }
GetData() const276   const uint8_t* GetData() const override {
277     return reinterpret_cast<const uint8_t*>(buffer_.data());
278   }
279 
280  private:
281   base::StringPiece buffer_;
282 
283   DISALLOW_COPY_AND_ASSIGN(BufferDataSource);
284 };
285 
DataPack(ui::ScaleFactor scale_factor)286 DataPack::DataPack(ui::ScaleFactor scale_factor)
287     : resource_table_(nullptr),
288       resource_count_(0),
289       alias_table_(nullptr),
290       alias_count_(0),
291       text_encoding_type_(BINARY),
292       scale_factor_(scale_factor) {
293   // Static assert must be within a DataPack member to appease visiblity rules.
294   static_assert(sizeof(Entry) == 6, "size of Entry must be 6");
295   static_assert(sizeof(Alias) == 4, "size of Alias must be 4");
296 }
297 
~DataPack()298 DataPack::~DataPack() {
299 }
300 
LoadFromPath(const base::FilePath & path)301 bool DataPack::LoadFromPath(const base::FilePath& path) {
302   std::unique_ptr<base::MemoryMappedFile> mmap =
303       std::make_unique<base::MemoryMappedFile>();
304   if (!mmap->Initialize(path)) {
305     DLOG(ERROR) << "Failed to mmap datapack";
306     LogDataPackError(INIT_FAILED);
307     return false;
308   }
309   if (MmapHasGzipHeader(mmap.get())) {
310     base::StringPiece compressed(reinterpret_cast<char*>(mmap->data()),
311                                  mmap->length());
312     std::string data;
313     if (!compression::GzipUncompress(compressed, &data)) {
314       LOG(ERROR) << "Failed to unzip compressed datapack: " << path;
315       LogDataPackError(UNZIP_FAILED);
316       return false;
317     }
318     return LoadImpl(std::make_unique<StringDataSource>(std::move(data)));
319   }
320   return LoadImpl(std::make_unique<MemoryMappedDataSource>(std::move(mmap)));
321 }
322 
LoadFromFile(base::File file)323 bool DataPack::LoadFromFile(base::File file) {
324   return LoadFromFileRegion(std::move(file),
325                             base::MemoryMappedFile::Region::kWholeFile);
326 }
327 
LoadFromFileRegion(base::File file,const base::MemoryMappedFile::Region & region)328 bool DataPack::LoadFromFileRegion(
329     base::File file,
330     const base::MemoryMappedFile::Region& region) {
331   std::unique_ptr<base::MemoryMappedFile> mmap =
332       std::make_unique<base::MemoryMappedFile>();
333   if (!mmap->Initialize(std::move(file), region)) {
334     DLOG(ERROR) << "Failed to mmap datapack";
335     LogDataPackError(INIT_FAILED_FROM_FILE);
336     mmap.reset();
337     return false;
338   }
339   return LoadImpl(std::make_unique<MemoryMappedDataSource>(std::move(mmap)));
340 }
341 
LoadFromBuffer(base::StringPiece buffer)342 bool DataPack::LoadFromBuffer(base::StringPiece buffer) {
343   return LoadImpl(std::make_unique<BufferDataSource>(buffer));
344 }
345 
LoadImpl(std::unique_ptr<DataPack::DataSource> data_source)346 bool DataPack::LoadImpl(std::unique_ptr<DataPack::DataSource> data_source) {
347   const uint8_t* data = data_source->GetData();
348   size_t data_length = data_source->GetLength();
349   // Parse the version and check for truncated header.
350   uint32_t version = 0;
351   if (data_length > sizeof(version))
352     version = reinterpret_cast<const uint32le_t*>(data)[0];
353   size_t header_length =
354       version == kFileFormatV4 ? kHeaderLengthV4 : kHeaderLengthV5;
355   if (version == 0 || data_length < header_length) {
356     DLOG(ERROR) << "Data pack file corruption: incomplete file header.";
357     LogDataPackError(HEADER_TRUNCATED);
358     return false;
359   }
360 
361   // Parse the header of the file.
362   if (version == kFileFormatV4) {
363     resource_count_ = reinterpret_cast<const uint32le_t*>(data)[1];
364     alias_count_ = 0;
365     text_encoding_type_ = static_cast<TextEncodingType>(data[8]);
366   } else if (version == kFileFormatV5) {
367     // Version 5 added the alias table and changed the header format.
368     text_encoding_type_ = static_cast<TextEncodingType>(data[4]);
369     resource_count_ = reinterpret_cast<const uint16le_t*>(data)[4];
370     alias_count_ = reinterpret_cast<const uint16le_t*>(data)[5];
371   } else {
372     LOG(ERROR) << "Bad data pack version: got " << version << ", expected "
373                << kFileFormatV4 << " or " << kFileFormatV5;
374     LogDataPackError(BAD_VERSION);
375     return false;
376   }
377 
378   if (text_encoding_type_ != UTF8 && text_encoding_type_ != UTF16 &&
379       text_encoding_type_ != BINARY) {
380     LOG(ERROR) << "Bad data pack text encoding: got " << text_encoding_type_
381                << ", expected between " << BINARY << " and " << UTF16;
382     LogDataPackError(WRONG_ENCODING);
383     return false;
384   }
385 
386   // Sanity check the file.
387   // 1) Check we have enough entries. There's an extra entry after the last item
388   // which gives the length of the last item.
389   size_t resource_table_size = (resource_count_ + 1) * sizeof(Entry);
390   size_t alias_table_size = alias_count_ * sizeof(Alias);
391   if (header_length + resource_table_size + alias_table_size > data_length) {
392     LOG(ERROR) << "Data pack file corruption: "
393                << "too short for number of entries.";
394     LogDataPackError(INDEX_TRUNCATED);
395     return false;
396   }
397 
398   resource_table_ = reinterpret_cast<const Entry*>(&data[header_length]);
399   alias_table_ = reinterpret_cast<const Alias*>(
400       &data[header_length + resource_table_size]);
401 
402   // 2) Verify the entries are within the appropriate bounds. There's an extra
403   // entry after the last item which gives us the length of the last item.
404   for (size_t i = 0; i < resource_count_ + 1; ++i) {
405     if (resource_table_[i].file_offset > data_length) {
406       LOG(ERROR) << "Data pack file corruption: "
407                  << "Entry #" << i << " past end.";
408       LogDataPackError(ENTRY_NOT_FOUND);
409       return false;
410     }
411   }
412 
413   // 3) Verify the aliases are within the appropriate bounds.
414   for (size_t i = 0; i < alias_count_; ++i) {
415     if (alias_table_[i].entry_index >= resource_count_) {
416       LOG(ERROR) << "Data pack file corruption: "
417                  << "Alias #" << i << " past end.";
418       LogDataPackError(ENTRY_NOT_FOUND);
419       return false;
420     }
421   }
422 
423   data_source_ = std::move(data_source);
424   return true;
425 }
426 
LookupEntryById(uint16_t resource_id) const427 const DataPack::Entry* DataPack::LookupEntryById(uint16_t resource_id) const {
428   // Search the resource table first as most resources will be in there.
429   const Entry* ret = reinterpret_cast<const Entry*>(
430       bsearch(&resource_id, resource_table_, resource_count_, sizeof(Entry),
431               Entry::CompareById));
432   if (ret == nullptr) {
433     // Search the alias table for the ~10% of entries which are aliases.
434     const Alias* alias = reinterpret_cast<const Alias*>(
435         bsearch(&resource_id, alias_table_, alias_count_, sizeof(Alias),
436                 Alias::CompareById));
437     if (alias != nullptr) {
438       ret = &resource_table_[alias->entry_index];
439     }
440   }
441   return ret;
442 }
443 
HasResource(uint16_t resource_id) const444 bool DataPack::HasResource(uint16_t resource_id) const {
445   return !!LookupEntryById(resource_id);
446 }
447 
GetStringPiece(uint16_t resource_id,base::StringPiece * data) const448 bool DataPack::GetStringPiece(uint16_t resource_id,
449                               base::StringPiece* data) const {
450   const Entry* target = LookupEntryById(resource_id);
451   if (!target)
452     return false;
453 
454   const Entry* next_entry = target + 1;
455   // If the next entry points beyond the end of the file this data pack's entry
456   // table is corrupt. Log an error and return false. See
457   // http://crbug.com/371301.
458   size_t entry_offset =
459       reinterpret_cast<const uint8_t*>(next_entry) - data_source_->GetData();
460   size_t pak_size = data_source_->GetLength();
461   if (entry_offset > pak_size || next_entry->file_offset > pak_size) {
462     size_t entry_index = target - resource_table_;
463     LOG(ERROR) << "Entry #" << entry_index << " in data pack points off end "
464                << "of file. This should have been caught when loading. Was the "
465                << "file modified?";
466     return false;
467   }
468 
469   MaybePrintResourceId(resource_id);
470   size_t length = next_entry->file_offset - target->file_offset;
471   *data = base::StringPiece(reinterpret_cast<const char*>(
472                                 data_source_->GetData() + target->file_offset),
473                             length);
474   return true;
475 }
476 
GetStaticMemory(uint16_t resource_id) const477 base::RefCountedStaticMemory* DataPack::GetStaticMemory(
478     uint16_t resource_id) const {
479   base::StringPiece piece;
480   if (!GetStringPiece(resource_id, &piece))
481     return NULL;
482 
483   return new base::RefCountedStaticMemory(piece.data(), piece.length());
484 }
485 
GetTextEncodingType() const486 ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const {
487   return text_encoding_type_;
488 }
489 
GetScaleFactor() const490 ui::ScaleFactor DataPack::GetScaleFactor() const {
491   return scale_factor_;
492 }
493 
494 #if DCHECK_IS_ON()
CheckForDuplicateResources(const std::vector<std::unique_ptr<ResourceHandle>> & packs)495 void DataPack::CheckForDuplicateResources(
496     const std::vector<std::unique_ptr<ResourceHandle>>& packs) {
497   for (size_t i = 0; i < resource_count_ + 1; ++i) {
498     const uint16_t resource_id = resource_table_[i].resource_id;
499     const float resource_scale = GetScaleForScaleFactor(scale_factor_);
500     for (const auto& handle : packs) {
501       if (GetScaleForScaleFactor(handle->GetScaleFactor()) != resource_scale)
502         continue;
503       DCHECK(!handle->HasResource(resource_id)) << "Duplicate resource "
504                                                 << resource_id << " with scale "
505                                                 << resource_scale;
506     }
507   }
508 }
509 #endif  // DCHECK_IS_ON()
510 
511 // static
WritePack(const base::FilePath & path,const std::map<uint16_t,base::StringPiece> & resources,TextEncodingType text_encoding_type)512 bool DataPack::WritePack(const base::FilePath& path,
513                          const std::map<uint16_t, base::StringPiece>& resources,
514                          TextEncodingType text_encoding_type) {
515   if (text_encoding_type != UTF8 && text_encoding_type != UTF16 &&
516       text_encoding_type != BINARY) {
517     LOG(ERROR) << "Invalid text encoding type, got " << text_encoding_type
518                << ", expected between " << BINARY << " and " << UTF16;
519     return false;
520   }
521 
522   size_t resources_count = resources.size();
523   if (static_cast<uint16_t>(resources_count) != resources_count) {
524     LOG(ERROR) << "Too many resources (" << resources_count << ")";
525     return false;
526   }
527 
528   ScopedFileWriter file(path);
529   if (!file.valid())
530     return false;
531 
532   uint32le_t encoding = static_cast<uint32le_t>(text_encoding_type);
533 
534   // Build a list of final resource aliases, and an alias map at the same time.
535   std::vector<uint16_t> resource_ids;
536   std::map<uint16_t, uint16_t> aliases;  // resource_id -> entry_index
537   if (resources_count > 0) {
538     // A reverse map from string pieces to the index of the corresponding
539     // original id in the final resource list.
540     std::map<base::StringPiece, uint16_t> rev_map;
541     for (const auto& entry : resources) {
542       auto it = rev_map.find(entry.second);
543       if (it != rev_map.end()) {
544         // Found an alias here!
545         aliases.emplace(entry.first, it->second);
546       } else {
547         // Found a final resource.
548         const auto entry_index = static_cast<uint16_t>(resource_ids.size());
549         rev_map.emplace(entry.second, entry_index);
550         resource_ids.push_back(entry.first);
551       }
552     }
553   }
554 
555   DCHECK(std::is_sorted(resource_ids.begin(), resource_ids.end()));
556 
557   // These values are guaranteed to fit in a uint16_t due to the earlier
558   // check of |resources_count|.
559   const uint16le_t alias_count = static_cast<uint16le_t>(aliases.size());
560   const uint16le_t entry_count = static_cast<uint16le_t>(resource_ids.size());
561   DCHECK_EQ(static_cast<size_t>(entry_count) + static_cast<size_t>(alias_count),
562             resources_count);
563 
564   uint32le_t version = kFileFormatV5;
565   file.Write(&version, sizeof(version));
566   file.Write(&encoding, sizeof(uint32le_t));
567   file.Write(&entry_count, sizeof(entry_count));
568   file.Write(&alias_count, sizeof(alias_count));
569 
570   // Each entry is a uint16_t + a uint32_t. We have an extra entry after the
571   // last item so we can compute the size of the list item.
572   const uint32_t index_length = (entry_count + 1) * sizeof(Entry);
573   const uint32_t alias_table_length = alias_count * sizeof(Alias);
574   uint32le_t data_offset = kHeaderLengthV5 + index_length + alias_table_length;
575   for (const uint16le_t resource_id : resource_ids) {
576     file.Write(&resource_id, sizeof(resource_id));
577     file.Write(&data_offset, sizeof(data_offset));
578     data_offset += resources.find(resource_id)->second.length();
579   }
580 
581   // We place an extra entry after the last item that allows us to read the
582   // size of the last item.
583   const uint16le_t resource_id = 0;
584   file.Write(&resource_id, sizeof(resource_id));
585   file.Write(&data_offset, sizeof(data_offset));
586 
587   // Write the aliases table, if any. Note: |aliases| is an std::map,
588   // ensuring values are written in increasing order.
589   for (const std::pair<const uint16le_t, uint16le_t>& alias : aliases) {
590     file.Write(&alias, sizeof(alias));
591   }
592 
593   for (const auto& resource_id : resource_ids) {
594     const base::StringPiece data = resources.find(resource_id)->second;
595     file.Write(data.data(), data.length());
596   }
597 
598   return file.Close();
599 }
600 
601 }  // namespace ui
602