blob: c5780f94b9780fbad348b2ddc557500645573702 [file] [log] [blame]
/*
* Copyright (C) 2020 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.
*/
#pragma once
#include <memory>
#include <shared_mutex>
#include <vector>
#include <android-base/logging.h>
#ifdef __ANDROID__
#include <linux/incrementalfs.h>
#endif
namespace android {
class FileMap;
namespace incfs {
// Controls whether not verifying the presence of data before de-referencing the pointer aborts
// program execution.
#define LIBINCFS_MAP_PTR_DEBUG false
#if LIBINCFS_MAP_PTR_DEBUG
#define LIBINCFS_MAP_PTR_DEBUG_CODE(x) x
#else
#define LIBINCFS_MAP_PTR_DEBUG_CODE(x)
#endif
template <typename T, bool Verified = false>
struct map_ptr;
// This class represents a memory-mapped, read-only file that may exist on an IncFs file system.
//
// Files stored on IncFs may not be fully present. This class is able to return a smart pointer
// (map_ptr<T>) that is able to verify whether the contents of the pointer are fully present on
// IncFs.
//
// This always uses MAP_SHARED.
class IncFsFileMap final {
template <typename, bool>
friend struct map_ptr;
using bucket_t = uint8_t;
static constexpr size_t kBucketBits = sizeof(bucket_t) * 8U;
public:
IncFsFileMap();
IncFsFileMap(IncFsFileMap&&) noexcept;
IncFsFileMap& operator =(IncFsFileMap&&) noexcept;
~IncFsFileMap();
// Initializes the map. Does not take ownership of the file descriptor.
// Returns whether or not the file was able to be memory-mapped.
bool Create(int fd, off64_t offset, size_t length, const char* file_name);
template <typename T = void>
map_ptr<T> data() const {
return map_ptr<T>(IsVerificationEnabled() ? this : nullptr,
reinterpret_cast<const T*>(unsafe_data()));
}
const void* unsafe_data() const;
size_t length() const;
off64_t offset() const;
const char* file_name() const;
private:
DISALLOW_COPY_AND_ASSIGN(IncFsFileMap);
// Returns whether pointers created from this map should run verification of data presence
// to protect against SIGBUS signals.
bool IsVerificationEnabled() const;
#ifdef __ANDROID__
// Returns whether the data range is entirely present on IncFs.
bool Verify(const uint8_t* const& data_start, const uint8_t* const& data_end,
const uint8_t** prev_verified_block) const;
#endif
// File descriptor of the memory-mapped file (not owned).
int fd_ = -1;
size_t start_block_offset_ = 0;
const uint8_t* start_block_ptr_ = nullptr;
std::unique_ptr<android::FileMap> map_;
// Bitwise cache for storing whether a block has already been verified. This cache relies on
// IncFs not deleting blocks of a file that is currently memory mapped.
mutable std::vector<std::atomic<bucket_t>> loaded_blocks_;
};
// Variant of map_ptr that statically guarantees that the pointed to data is fully present and
// reading data will not result in IncFs raising a SIGBUS.
template <typename T>
using verified_map_ptr = map_ptr<T, true>;
// Smart pointer that is able to verify whether the contents of the pointer are fully present on
// the file system before using the pointer. Files residing on IncFs may not be fully present.
//
// Before attempting to use the data represented by the smart pointer, the caller should always use
// the bool operator to verify the presence of the data. The bool operator is not thread-safe. If
// this pointer must be used in multiple threads concurrently, use verified_map_ptr instead.
//
// map_ptr created from raw pointers have less overhead than when created from IncFsFileMap.
template <typename T, bool Verified>
struct map_ptr final {
private:
friend class IncFsFileMap;
// To access internals of map_ptr with a different type
template <typename, bool>
friend struct map_ptr;
template <typename T1>
using IsVoid = typename std::enable_if_t<std::is_void<T1>::value, int>;
template <typename T1>
using NotVoid = typename std::enable_if_t<!std::is_void<T1>::value, int>;
template <bool V>
using IsVerified = typename std::enable_if_t<V, int>;
template <bool V>
using IsUnverified = typename std::enable_if_t<!V, int>;
public:
class const_iterator final {
public:
friend struct map_ptr<T, Verified>;
using iterator_category = std::random_access_iterator_tag;
using value_type = const map_ptr<T>;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = value_type;
const_iterator() = default;
const_iterator(const const_iterator& it) = default;
bool operator==(const const_iterator& other) const { return safe_ptr_ == other.safe_ptr_; }
bool operator!=(const const_iterator& other) const { return safe_ptr_ != other.safe_ptr_; }
std::ptrdiff_t operator-(const const_iterator& other) const {
return safe_ptr_ - other.safe_ptr_;
}
const_iterator operator+(int n) const {
const_iterator other = *this;
other += n;
return other;
}
reference operator*() const { return safe_ptr_; }
const const_iterator& operator++() {
safe_ptr_++;
return *this;
}
const_iterator& operator+=(int n) {
safe_ptr_ = safe_ptr_ + n;
return *this;
}
const const_iterator operator++(int) {
const_iterator temp(*this);
safe_ptr_++;
return temp;
}
private:
explicit const_iterator(const map_ptr<T>& ptr) : safe_ptr_(ptr) {}
map_ptr<T> safe_ptr_;
};
// Default constructor
map_ptr() = default;
// Implicit conversion from raw pointer
map_ptr(const T* ptr) : map_ptr(nullptr, ptr, nullptr) {}
// Copy constructor
map_ptr(const map_ptr& other) = default;
// Implicit copy conversion from verified to unverified map_ptr<T>
template <bool V2, bool V1 = Verified, IsUnverified<V1> = 0, IsVerified<V2> = 0>
map_ptr(const map_ptr<T, V2>& other) : map_ptr(other.map_, other.ptr_, other.verified_block_) {}
// Move constructor
map_ptr(map_ptr&& other) noexcept = default;
// Implicit move conversion from verified to unverified map_ptr<T>
template <bool V2, bool V1 = Verified, IsUnverified<V1> = 0, IsVerified<V2> = 0>
map_ptr(map_ptr&& other) : map_ptr(other.map_, other.ptr_, other.verified_block_) {}
// Implicit conversion to unverified map_ptr<void>
template <typename U, bool V2, typename T1 = T, bool V1 = Verified, IsVoid<T1> = 0,
NotVoid<U> = 0, IsUnverified<V1> = 0>
map_ptr(const map_ptr<U, V2>& other)
: map_ptr(other.map_, reinterpret_cast<const void*>(other.ptr_), other.verified_block_) {}
// Implicit conversion from regular raw pointer
map_ptr& operator=(const T* ptr) {
ptr_ = ptr;
map_ = nullptr;
verified_block_ = nullptr;
LIBINCFS_MAP_PTR_DEBUG_CODE(verified_ = Verified);
return *this;
}
// Copy assignment operator
map_ptr& operator=(const map_ptr& other) = default;
// Copy assignment operator
template <bool V2, bool V1 = Verified, IsUnverified<V1> = 0, IsVerified<V2> = 0>
map_ptr& operator=(const map_ptr<T, V2>& other) {
ptr_ = other.ptr_;
map_ = other.map_;
verified_block_ = other.verified_block_;
LIBINCFS_MAP_PTR_DEBUG_CODE(verified_ = other.verified_);
return *this;
}
template <bool V2>
bool operator==(const map_ptr<T, V2>& other) const {
return ptr_ == other.ptr_;
}
template <bool V2>
bool operator!=(const map_ptr<T, V2>& other) const {
return ptr_ != other.ptr_;
}
template <bool V2>
bool operator<(const map_ptr<T, V2>& other) const {
return ptr_ < other.ptr_;
}
template <bool V2>
std::ptrdiff_t operator-(const map_ptr<T, V2>& other) const {
return ptr_ - other.ptr_;
}
template <typename U>
map_ptr<U> convert() const {
return map_ptr<U>(map_, reinterpret_cast<const U*>(ptr_), verified_block_);
}
// Retrieves a map_ptr<T> offset from an original map_ptr<U> by the specified number of `offset`
// bytes.
map_ptr<T> offset(std::ptrdiff_t offset) const {
return map_ptr<T>(map_,
reinterpret_cast<const T*>(reinterpret_cast<const uint8_t*>(ptr_) +
offset),
verified_block_);
}
// Returns a raw pointer to the value of this pointer.
const T* unsafe_ptr() const { return ptr_; }
// Start T == void methods
template <typename T1 = T, IsVoid<T1> = 0>
operator bool() const {
return ptr_ != nullptr;
}
// End T == void methods
// Start T != void methods
template <typename T1 = T, NotVoid<T1> = 0, bool V1 = Verified, IsUnverified<V1> = 0>
operator bool() const {
return verify();
}
template <typename T1 = T, NotVoid<T1> = 0, bool V1 = Verified, IsVerified<V1> = 0>
operator bool() const {
return ptr_ != nullptr;
}
template <typename T1 = T, NotVoid<T1> = 0>
const_iterator iterator() const {
return const_iterator(*this);
}
template <typename T1 = T, NotVoid<T1> = 0>
const map_ptr<T1>& operator++() {
LIBINCFS_MAP_PTR_DEBUG_CODE(verified_ = false);
++ptr_;
return *this;
}
template <typename T1 = T, NotVoid<T1> = 0>
const map_ptr<T1> operator++(int) {
map_ptr<T1> temp = *this;
LIBINCFS_MAP_PTR_DEBUG_CODE(verified_ = false);
++ptr_;
return temp;
}
template <typename S, typename T1 = T, NotVoid<T1> = 0>
map_ptr<T1> operator+(const S n) const {
return map_ptr<T1>(map_, ptr_ + n, verified_block_);
}
template <typename S, typename T1 = T, NotVoid<T1> = 0>
map_ptr<T1> operator-(const S n) const {
return map_ptr<T1>(map_, ptr_ - n, verified_block_);
}
// Returns the value of the pointer.
// The caller should verify the presence of the pointer data before calling this method.
template <typename T1 = T, NotVoid<T1> = 0>
const T1& value() const {
LIBINCFS_MAP_PTR_DEBUG_CODE(
CHECK(verified_) << "Did not verify presence before de-referencing safe pointer");
return *ptr_;
}
// Returns a raw pointer to the value this pointer.
// The caller should verify the presence of the pointer data before calling this method.
template <typename T1 = T, NotVoid<T1> = 0>
const T1* operator->() const {
LIBINCFS_MAP_PTR_DEBUG_CODE(
CHECK(verified_) << "Did not verify presence before de-referencing safe pointer");
return ptr_;
}
// Verifies the presence of `n` elements of `T`.
//
// Returns true if the elements are completely present; otherwise, returns false.
template <typename T1 = T, NotVoid<T1> = 0, bool V1 = Verified, IsUnverified<V1> = 0>
bool verify(size_t n = 1) const {
#ifdef __ANDROID__
if (LIKELY(map_ == nullptr)) {
return ptr_ != nullptr;
}
if (ptr_ == nullptr) {
return false;
}
const size_t verify_size = sizeof(T) * n;
LIBINCFS_MAP_PTR_DEBUG_CODE(if (sizeof(T) <= verify_size) verified_ = true;);
const auto data_start = reinterpret_cast<const uint8_t*>(ptr_);
const auto data_end = reinterpret_cast<const uint8_t*>(ptr_) + verify_size;
// If the data is entirely within the block beginning at the previous verified block
// pointer, then the data can safely be used.
if (LIKELY(data_start >= verified_block_ &&
data_end <= verified_block_ + INCFS_DATA_FILE_BLOCK_SIZE)) {
return true;
}
if (LIKELY(map_->Verify(data_start, data_end, &verified_block_))) {
return true;
}
LIBINCFS_MAP_PTR_DEBUG_CODE(verified_ = false);
return false;
#else
(void)n;
return true;
#endif
}
// Returns a verified version of this pointer.
// The caller should verify the presence of the pointer data before calling this method.
template <typename T1 = T, NotVoid<T1> = 0>
verified_map_ptr<T1> verified() const {
return verified_map_ptr<T1>(map_, ptr_, verified_block_);
}
private:
map_ptr(const IncFsFileMap* map, const T* ptr)
: ptr_(ptr), map_(map), verified_block_(nullptr) {}
map_ptr(const IncFsFileMap* map, const T* ptr, const uint8_t* verified_block)
: ptr_(ptr), map_(map), verified_block_(verified_block) {}
const T* ptr_ = nullptr;
mutable const IncFsFileMap* map_ = nullptr;
mutable const uint8_t* verified_block_;
LIBINCFS_MAP_PTR_DEBUG_CODE(mutable bool verified_ = Verified);
};
} // namespace incfs
} // namespace android