1 /*************************************************************************
3 * Copyright 2016 Realm Inc.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 **************************************************************************/
19 #ifndef REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP
20 #define REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP
22 #include <realm/util/file.hpp>
23 #include <realm/util/thread.hpp>
24 #include <realm/util/features.h>
26 #if REALM_ENABLE_ENCRYPTION
28 typedef size_t (*Header_to_size)(const char* addr);
35 struct SharedFileInfo;
36 class EncryptedFileMapping;
38 class EncryptedFileMapping {
40 // Adds the newly-created object to file.mappings iff it's successfully constructed
41 EncryptedFileMapping(SharedFileInfo& file, size_t file_offset, void* addr, size_t size, File::AccessMode access);
42 ~EncryptedFileMapping();
44 // Default implementations of copy/assign can trigger multiple destructions
45 EncryptedFileMapping(const EncryptedFileMapping&) = delete;
46 EncryptedFileMapping& operator=(const EncryptedFileMapping&) = delete;
48 // Write all dirty pages to disk and mark them read-only
49 // Does not call fsync
50 void flush() noexcept;
52 // Sync this file to disk
55 // Make sure that memory in the specified range is synchronized with any
56 // changes made globally visible through call to write_barrier
57 void read_barrier(const void* addr, size_t size, UniqueLock& lock, Header_to_size header_to_size);
59 // Ensures that any changes made to memory in the specified range
60 // becomes visible to any later calls to read_barrier()
61 void write_barrier(const void* addr, size_t size) noexcept;
63 // Set this mapping to a new address and size
64 // Flushes any remaining dirty pages from the old mapping
65 void set(void* new_addr, size_t new_size, size_t new_file_offset);
67 bool contains_page(size_t page_in_file) const;
68 size_t get_local_index_of_address(const void* addr, size_t offset = 0) const;
71 SharedFileInfo& m_file;
74 size_t m_blocks_per_page;
76 void* m_addr = nullptr;
80 // MUST be of type char because of coherence issues when writing inside mutex and reading outside
81 // it. FIXME: We're investigating if this is good enough, or if we need further mechanisms
82 std::vector<char> m_up_to_date_pages;
83 std::vector<bool> m_dirty_pages;
85 File::AccessMode m_access;
88 std::unique_ptr<char[]> m_validate_buffer;
91 char* page_addr(size_t local_page_ndx) const noexcept;
93 void mark_outdated(size_t local_page_ndx) noexcept;
94 bool copy_up_to_date_page(size_t local_page_ndx) noexcept;
95 void refresh_page(size_t local_page_ndx);
96 void write_page(size_t local_page_ndx) noexcept;
98 void validate_page(size_t local_page_ndx) noexcept;
99 void validate() noexcept;
102 inline size_t EncryptedFileMapping::get_local_index_of_address(const void* addr, size_t offset) const
104 REALM_ASSERT_EX(addr >= m_addr, addr, m_addr);
106 size_t local_ndx = ((reinterpret_cast<uintptr_t>(addr) - reinterpret_cast<uintptr_t>(m_addr) + offset) >> m_page_shift);
107 REALM_ASSERT_EX(local_ndx < m_up_to_date_pages.size(), local_ndx, m_up_to_date_pages.size());
111 inline bool EncryptedFileMapping::contains_page(size_t page_in_file) const
113 // first check for (page_in_file >= m_first_page) so that the following
114 // subtraction using unsigned types never wraps under 0
115 return page_in_file >= m_first_page && page_in_file - m_first_page < m_up_to_date_pages.size();
118 inline void EncryptedFileMapping::read_barrier(const void* addr, size_t size, UniqueLock& lock,
119 Header_to_size header_to_size)
121 size_t first_accessed_local_page = get_local_index_of_address(addr);
123 // make sure the first page is available
124 // Checking before taking the lock is important to performance.
125 if (!m_up_to_date_pages[first_accessed_local_page]) {
126 if (!lock.holds_lock())
128 // after taking the lock, we must repeat the check so that we never
129 // call refresh_page() on a page which is already up to date.
130 if (!m_up_to_date_pages[first_accessed_local_page])
131 refresh_page(first_accessed_local_page);
134 if (header_to_size) {
136 // We know it's an array, and array headers are 8-byte aligned, so it is
137 // included in the first page which was handled above.
138 size = header_to_size(static_cast<const char*>(addr));
141 size_t last_idx = get_local_index_of_address(addr, size == 0 ? 0 : size - 1);
142 size_t up_to_date_pages_size = m_up_to_date_pages.size();
144 // We already checked first_accessed_local_page above, so we start the loop
145 // at first_accessed_local_page + 1 to check the following page.
146 for (size_t idx = first_accessed_local_page + 1; idx <= last_idx && idx < up_to_date_pages_size; ++idx) {
147 if (!m_up_to_date_pages[idx]) {
148 if (!lock.holds_lock())
150 // after taking the lock, we must repeat the check so that we never
151 // call refresh_page() on a page which is already up to date.
152 if (!m_up_to_date_pages[idx])
160 #endif // REALM_ENABLE_ENCRYPTION
165 /// Thrown by EncryptedFileMapping if a file opened is non-empty and does not
166 /// contain valid encrypted data
167 struct DecryptionFailed : util::File::AccessError {
169 : util::File::AccessError("Decryption failed", std::string())
176 #endif // REALM_UTIL_ENCRYPTED_FILE_MAPPING_HPP