1 // Copyright (C) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "icing/legacy/index/icing-flash-bitmap.h"
16
17 #include <sys/mman.h>
18
19 #include <cstddef>
20 #include <cstdint>
21 #include <cstring>
22 #include <memory>
23 #include <string>
24
25 #include "icing/legacy/core/icing-string-util.h"
26 #include "icing/legacy/core/icing-timer.h"
27 #include "icing/legacy/index/icing-bit-util.h"
28 #include "icing/legacy/index/icing-filesystem.h"
29 #include "icing/legacy/index/icing-mmapper.h"
30 #include "icing/util/crc32.h"
31 #include "icing/util/logging.h"
32
33 namespace icing {
34 namespace lib {
35
36 // TODO(b/77482303) : Remove version from the IcingFlashBitmap header - magic
37 // makes it unnecessary.
38 struct IcingFlashBitmap::Header {
39 uint32_t magic;
40 uint32_t version;
41 uint32_t crc;
42 uint32_t dirty;
43 };
44
45 // Helper class used to access the data and the header regions
46 // of the shared memory. The header appears first, followed by the
47 // bitmap memory.
48 class IcingFlashBitmap::Accessor {
49 public:
Accessor(IcingMMapper * mmapper)50 explicit Accessor(IcingMMapper *mmapper) : mmapper_(mmapper) {}
header()51 IcingFlashBitmap::Header *header() {
52 return reinterpret_cast<IcingFlashBitmap::Header *>(mmapper_->address());
53 }
header() const54 const IcingFlashBitmap::Header *header() const {
55 return reinterpret_cast<const IcingFlashBitmap::Header *>(
56 mmapper_->address());
57 }
data() const58 const char *data() const {
59 return reinterpret_cast<const char *>(mmapper_->address() +
60 sizeof(IcingFlashBitmap::Header));
61 }
data_size() const62 size_t data_size() const {
63 return mmapper_->len() - sizeof(IcingFlashBitmap::Header);
64 }
num_words() const65 size_t num_words() const { return data_size() / sizeof(Word); }
data32()66 Word *data32() {
67 return reinterpret_cast<Word *>(mmapper_->address() +
68 sizeof(IcingFlashBitmap::Header));
69 }
data32() const70 const Word *data32() const { return reinterpret_cast<const Word *>(data()); }
end32() const71 const Word *end32() const {
72 return reinterpret_cast<const Word *>(mmapper_->address() +
73 mmapper_->len());
74 }
75
76 private:
77 IcingMMapper *const mmapper_;
78 };
79
Verify() const80 bool IcingFlashBitmap::Verify() const {
81 if (!is_initialized()) {
82 ICING_LOG(ERROR) << "Can't verify unopened flash bitmap " << filename_;
83 return false;
84 }
85 if (mmapper_ == nullptr) {
86 // Opened for read and file doesn't exist.
87 return true;
88 }
89 Accessor accessor(mmapper_.get());
90 if (accessor.header()->magic != kMagic) {
91 ICING_LOG(ERROR) << "Flash bitmap " << filename_
92 << " has incorrect magic header";
93 return false;
94 }
95 if (accessor.header()->version != kCurVersion) {
96 ICING_LOG(ERROR) << "Flash bitmap " << filename_
97 << " has incorrect version";
98 return false;
99 }
100 if (accessor.header()->dirty) {
101 ICING_LOG(ERROR) << "Flash bitmap " << filename_ << " is dirty";
102 return false;
103 }
104 uint32_t crc =
105 IcingStringUtil::UpdateCrc32(0, accessor.data(), accessor.data_size());
106 if (accessor.header()->crc != crc) {
107 ICING_LOG(ERROR) << "Flash bitmap " << filename_ << " has incorrect CRC32 "
108 << accessor.header()->crc << " " << crc;
109 return false;
110 }
111 return true;
112 }
113
Init()114 bool IcingFlashBitmap::Init() {
115 Close();
116
117 // Ensure the storage directory exists
118 std::string storage_dir = filesystem_->GetDirname(filename_.c_str());
119 if (!filesystem_->CreateDirectoryRecursively(storage_dir.c_str())) {
120 return false;
121 }
122
123 IcingScopedFd fd(filesystem_->OpenForWrite(filename_.c_str()));
124 if (!fd.is_valid()) {
125 return false;
126 }
127
128 // Figure out our file size for mmap.
129 uint64_t orig_file_size = filesystem_->GetFileSize(fd.get());
130 uint64_t file_size = orig_file_size;
131 if (orig_file_size == IcingFilesystem::kBadFileSize) {
132 goto error;
133 }
134
135 // Make sure we have something to mmap.
136 if (orig_file_size < kGrowSize) {
137 if (!filesystem_->GrowUsingPWrite(fd.get(), kGrowSize)) {
138 goto error;
139 }
140 file_size = kGrowSize;
141 }
142
143 // Mmap for write.
144 mmapper_ =
145 std::make_unique<IcingMMapper>(fd.get(), false, 0, file_size, MAP_SHARED);
146 if (!mmapper_->is_valid()) {
147 goto error;
148 }
149
150 // Set open_type_ before the possible flush on create.
151 open_type_ = READ_WRITE;
152
153 if (orig_file_size == 0) {
154 Accessor accessor(mmapper_.get());
155 // Original file didn't yet exist, create header.
156 accessor.header()->magic = kMagic;
157 accessor.header()->version = kCurVersion;
158 accessor.header()->dirty = true; // Forces crc update at sync.
159 // Sync file so we know it's supposed to exist.
160 if (!Sync()) {
161 goto error;
162 }
163 }
164 return true;
165
166 error:
167 open_type_ = UNOPENED;
168 mmapper_.reset();
169 return false;
170 }
171
InitForRead()172 bool IcingFlashBitmap::InitForRead() {
173 IcingTimer open_timer;
174 Close();
175
176 // Cannot mmap non-existing or zero-size files.
177 // It's not an error in this case, it just means the
178 // bitmap is empty, so proceed without mapping it.
179 if (!filesystem_->FileExists(filename_.c_str()) ||
180 filesystem_->GetFileSize(filename_.c_str()) == 0) {
181 open_type_ = READ_ONLY;
182 return true;
183 }
184
185 IcingScopedFd fd(filesystem_->OpenForRead(filename_.c_str()));
186 if (!fd.is_valid()) {
187 return false;
188 }
189
190 #ifdef __APPLE__
191 // No MAP_POPULATE in iOS (so no pre-page-faulting. See man mmap)
192 // On Apple we need MAP_SHARED even for sharing the state within the same
193 // process (which gets optimized in the linux-implementation).
194 // Usages of flash-bitmap are expected to flush the content (delayed for
195 // performance reasons). That implies that the copy-on-write behavior of
196 // MAP_PRIVATE is a performance optimization, and MAP_SHARED as alternative
197 // behavior is acceptable.
198 int flags = MAP_SHARED;
199 #else
200 int flags = MAP_PRIVATE | MAP_POPULATE;
201 #endif
202
203 // Figure out our file size for mmap.
204 uint64_t file_size = filesystem_->GetFileSize(fd.get());
205 if (file_size == IcingFilesystem::kBadFileSize) {
206 goto error;
207 }
208
209 // Slurp the bitmap in one go with MAP_POPULATE
210 mmapper_ =
211 std::make_unique<IcingMMapper>(fd.get(), true, 0, file_size, flags);
212 if (!mmapper_->is_valid()) {
213 goto error;
214 }
215
216 open_type_ = READ_ONLY;
217 return true;
218
219 error:
220 open_type_ = UNOPENED;
221 mmapper_.reset();
222 return false;
223 }
224
Close()225 void IcingFlashBitmap::Close() {
226 if (is_initialized()) {
227 UpdateCrc();
228 mmapper_.reset();
229 open_type_ = UNOPENED;
230 }
231 }
232
Delete()233 bool IcingFlashBitmap::Delete() {
234 Close();
235 return filesystem_->DeleteFile(filename_.c_str());
236 }
237
Sync()238 bool IcingFlashBitmap::Sync() {
239 if (!is_initialized()) {
240 ICING_LOG(FATAL) << "Bitmap not initialized";
241 }
242
243 UpdateCrc();
244 return (mmapper_ == nullptr) ? true : mmapper_->Sync();
245 }
246
GetDiskUsage() const247 uint64_t IcingFlashBitmap::GetDiskUsage() const {
248 // For non-existing files, size is 0.
249 if (mmapper_ == nullptr) {
250 return 0;
251 }
252 return filesystem_->GetFileDiskUsage(filename_.c_str());
253 }
254
UpdateCrc()255 Crc32 IcingFlashBitmap::UpdateCrc() {
256 if (mmapper_ == nullptr) {
257 // Non-existent mmapper means file does not exist.
258 return Crc32();
259 }
260 Accessor accessor(mmapper_.get());
261 if (open_type_ == READ_WRITE && accessor.header()->dirty) {
262 Crc32 crc(std::string_view(accessor.data(), accessor.data_size()));
263 accessor.header()->crc = crc.Get();
264 accessor.header()->dirty = false;
265 }
266 return Crc32(accessor.header()->crc);
267 }
268
GetCrc() const269 Crc32 IcingFlashBitmap::GetCrc() const {
270 if (mmapper_ == nullptr) {
271 // Non-existent mmapper means file does not exist.
272 return Crc32();
273 }
274 Accessor accessor(mmapper_.get());
275 if (open_type_ != READ_WRITE || !accessor.header()->dirty) {
276 return Crc32(accessor.header()->crc);
277 }
278 return Crc32(std::string_view(accessor.data(), accessor.data_size()));
279 }
280
Grow(size_t new_file_size)281 bool IcingFlashBitmap::Grow(size_t new_file_size) {
282 IcingScopedFd fd(filesystem_->OpenForWrite(filename_.c_str()));
283 if (!filesystem_->GrowUsingPWrite(fd.get(), new_file_size)) {
284 ICING_LOG(ERROR) << "GrowUsingPWrite " << filename_ << " to new size "
285 << new_file_size << " failed";
286 return false;
287 }
288 if (!mmapper_->Remap(fd.get(), 0, new_file_size)) {
289 ICING_LOG(ERROR) << "Remap of " << filename_ << " after grow failed";
290 return false;
291 }
292 ICING_VLOG(1) << "Grew " << filename_ << " new size " << new_file_size;
293 Accessor accessor(mmapper_.get());
294 accessor.header()->dirty = true;
295 return true;
296 }
297
SetBit(uint64_t idx,bool value)298 bool IcingFlashBitmap::SetBit(uint64_t idx, bool value) {
299 if (open_type_ != READ_WRITE) {
300 ICING_LOG(FATAL) << "Bitmap not opened with type READ_WRITE";
301 }
302
303 Accessor accessor(mmapper_.get());
304
305 // Figure out which word needs to be modified.
306 uint64_t word_offset = idx / kWordBits;
307
308 // Grow file (and mmap) if word_offset >= file size / sizeof(Word).
309 if (word_offset >= accessor.num_words()) {
310 if (!value) {
311 // Values beyond the end of file are false by default, don't write it.
312 return true;
313 }
314 // Grow enough to fit word_offset (including the header).
315 size_t file_size = sizeof(Header) + (word_offset + 1) * sizeof(Word);
316 // Align to kGrowSize.
317 file_size = ALIGN_UP(file_size, kGrowSize);
318 if (!Grow(file_size)) {
319 return false;
320 }
321 }
322
323 // Set the word in the mmapped region.
324 Word *words = accessor.data32();
325 Word mask = GetWordBitmask(idx);
326 Word old_word = words[word_offset];
327 Word new_word = value ? old_word | mask : old_word & ~mask;
328 if (new_word != old_word) {
329 words[word_offset] = new_word;
330 accessor.header()->dirty = true;
331 }
332 return true;
333 }
334
GetBit(uint64_t idx) const335 bool IcingFlashBitmap::GetBit(uint64_t idx) const {
336 return GetWord(idx / kWordBits) & GetWordBitmask(idx);
337 }
338
GetWord(uint64_t idx) const339 IcingFlashBitmap::Word IcingFlashBitmap::GetWord(uint64_t idx) const {
340 if (!is_initialized()) {
341 ICING_LOG(FATAL) << "Bitmap not initialized";
342 }
343
344 // For non-existing files, always return false.
345 if (mmapper_ == nullptr) {
346 return 0;
347 }
348
349 Accessor accessor(mmapper_.get());
350 // Check that we are within limits.
351 if (idx >= accessor.num_words()) {
352 return 0;
353 }
354 return accessor.data32()[idx];
355 }
356
NumWords() const357 size_t IcingFlashBitmap::NumWords() const {
358 if (!is_initialized()) {
359 ICING_LOG(FATAL) << "Bitmap not initialized";
360 }
361
362 // For non-existing files, always return false.
363 if (mmapper_ == nullptr) {
364 return 0;
365 }
366
367 return Accessor(mmapper_.get()).num_words();
368 }
369
GetWordBitmask(uint64_t idx)370 IcingFlashBitmap::Word IcingFlashBitmap::GetWordBitmask(uint64_t idx) {
371 return 1u << (idx % kWordBits);
372 }
373
Truncate(uint64_t idx)374 void IcingFlashBitmap::Truncate(uint64_t idx) {
375 if (!is_initialized()) {
376 ICING_LOG(FATAL) << "Bitmap not initialized";
377 }
378
379 Accessor accessor(mmapper_.get());
380 size_t num_words = accessor.num_words();
381
382 uint64_t word_offset = idx / kWordBits;
383 if (word_offset >= num_words) {
384 // Truncation offset beyond actual file. We're done.
385 return;
386 }
387
388 Word *words = accessor.data32();
389
390 // Keep only bits < idx in the last word.
391 words[word_offset] &= (GetWordBitmask(idx) - 1);
392
393 // Clear everything starting at word_offset + 1
394 uint64_t last_word_offset = word_offset + 1;
395 if (last_word_offset < num_words) {
396 memset(words + last_word_offset, 0,
397 (num_words - last_word_offset) * sizeof(Word));
398 }
399 accessor.header()->dirty = true;
400 UpdateCrc();
401 }
402
OrBitmap(const IcingFlashBitmap & bitmap)403 bool IcingFlashBitmap::OrBitmap(const IcingFlashBitmap &bitmap) {
404 if (!is_initialized()) {
405 ICING_LOG(FATAL) << "Bitmap not initialized";
406 }
407
408 if (mmapper_ == nullptr || bitmap.mmapper_ == nullptr) {
409 // TODO(b/32125196): Figure out how we can get into this state.
410 return false;
411 }
412
413 // If this bitmap is smaller than the source, then grow the
414 // size to match.
415 if (mmapper_->len() < bitmap.mmapper_->len()) {
416 if (!Grow(bitmap.mmapper_->len())) {
417 return false;
418 }
419 }
420 Accessor src_accessor(bitmap.mmapper_.get());
421 const Word *src = src_accessor.data32();
422 const Word *end = src_accessor.end32();
423
424 Accessor dst_accessor(mmapper_.get());
425 Word *dst = dst_accessor.data32();
426 while (src < end) {
427 *dst++ |= *src++;
428 }
429 dst_accessor.header()->dirty = true;
430 UpdateCrc();
431 return true;
432 }
433
434 } // namespace lib
435 } // namespace icing
436