Home | History | Annotate | Download | only in resource
      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/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/linuxresourcesandlocalizedstrings
     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::PlatformFile file) {
     88   mmap_.reset(new base::MemoryMappedFile);
     89   if (!mmap_->Initialize(file)) {
     90     DLOG(ERROR) << "Failed to mmap datapack";
     91     UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INIT_FAILED_FROM_FILE,
     92                               LOAD_ERRORS_COUNT);
     93     mmap_.reset();
     94     return false;
     95   }
     96   return LoadImpl();
     97 }
     98 
     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,
    104                               LOAD_ERRORS_COUNT);
    105     mmap_.reset();
    106     return false;
    107   }
    108 
    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,
    117                               LOAD_ERRORS_COUNT);
    118     mmap_.reset();
    119     return false;
    120   }
    121   resource_count_ = ptr[1];
    122 
    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,
    131                               LOAD_ERRORS_COUNT);
    132     mmap_.reset();
    133     return false;
    134   }
    135 
    136   // Sanity check the file.
    137   // 1) Check we have enough entries.
    138   if (kHeaderLength + resource_count_ * sizeof(DataPackEntry) >
    139       mmap_->length()) {
    140     LOG(ERROR) << "Data pack file corruption: too short for number of "
    141                   "entries specified.";
    142     UMA_HISTOGRAM_ENUMERATION("DataPack.Load", INDEX_TRUNCATED,
    143                               LOAD_ERRORS_COUNT);
    144     mmap_.reset();
    145     return false;
    146   }
    147   // 2) Verify the entries are within the appropriate bounds. There's an extra
    148   // entry after the last item which gives us the length of the last item.
    149   for (size_t i = 0; i < resource_count_ + 1; ++i) {
    150     const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>(
    151         mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry)));
    152     if (entry->file_offset > mmap_->length()) {
    153       LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. "
    154                  << "Was the file corrupted?";
    155       UMA_HISTOGRAM_ENUMERATION("DataPack.Load", ENTRY_NOT_FOUND,
    156                                 LOAD_ERRORS_COUNT);
    157       mmap_.reset();
    158       return false;
    159     }
    160   }
    161 
    162   return true;
    163 }
    164 
    165 bool DataPack::HasResource(uint16 resource_id) const {
    166   return !!bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
    167                    sizeof(DataPackEntry), DataPackEntry::CompareById);
    168 }
    169 
    170 bool DataPack::GetStringPiece(uint16 resource_id,
    171                               base::StringPiece* data) const {
    172   // It won't be hard to make this endian-agnostic, but it's not worth
    173   // bothering to do right now.
    174 #if defined(__BYTE_ORDER)
    175   // Linux check
    176   COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
    177                  datapack_assumes_little_endian);
    178 #elif defined(__BIG_ENDIAN__)
    179   // Mac check
    180   #error DataPack assumes little endian
    181 #endif
    182 
    183   const DataPackEntry* target = reinterpret_cast<const DataPackEntry*>(
    184       bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
    185               sizeof(DataPackEntry), DataPackEntry::CompareById));
    186   if (!target) {
    187     return false;
    188   }
    189 
    190   const DataPackEntry* next_entry = target + 1;
    191   size_t length = next_entry->file_offset - target->file_offset;
    192 
    193   data->set(mmap_->data() + target->file_offset, length);
    194   return true;
    195 }
    196 
    197 base::RefCountedStaticMemory* DataPack::GetStaticMemory(
    198     uint16 resource_id) const {
    199   base::StringPiece piece;
    200   if (!GetStringPiece(resource_id, &piece))
    201     return NULL;
    202 
    203   return new base::RefCountedStaticMemory(
    204       reinterpret_cast<const unsigned char*>(piece.data()), piece.length());
    205 }
    206 
    207 ResourceHandle::TextEncodingType DataPack::GetTextEncodingType() const {
    208   return text_encoding_type_;
    209 }
    210 
    211 ui::ScaleFactor DataPack::GetScaleFactor() const {
    212   return scale_factor_;
    213 }
    214 
    215 // static
    216 bool DataPack::WritePack(const base::FilePath& path,
    217                          const std::map<uint16, base::StringPiece>& resources,
    218                          TextEncodingType textEncodingType) {
    219   FILE* file = file_util::OpenFile(path, "wb");
    220   if (!file)
    221     return false;
    222 
    223   if (fwrite(&kFileFormatVersion, sizeof(kFileFormatVersion), 1, file) != 1) {
    224     LOG(ERROR) << "Failed to write file version";
    225     file_util::CloseFile(file);
    226     return false;
    227   }
    228 
    229   // Note: the python version of this function explicitly sorted keys, but
    230   // std::map is a sorted associative container, we shouldn't have to do that.
    231   uint32 entry_count = resources.size();
    232   if (fwrite(&entry_count, sizeof(entry_count), 1, file) != 1) {
    233     LOG(ERROR) << "Failed to write entry count";
    234     file_util::CloseFile(file);
    235     return false;
    236   }
    237 
    238   if (textEncodingType != UTF8 && textEncodingType != UTF16 &&
    239       textEncodingType != BINARY) {
    240     LOG(ERROR) << "Invalid text encoding type, got " << textEncodingType
    241                << ", expected between " << BINARY << " and " << UTF16;
    242     file_util::CloseFile(file);
    243     return false;
    244   }
    245 
    246   uint8 write_buffer = textEncodingType;
    247   if (fwrite(&write_buffer, sizeof(uint8), 1, file) != 1) {
    248     LOG(ERROR) << "Failed to write file text resources encoding";
    249     file_util::CloseFile(file);
    250     return false;
    251   }
    252 
    253   // Each entry is a uint16 + a uint32. We have an extra entry after the last
    254   // item so we can compute the size of the list item.
    255   uint32 index_length = (entry_count + 1) * sizeof(DataPackEntry);
    256   uint32 data_offset = kHeaderLength + index_length;
    257   for (std::map<uint16, base::StringPiece>::const_iterator it =
    258            resources.begin();
    259        it != resources.end(); ++it) {
    260     uint16 resource_id = it->first;
    261     if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
    262       LOG(ERROR) << "Failed to write id for " << resource_id;
    263       file_util::CloseFile(file);
    264       return false;
    265     }
    266 
    267     if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
    268       LOG(ERROR) << "Failed to write offset for " << resource_id;
    269       file_util::CloseFile(file);
    270       return false;
    271     }
    272 
    273     data_offset += it->second.length();
    274   }
    275 
    276   // We place an extra entry after the last item that allows us to read the
    277   // size of the last item.
    278   uint16 resource_id = 0;
    279   if (fwrite(&resource_id, sizeof(resource_id), 1, file) != 1) {
    280     LOG(ERROR) << "Failed to write extra resource id.";
    281     file_util::CloseFile(file);
    282     return false;
    283   }
    284 
    285   if (fwrite(&data_offset, sizeof(data_offset), 1, file) != 1) {
    286     LOG(ERROR) << "Failed to write extra offset.";
    287     file_util::CloseFile(file);
    288     return false;
    289   }
    290 
    291   for (std::map<uint16, base::StringPiece>::const_iterator it =
    292            resources.begin();
    293        it != resources.end(); ++it) {
    294     if (fwrite(it->second.data(), it->second.length(), 1, file) != 1) {
    295       LOG(ERROR) << "Failed to write data for " << it->first;
    296       file_util::CloseFile(file);
    297       return false;
    298     }
    299   }
    300 
    301   file_util::CloseFile(file);
    302 
    303   return true;
    304 }
    305 
    306 }  // namespace ui
    307