From 9a9df873c619cd08ac6fb14a510135f29d8cccd1 Mon Sep 17 00:00:00 2001
From: "evan@chromium.org"
 <evan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Thu, 8 Jan 2009 19:51:09 +0000
Subject: Data pack file reader and unit test, used for resources on Linux.

See http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings for more details.

Review URL: http://codereview.chromium.org/17253


git-svn-id: svn://svn.chromium.org/chrome/trunk/src@7751 0039d316-1c4b-4281-b951-d872f2087c98
---
 base/base_lib.scons                     |   1 +
 base/base_unittests.scons               |   5 ++
 base/data/data_pack_unittest/sample.pak | Bin 0 -> 80 bytes
 base/data_pack.cc                       | 109 ++++++++++++++++++++++++++++++++
 base/data_pack.h                        |  46 ++++++++++++++
 base/data_pack_unittest.cc              |  42 ++++++++++++
 6 files changed, 203 insertions(+)
 create mode 100644 base/data/data_pack_unittest/sample.pak
 create mode 100644 base/data_pack.cc
 create mode 100644 base/data_pack.h
 create mode 100644 base/data_pack_unittest.cc

diff --git a/base/base_lib.scons b/base/base_lib.scons
index 976a899..0dd3673 100644
--- a/base/base_lib.scons
+++ b/base/base_lib.scons
@@ -361,6 +361,7 @@ if env.Bit('linux'):
       'atomicops_internals_x86_gcc.cc',
       'base_paths_linux.cc',
       'clipboard_linux.cc',
+      'data_pack.cc',
       'file_util_linux.cc',
       'file_version_info_linux.cc',
       'hmac_nss.cc',
diff --git a/base/base_unittests.scons b/base/base_unittests.scons
index 66be1f3..4b0c2e8 100644
--- a/base/base_unittests.scons
+++ b/base/base_unittests.scons
@@ -131,6 +131,11 @@ if env.Bit('posix'):
       'gfx/native_theme_unittest.cc',
   )
 
+if env.Bit('linux'):
+  input_files.Append(
+      'data_pack_unittest.cc',
+  )
+
 if env.Bit('mac'):
   # Remove files that still need to be ported from the input_files list.
   # TODO(port): delete files from this list as they get ported.
diff --git a/base/data/data_pack_unittest/sample.pak b/base/data/data_pack_unittest/sample.pak
new file mode 100644
index 0000000..fdbe2b5
Binary files /dev/null and b/base/data/data_pack_unittest/sample.pak differ
diff --git a/base/data_pack.cc b/base/data_pack.cc
new file mode 100644
index 0000000..17db8c4
--- /dev/null
+++ b/base/data_pack.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/data_pack.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_piece.h"
+
+// For details of the file layout, see
+// http://dev.chromium.org/developers/design-documents/linuxresourcesandlocalizedstrings
+
+namespace {
+static const uint32_t kFileFormatVersion = 1;
+// Length of file header: version and entry count.
+static const size_t kHeaderLength = 2 * sizeof(uint32_t);
+
+struct DataPackEntry {
+  uint32_t resource_id;
+  uint32_t file_offset;
+  uint32_t length;
+
+  static int CompareById(const void* void_key, const void* void_entry) {
+    uint32_t key = *reinterpret_cast<const uint32_t*>(void_key);
+    const DataPackEntry* entry =
+        reinterpret_cast<const DataPackEntry*>(void_entry);
+    if (key < entry->resource_id) {
+      return -1;
+    } else if (key > entry->resource_id) {
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+}  __attribute((packed));
+
+}  // anonymous namespace
+
+namespace base {
+
+// In .cc for MemoryMappedFile dtor.
+DataPack::DataPack() : resource_count_(0) {
+}
+DataPack::~DataPack() {
+}
+
+bool DataPack::Load(const FilePath& path) {
+  mmap_.reset(new file_util::MemoryMappedFile);
+  if (!mmap_->Initialize(path)) {
+    mmap_.reset();
+    return false;
+  }
+
+  // Parse the header of the file.
+  // First uint32_t: version; second: resource count.
+  const uint32* ptr = reinterpret_cast<const uint32_t*>(mmap_->data());
+  uint32 version = ptr[0];
+  if (version != kFileFormatVersion) {
+    LOG(ERROR) << "Bad data pack version: got " << version << ", expected "
+               << kFileFormatVersion;
+    mmap_.reset();
+    return false;
+  }
+  resource_count_ = ptr[1];
+
+  // Sanity check the file.
+  // 1) Check we have enough entries.
+  if (kHeaderLength + resource_count_ * sizeof(DataPackEntry) >
+      mmap_->length()) {
+    LOG(ERROR) << "Data pack file corruption: too short for number of "
+                  "entries specified.";
+    mmap_.reset();
+    return false;
+  }
+  // 2) Verify the entries are within the appropriate bounds.
+  for (size_t i = 0; i < resource_count_; ++i) {
+    const DataPackEntry* entry = reinterpret_cast<const DataPackEntry*>(
+        mmap_->data() + kHeaderLength + (i * sizeof(DataPackEntry)));
+    if (entry->file_offset + entry->length > mmap_->length()) {
+      LOG(ERROR) << "Entry #" << i << " in data pack points off end of file. "
+                 << "Was the file corrupted?";
+      mmap_.reset();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool DataPack::Get(uint32_t resource_id, StringPiece* data) {
+  // It won't be hard to make this endian-agnostic, but it's not worth
+  // bothering to do right now.
+  COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN,
+                 datapack_assumes_little_endian);
+
+  DataPackEntry* target = reinterpret_cast<DataPackEntry*>(
+      bsearch(&resource_id, mmap_->data() + kHeaderLength, resource_count_,
+              sizeof(DataPackEntry), DataPackEntry::CompareById));
+  if (!target) {
+    LOG(ERROR) << "No resource found with id: " << resource_id;
+    return false;
+  }
+
+  data->set(mmap_->data() + target->file_offset, target->length);
+  return true;
+}
+
+}  // namespace base
diff --git a/base/data_pack.h b/base/data_pack.h
new file mode 100644
index 0000000..1836552
--- /dev/null
+++ b/base/data_pack.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// DataPack represents a read-only view onto an on-disk file that contains
+// (key, value) pairs of data.  It's used to store static resources like
+// translation strings and images.
+
+#ifndef BASE_DATA_PACK_H_
+#define BASE_DATA_PACK_H_
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+
+namespace file_util {
+  class MemoryMappedFile;
+}
+class FilePath;
+class StringPiece;
+
+namespace base {
+
+class DataPack {
+ public:
+  DataPack();
+  ~DataPack();
+
+  // Load a pack file from |path|, returning false on error.
+  bool Load(const FilePath& path);
+
+  // Get resource by id |resource_id|, filling in |data|.
+  // The data is owned by the DataPack object and should not be modified.
+  // Returns false if the resource id isn't found.
+  bool Get(uint32_t resource_id, StringPiece* data);
+
+ private:
+  // The memory-mapped data.
+  scoped_ptr<file_util::MemoryMappedFile> mmap_;
+
+  // Number of resources in the data.
+  size_t resource_count_;
+};
+
+}  // namespace base
+
+#endif  // BASE_DATA_PACK_H_
diff --git a/base/data_pack_unittest.cc b/base/data_pack_unittest.cc
new file mode 100644
index 0000000..1f6b264
--- /dev/null
+++ b/base/data_pack_unittest.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/data_pack.h"
+
+#include "base/file_path.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/string_piece.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class DataPackTest : public testing::Test {
+ public:
+  DataPackTest() {
+    PathService::Get(base::DIR_SOURCE_ROOT, &data_path_);
+    data_path_ = data_path_.Append(
+        FILE_PATH_LITERAL("base/data/data_pack_unittest/sample.pak"));
+  }
+
+  FilePath data_path_;
+};
+
+TEST_F(DataPackTest, Load) {
+  base::DataPack pack;
+  ASSERT_TRUE(pack.Load(data_path_));
+
+  StringPiece data;
+  ASSERT_TRUE(pack.Get(4, &data));
+  EXPECT_EQ("this is id 4", data);
+  ASSERT_TRUE(pack.Get(6, &data));
+  EXPECT_EQ("this is id 6", data);
+
+  // Try reading zero-length data blobs, just in case.
+  ASSERT_TRUE(pack.Get(1, &data));
+  EXPECT_EQ(0U, data.length());
+  ASSERT_TRUE(pack.Get(10, &data));
+  EXPECT_EQ(0U, data.length());
+
+  // Try looking up an invalid key.
+  ASSERT_FALSE(pack.Get(140, &data));
+}
-- 
cgit v1.1