xref: /aosp_15_r20/external/icing/icing/legacy/index/icing-flash-bitmap.cc (revision 8b6cd535a057e39b3b86660c4aa06c99747c2136)
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