Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d853f77e authored by Tom Cherry's avatar Tom Cherry
Browse files

Parse property contexts via a serialized trie

Currently, whenever a new program starts, libc initializes two data
structures related to properties from the raw property_context files.
There are two problems here,
1) This takes roughly 1.2ms on a trivial program to generate contents
   that could otherwise be cached.
2) One of the data structures is a descending list of prefixes, each
   of which needs to be checked, whereas a trie would be more
   efficient.

This change introduces two libraries,
1) libpropertycontextserializer meant to be used by property_service
   to create a serialized trie containing all of the property
   contexts.
2) libpropertycontextparser meant to be used by libc's property
   functions to parse this serialized trie during property lookup.

This new trie also contains the ability to have exact matches instead
of prefix matches for properties, which was not possible before.

Bug: 36001741
Change-Id: I42324f04c4d995a0e055e9685d79f40393dfca51
parent d7edcc9b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
../.clang-format-2
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
subdirs = ["*"]
+16 −0
Original line number Diff line number Diff line
cc_library_static {
    name: "libpropertyinfoparser",
    srcs: ["property_info_parser.cpp"],

    cpp_std: "experimental",
    sanitize: {
        misc_undefined: ["signed-integer-overflow"],
    },
    cppflags: [
        "-Wall",
        "-Wextra",
        "-Werror",
    ],
    stl: "none",
    export_include_dirs: ["include"],
}
+221 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef PROPERTY_INFO_PARSER_H
#define PROPERTY_INFO_PARSER_H

#include <stdint.h>

namespace android {
namespace properties {

// The below structs intentionally do not end with char name[0] or other tricks to allocate
// with a dynamic size, such that they can be added onto in the future without breaking
// backwards compatibility.
struct PropertyEntry {
  uint32_t name_offset;
  uint32_t namelen;

  // This is the context match for this node_; ~0u if it doesn't correspond to any.
  uint32_t context_index;
  // This is the schema for this node_; ~0u if it doesn't correspond to any.
  uint32_t schema_index;
};

struct TrieNodeInternal {
  // This points to a property entry struct, which includes the name for this node
  uint32_t property_entry;

  // Children are a sorted list of child nodes_; binary search them.
  uint32_t num_child_nodes;
  uint32_t child_nodes;

  // Prefixes are terminating prefix matches at this node, sorted longest to smallest
  // Take the first match sequentially found with StartsWith().
  uint32_t num_prefixes;
  uint32_t prefix_entries;

  // Exact matches are a sorted list of exact matches at this node_; binary search them.
  uint32_t num_exact_matches;
  uint32_t exact_match_entries;
};

struct PropertyInfoAreaHeader {
  // The current version of this data as created by property service.
  uint32_t current_version;
  // The lowest version of libc that can properly parse this data.
  uint32_t minimum_supported_version;
  uint32_t size;
  uint32_t contexts_offset;
  uint32_t schemas_offset;
  uint32_t root_offset;
};

class SerializedData {
 public:
  uint32_t size() const {
    return reinterpret_cast<const PropertyInfoAreaHeader*>(data_base_)->size;
  }

  const char* c_string(uint32_t offset) const {
    if (offset != 0 && offset > size()) return nullptr;
    return static_cast<const char*>(data_base_ + offset);
  }

  const uint32_t* uint32_array(uint32_t offset) const {
    if (offset != 0 && offset > size()) return nullptr;
    return reinterpret_cast<const uint32_t*>(data_base_ + offset);
  }

  uint32_t uint32(uint32_t offset) const {
    if (offset != 0 && offset > size()) return ~0u;
    return *reinterpret_cast<const uint32_t*>(data_base_ + offset);
  }

  const char* data_base() const { return data_base_; }

 private:
  const char data_base_[0];
};

class TrieNode {
 public:
  TrieNode() : serialized_data_(nullptr), trie_node_base_(nullptr) {}
  TrieNode(const SerializedData* data_base, const TrieNodeInternal* trie_node_base)
      : serialized_data_(data_base), trie_node_base_(trie_node_base) {}

  const char* name() const {
    return serialized_data_->c_string(node_property_entry()->name_offset);
  }

  uint32_t context_index() const { return node_property_entry()->context_index; }
  uint32_t schema_index() const { return node_property_entry()->schema_index; }

  uint32_t num_child_nodes() const { return trie_node_base_->num_child_nodes; }
  TrieNode child_node(int n) const {
    uint32_t child_node_offset = serialized_data_->uint32_array(trie_node_base_->child_nodes)[n];
    const TrieNodeInternal* trie_node_base =
        reinterpret_cast<const TrieNodeInternal*>(serialized_data_->data_base() + child_node_offset);
    return TrieNode(serialized_data_, trie_node_base);
  }

  bool FindChildForString(const char* input, uint32_t namelen, TrieNode* child) const;

  uint32_t num_prefixes() const { return trie_node_base_->num_prefixes; }
  const PropertyEntry* prefix(int n) const {
    uint32_t prefix_entry_offset =
        serialized_data_->uint32_array(trie_node_base_->prefix_entries)[n];
    return reinterpret_cast<const PropertyEntry*>(serialized_data_->data_base() +
                                                  prefix_entry_offset);
  }

  uint32_t num_exact_matches() const { return trie_node_base_->num_exact_matches; }
  const PropertyEntry* exact_match(int n) const {
    uint32_t exact_match_entry_offset =
        serialized_data_->uint32_array(trie_node_base_->exact_match_entries)[n];
    return reinterpret_cast<const PropertyEntry*>(serialized_data_->data_base() +
                                                  exact_match_entry_offset);
  }

 private:
  const PropertyEntry* node_property_entry() const {
    return reinterpret_cast<const PropertyEntry*>(serialized_data_->data_base() +
                                                  trie_node_base_->property_entry);
  }

  const SerializedData* serialized_data_;
  const TrieNodeInternal* trie_node_base_;
};

class PropertyInfoArea : private SerializedData {
 public:
  void GetPropertyInfoIndexes(const char* name, uint32_t* context_index,
                              uint32_t* schema_index) const;
  void GetPropertyInfo(const char* property, const char** context, const char** schema) const;

  int FindContextIndex(const char* context) const;
  int FindSchemaIndex(const char* schema) const;

  const char* context(uint32_t index) const {
    uint32_t context_array_size_offset = contexts_offset();
    const uint32_t* context_array = uint32_array(context_array_size_offset + sizeof(uint32_t));
    return data_base() + context_array[index];
  }

  const char* schema(uint32_t index) const {
    uint32_t schema_array_size_offset = schemas_offset();
    const uint32_t* schema_array = uint32_array(schema_array_size_offset + sizeof(uint32_t));
    return data_base() + schema_array[index];
  }

  uint32_t current_version() const { return header()->current_version; }
  uint32_t minimum_supported_version() const { return header()->minimum_supported_version; }

  uint32_t size() const { return SerializedData::size(); }

  uint32_t num_contexts() const { return uint32_array(contexts_offset())[0]; }
  uint32_t num_schemas() const { return uint32_array(schemas_offset())[0]; }

  TrieNode root_node() const { return trie(header()->root_offset); }

 private:
  const PropertyInfoAreaHeader* header() const {
    return reinterpret_cast<const PropertyInfoAreaHeader*>(data_base());
  }
  uint32_t contexts_offset() const { return header()->contexts_offset; }
  uint32_t contexts_array_offset() const { return contexts_offset() + sizeof(uint32_t); }
  uint32_t schemas_offset() const { return header()->schemas_offset; }
  uint32_t schemas_array_offset() const { return schemas_offset() + sizeof(uint32_t); }

  TrieNode trie(uint32_t offset) const {
    if (offset != 0 && offset > size()) return TrieNode();
    const TrieNodeInternal* trie_node_base =
        reinterpret_cast<const TrieNodeInternal*>(data_base() + offset);
    return TrieNode(this, trie_node_base);
  }
};

// This is essentially a smart pointer for read only mmap region for property contexts.
class PropertyInfoAreaFile {
 public:
  PropertyInfoAreaFile() : mmap_base_(nullptr), mmap_size_(0) {}
  ~PropertyInfoAreaFile() { Reset(); }

  PropertyInfoAreaFile(const PropertyInfoAreaFile&) = delete;
  void operator=(const PropertyInfoAreaFile&) = delete;
  PropertyInfoAreaFile(PropertyInfoAreaFile&&) = default;
  PropertyInfoAreaFile& operator=(PropertyInfoAreaFile&&) = default;

  bool LoadDefaultPath();
  bool LoadPath(const char* filename);

  const PropertyInfoArea* operator->() const {
    return reinterpret_cast<const PropertyInfoArea*>(mmap_base_);
  }

  explicit operator bool() const { return mmap_base_ != nullptr; }

  void Reset();

 private:
  void* mmap_base_;
  size_t mmap_size_;
};

}  // namespace properties
}  // namespace android

#endif
+220 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "property_info_parser/property_info_parser.h"

#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

namespace android {
namespace properties {

namespace {

// Binary search to find index of element in an array compared via f(search).
template <typename F>
int Find(uint32_t array_length, F&& f) {
  int bottom = 0;
  int top = array_length - 1;
  while (top >= bottom) {
    int search = (top + bottom) / 2;

    auto cmp = f(search);

    if (cmp == 0) return search;
    if (cmp < 0) bottom = search + 1;
    if (cmp > 0) top = search - 1;
  }
  return -1;
}

}  // namespace

// Binary search the list of contexts to find the index of a given context string.
// Only should be used for TrieSerializer to construct the Trie.
int PropertyInfoArea::FindContextIndex(const char* context) const {
  return Find(num_contexts(), [this, context](auto array_offset) {
    auto string_offset = uint32_array(contexts_array_offset())[array_offset];
    return strcmp(c_string(string_offset), context);
  });
}

// Binary search the list of schemas to find the index of a given schema string.
// Only should be used for TrieSerializer to construct the Trie.
int PropertyInfoArea::FindSchemaIndex(const char* schema) const {
  return Find(num_schemas(), [this, schema](auto array_offset) {
    auto string_offset = uint32_array(schemas_array_offset())[array_offset];
    return strcmp(c_string(string_offset), schema);
  });
}

// Binary search the list of children nodes to find a TrieNode for a given property piece.
// Used to traverse the Trie in GetPropertyInfoIndexes().
bool TrieNode::FindChildForString(const char* name, uint32_t namelen, TrieNode* child) const {
  auto node_index = Find(trie_node_base_->num_child_nodes, [this, name, namelen](auto array_offset) {
    const char* child_name = child_node(array_offset).name();
    int cmp = strncmp(child_name, name, namelen);
    if (cmp == 0 && child_name[namelen] != '\0') {
      // We use strncmp() since name isn't null terminated, but we don't want to match only a
      // prefix of a child node's name, so we check here if we did only match a prefix and
      // return 1, to indicate to the binary search to search earlier in the array for the real
      // match.
      return 1;
    }
    return cmp;
  });

  if (node_index == -1) {
    return false;
  }
  *child = child_node(node_index);
  return true;
}

void PropertyInfoArea::GetPropertyInfoIndexes(const char* name, uint32_t* context_index,
                                              uint32_t* schema_index) const {
  uint32_t return_context_index = ~0u;
  uint32_t return_schema_index = ~0u;
  const char* remaining_name = name;
  auto trie_node = root_node();
  while (true) {
    const char* sep = strchr(remaining_name, '.');

    // Apply prefix match for prefix deliminated with '.'
    if (trie_node.context_index() != ~0u) {
      return_context_index = trie_node.context_index();
    }
    if (trie_node.schema_index() != ~0u) {
      return_schema_index = trie_node.schema_index();
    }

    if (sep == nullptr) {
      break;
    }

    const uint32_t substr_size = sep - remaining_name;
    TrieNode child_node;
    if (!trie_node.FindChildForString(remaining_name, substr_size, &child_node)) {
      break;
    }

    trie_node = child_node;
    remaining_name = sep + 1;
  }

  // We've made it to a leaf node, so check contents and return appropriately.
  // Check exact matches
  for (uint32_t i = 0; i < trie_node.num_exact_matches(); ++i) {
    if (!strcmp(c_string(trie_node.exact_match(i)->name_offset), remaining_name)) {
      if (context_index != nullptr) *context_index = trie_node.exact_match(i)->context_index;
      if (schema_index != nullptr) *schema_index = trie_node.exact_match(i)->schema_index;
      return;
    }
  }
  // Check prefix matches for prefixes not deliminated with '.'
  const uint32_t remaining_name_size = strlen(remaining_name);
  for (uint32_t i = 0; i < trie_node.num_prefixes(); ++i) {
    auto prefix_len = trie_node.prefix(i)->namelen;
    if (prefix_len > remaining_name_size) continue;

    if (!strncmp(c_string(trie_node.prefix(i)->name_offset), remaining_name, prefix_len)) {
      if (context_index != nullptr) *context_index = trie_node.prefix(i)->context_index;
      if (schema_index != nullptr) *schema_index = trie_node.prefix(i)->schema_index;
      return;
    }
  }
  // Return previously found '.' deliminated prefix match.
  if (context_index != nullptr) *context_index = return_context_index;
  if (schema_index != nullptr) *schema_index = return_schema_index;
  return;
}

void PropertyInfoArea::GetPropertyInfo(const char* property, const char** context,
                                       const char** schema) const {
  uint32_t context_index;
  uint32_t schema_index;
  GetPropertyInfoIndexes(property, &context_index, &schema_index);
  if (context != nullptr) {
    if (context_index == ~0u) {
      *context = nullptr;
    } else {
      *context = this->context(context_index);
    }
  }
  if (schema != nullptr) {
    if (schema_index == ~0u) {
      *schema = nullptr;
    } else {
      *schema = this->schema(schema_index);
    }
  }
}

bool PropertyInfoAreaFile::LoadDefaultPath() {
  return LoadPath("/dev/__properties__/property_info");
}

bool PropertyInfoAreaFile::LoadPath(const char* filename) {
  int fd = open(filename, O_CLOEXEC | O_NOFOLLOW | O_RDONLY);

  struct stat fd_stat;
  if (fstat(fd, &fd_stat) < 0) {
    close(fd);
    return false;
  }

  if ((fd_stat.st_uid != 0) || (fd_stat.st_gid != 0) ||
      ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) ||
      (fd_stat.st_size < static_cast<off_t>(sizeof(PropertyInfoArea)))) {
    close(fd);
    return false;
  }

  auto mmap_size = fd_stat.st_size;

  void* map_result = mmap(nullptr, mmap_size, PROT_READ, MAP_SHARED, fd, 0);
  if (map_result == MAP_FAILED) {
    close(fd);
    return false;
  }

  auto property_info_area = reinterpret_cast<PropertyInfoArea*>(map_result);
  if (property_info_area->minimum_supported_version() > 1 ||
      property_info_area->size() != mmap_size) {
    munmap(map_result, mmap_size);
    close(fd);
    return false;
  }

  close(fd);
  mmap_base_ = map_result;
  mmap_size_ = mmap_size;
  return true;
}

void PropertyInfoAreaFile::Reset() {
  if (mmap_size_ > 0) {
    munmap(mmap_base_, mmap_size_);
  }
  mmap_base_ = nullptr;
  mmap_size_ = 0;
}

}  // namespace properties
}  // namespace android
Loading