• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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-array-storage.h"
16 
17 #include <sys/mman.h>
18 
19 #include <algorithm>
20 #include <cinttypes>
21 
22 #include "icing/legacy/core/icing-string-util.h"
23 #include "icing/legacy/core/icing-timer.h"
24 #include "icing/legacy/index/icing-bit-util.h"
25 #include "icing/legacy/index/icing-filesystem.h"
26 #include "icing/legacy/index/icing-mmapper.h"
27 #include "icing/util/logging.h"
28 
29 using std::max;
30 using std::min;
31 using std::vector;
32 
33 namespace icing {
34 namespace lib {
35 
36 namespace {
37 
38 // Do the cast and const dance.
MakeVoidPtr(const void * ptr)39 void *MakeVoidPtr(const void *ptr) { return const_cast<void *>(ptr); }
40 
41 }  // namespace
42 
43 const uint32_t IcingArrayStorage::kPartialCrcLimitDiv = 8;  // limit is 1/8th
44 const size_t IcingArrayStorage::kGrowElts = 1u << 14;       // 16KB
45 
IcingArrayStorage(const IcingFilesystem & filesystem)46 IcingArrayStorage::IcingArrayStorage(const IcingFilesystem &filesystem)
47     : mmapper_(nullptr), filesystem_(filesystem) {
48   Reset();
49 }
50 
~IcingArrayStorage()51 IcingArrayStorage::~IcingArrayStorage() { delete mmapper_; }
52 
Init(int fd,size_t fd_offset,bool map_shared,uint32_t elt_size,uint32_t num_elts,uint32_t max_num_elts,uint32_t * crc_ptr,bool init_crc)53 bool IcingArrayStorage::Init(int fd, size_t fd_offset, bool map_shared,
54                              uint32_t elt_size, uint32_t num_elts,
55                              uint32_t max_num_elts, uint32_t *crc_ptr,
56                              bool init_crc) {
57   if (is_initialized()) {
58     return true;
59   }
60 
61   // Compute capacity_num_.
62   uint64_t file_size = filesystem_.GetFileSize(fd);
63   if (file_size == IcingFilesystem::kBadFileSize) {
64     ICING_LOG(ERROR) << "Array storage could not get file size";
65     return false;
66   }
67   if (file_size < fd_offset) {
68     ICING_LOG(ERROR) << "Array storage file size " << file_size << " less than offset " << fd_offset;
69     return false;
70   }
71 
72   uint32_t capacity_num_elts = (file_size - fd_offset) / elt_size;
73   if (capacity_num_elts < num_elts) {
74     ICING_LOG(ERROR) << "Array storage num elts " << num_elts << " > capacity num elts " << capacity_num_elts;
75     return false;
76   }
77 
78   // Map beyond the capacity. We will grow underlying file to avoid
79   // SIGBUS.
80   mmapper_ = new IcingMMapper(fd, false, fd_offset, max_num_elts * elt_size,
81                               map_shared ? MAP_SHARED : MAP_PRIVATE);
82   if (!mmapper_->is_valid()) {
83     ICING_LOG(ERROR) << "Array storage map failed";
84     delete mmapper_;
85     mmapper_ = nullptr;
86     return false;
87   }
88 
89   fd_ = fd;
90   fd_offset_ = fd_offset;
91   map_shared_ = map_shared;
92   elt_size_ = elt_size;
93   // changes_end_ refers to the last element that was included in the
94   // current crc. If we change it, we must also update *crc_ptr_ to
95   // 0. Otherwise UpdateCrc will fail.
96   cur_num_ = changes_end_ = num_elts;
97   max_num_ = max_num_elts;
98   capacity_num_ = capacity_num_elts;
99   crc_ptr_ = crc_ptr;
100 
101   if (crc_ptr_) {
102     uint32_t crc = IcingStringUtil::UpdateCrc32(0, array_cast<char>(),
103                                                 cur_num_ * elt_size_);
104     if (init_crc) {
105       *crc_ptr_ = crc;
106     } else if (crc != *crc_ptr_) {
107       ICING_LOG(ERROR) << "Array storage bad crc " << crc << " vs " << *crc_ptr_;
108       goto failed;
109     }
110   }
111   return true;
112 
113 failed:
114   Reset();
115   return false;
116 }
117 
Reset()118 void IcingArrayStorage::Reset() {
119   fd_ = -1;
120   fd_offset_ = 0;
121   map_shared_ = false;
122   delete mmapper_;
123   mmapper_ = nullptr;
124   elt_size_ = 0;
125   cur_num_ = 0;
126   changes_end_ = 0;
127   max_num_ = 0;
128   capacity_num_ = 0;
129   crc_ptr_ = nullptr;
130   changes_.clear();
131   saved_orig_buf_.clear();
132   dirty_pages_.clear();
133 }
134 
Truncate(uint32_t len)135 void IcingArrayStorage::Truncate(uint32_t len) {
136   if (len > cur_num_) {
137     ICING_LOG(FATAL) << "Length exceeds current size";
138   }
139 
140   cur_num_ = len;
141 }
142 
GetMutableMemInternal(uint32_t elt_idx,uint32_t elt_len)143 void *IcingArrayStorage::GetMutableMemInternal(uint32_t elt_idx,
144                                                uint32_t elt_len) {
145   uint32_t start_byte = elt_idx * elt_size_;
146   uint32_t len_bytes = elt_len * elt_size_;
147 
148   if (!GrowIfNecessary(elt_idx + elt_len)) {
149     return nullptr;
150   }
151 
152   cur_num_ = max(cur_num_, elt_idx + elt_len);
153 
154   if (crc_ptr_) {
155     // Cache original value to update crcs.
156     if (elt_idx < changes_end_) {
157       uint32_t change_len = min(changes_end_, elt_idx + elt_len) - elt_idx;
158 
159       // If we exceed kPartialCrcLimitDiv, clear changes_end_ to
160       // revert to full CRC.
161       if ((saved_orig_buf_.size() + change_len * elt_size_) *
162               kPartialCrcLimitDiv >
163           changes_end_ * elt_size_) {
164         ICING_VLOG(2) << "Array storage change tracking limit exceeded";
165         changes_.clear();
166         saved_orig_buf_.clear();
167         changes_end_ = 0;
168         *crc_ptr_ = 0;
169       } else {
170         changes_.push_back(Change(elt_idx, change_len));
171         saved_orig_buf_.append(array_cast<char>() + start_byte,
172                                change_len * elt_size_);
173       }
174     }
175   }
176 
177   if (!map_shared_) {
178     // Mark dirty pages.
179     int start_page = start_byte / IcingMMapper::system_page_size();
180     int end_page =
181         (start_byte + len_bytes - 1) / IcingMMapper::system_page_size();
182 
183     for (int i = start_page; i <= end_page; i++) {
184       if (static_cast<size_t>(i) >= dirty_pages_.size()) {
185         dirty_pages_.resize(i + 1);
186       }
187       dirty_pages_[i] = true;
188     }
189   }
190 
191   return MakeVoidPtr(&(array())[start_byte]);
192 }
193 
GrowIfNecessary(uint32_t num_elts)194 bool IcingArrayStorage::GrowIfNecessary(uint32_t num_elts) {
195   if (num_elts <= capacity_num_) return true;
196   if (num_elts > max_num_) return false;
197 
198   // Need to grow.
199   uint64_t new_file_size = fd_offset_ + uint64_t{num_elts} * elt_size_;
200   // Grow to kGrowElts boundary.
201   new_file_size = AlignUp(new_file_size, kGrowElts * elt_size_);
202   if (!filesystem_.Grow(fd_, new_file_size)) {
203     return false;
204   }
205   capacity_num_ = (new_file_size - fd_offset_) / elt_size_;
206   return true;
207 }
208 
UpdateCrc()209 void IcingArrayStorage::UpdateCrc() {
210   if (!crc_ptr_) return;
211 
212   // First apply the modified area. Keep a bitmap of already updated
213   // regions so we don't double-update.
214   vector<bool> updated(changes_end_);
215   uint32_t cur_offset = 0;
216   uint32_t cur_crc = *crc_ptr_;
217   int num_partial_crcs = 0;
218   int num_truncated = 0;
219   int num_overlapped = 0;
220   int num_duplicate = 0;
221   for (size_t i = 0; i < changes_.size(); i++) {
222     const Change &change = changes_[i];
223     if (change.elt_offset + change.elt_len > changes_end_) {
224       ICING_LOG(FATAL) << "Off " << change.elt_offset << " len "
225                        << change.elt_len << " end " << changes_end_;
226     }
227 
228     // Skip truncated tracked changes.
229     if (change.elt_offset >= cur_num_) {
230       ++num_truncated;
231       continue;
232     }
233 
234     // Turn change buf into change^orig.
235     const char *buf_end =
236         &saved_orig_buf_[cur_offset + change.elt_len * elt_size_];
237     const char *cur_array = array_cast<char>() + change.elt_offset * elt_size_;
238     // Now xor in. SSE acceleration please?
239     for (char *cur = &saved_orig_buf_[cur_offset]; cur < buf_end;
240          cur++, cur_array++) {
241       *cur ^= *cur_array;
242     }
243 
244     // Skip over already updated bytes by setting update to 0.
245     bool new_update = false;
246     bool overlap = false;
247     uint32_t cur_elt = change.elt_offset;
248     for (char *cur = &saved_orig_buf_[cur_offset]; cur < buf_end;
249          cur_elt++, cur += elt_size_) {
250       if (updated[cur_elt]) {
251         memset(cur, 0, elt_size_);
252         overlap = true;
253       } else {
254         updated[cur_elt] = true;
255         new_update = true;
256       }
257     }
258 
259     // Apply update to crc.
260     if (new_update) {
261       cur_crc = IcingStringUtil::UpdateAtPositionCrc32(
262           cur_crc, changes_end_ * elt_size_, change.elt_offset * elt_size_,
263           buf_end - change.elt_len * elt_size_, change.elt_len * elt_size_);
264       num_partial_crcs++;
265       if (overlap) {
266         num_overlapped++;
267       }
268     } else {
269       num_duplicate++;
270     }
271     cur_offset += change.elt_len * elt_size_;
272   }
273   if (!changes_.empty()) {
274     ICING_VLOG(2) << "Array update partial crcs " << num_partial_crcs
275         << " truncated " << num_truncated << " overlapped " << num_overlapped
276         << " duplicate " << num_duplicate;
277   }
278 
279   // Now update with grown area.
280   if (changes_end_ < cur_num_) {
281     cur_crc = IcingStringUtil::UpdateCrc32(
282         cur_crc, array_cast<char>() + changes_end_ * elt_size_,
283         (cur_num_ - changes_end_) * elt_size_);
284     ICING_VLOG(2) << "Array update tail crc offset " << changes_end_ << " -> " << cur_num_;
285   }
286 
287   // Clear, now that we've applied changes.
288   changes_.clear();
289   saved_orig_buf_.clear();
290   changes_end_ = cur_num_;
291 
292   // Commit new crc.
293   *crc_ptr_ = cur_crc;
294 }
295 
Warm() const296 void IcingArrayStorage::Warm() const {
297   if (madvise(MakeVoidPtr(array()),
298               IcingMMapper::page_aligned_size(cur_num_ * elt_size_),
299               MADV_WILLNEED) != 0) {
300     ICING_LOG(FATAL) << "Failed to madvise()";
301   }
302 }
303 
Clear()304 void IcingArrayStorage::Clear() {
305   cur_num_ = 0;
306   changes_end_ = 0;
307   changes_.clear();
308   saved_orig_buf_.clear();
309   dirty_pages_.clear();
310   if (crc_ptr_) *crc_ptr_ = 0;
311 }
312 
313 // TODO(b/69383247): investigate strange behavior here
314 // If map_shared_ is false (i.e. we are using MAP_PRIVATE), dirty pages are
315 // flushed to the underlying file, but strangely a sync isn't done.
316 // If map_shared_ is true, then we call sync.
Sync()317 uint32_t IcingArrayStorage::Sync() {
318   if (!map_shared_) {
319     IcingTimer timer;
320     uint32_t num_flushed = 0;     // pages flushed
321     uint32_t num_contiguous = 0;  // contiguous series of pages flushed
322     uint32_t dirty_pages_size = dirty_pages_.size();
323 
324     bool in_dirty = false;
325     uint32_t dirty_start = 0;
326     for (size_t i = 0; i < dirty_pages_size; i++) {
327       bool is_dirty = dirty_pages_[i];
328       if (in_dirty && !is_dirty) {
329         // Flush pages between dirty_start and this.
330         uint32_t dirty_end = i * IcingMMapper::system_page_size();
331         num_contiguous++;
332         num_flushed +=
333             (dirty_end - dirty_start) / IcingMMapper::system_page_size();
334 
335         if (pwrite(fd_, array() + dirty_start, dirty_end - dirty_start,
336                    fd_offset_ + dirty_start) !=
337             static_cast<ssize_t>(dirty_end - dirty_start)) {
338           ICING_LOG(ERROR) << "Flushing pages failed (" << dirty_start << ", " << dirty_end << ")";
339         }
340         in_dirty = false;
341       } else if (!in_dirty && is_dirty) {
342         dirty_start = i * IcingMMapper::system_page_size();
343         in_dirty = true;
344       }
345     }
346 
347     // Flush remaining.
348     if (in_dirty) {
349       uint32_t dirty_end = dirty_pages_size * IcingMMapper::system_page_size();
350       num_contiguous++;
351       num_flushed +=
352           (dirty_end - dirty_start) / IcingMMapper::system_page_size();
353 
354       if (pwrite(fd_, array() + dirty_start, dirty_end - dirty_start,
355                  fd_offset_ + dirty_start) !=
356           static_cast<ssize_t>(dirty_end - dirty_start)) {
357         ICING_LOG(ERROR) << "Flushing pages failed (" << dirty_start << ", " << dirty_end << ")";
358       }
359     }
360 
361     // Clear in one shot.
362     dirty_pages_.clear();
363 
364     // Invalidate region so that we are rid of dirty private pages.
365     if (madvise(MakeVoidPtr(array()),
366                 IcingMMapper::page_aligned_size(cur_num_ * elt_size_),
367                 MADV_DONTNEED) != 0) {
368       ICING_LOG(FATAL) << "Failed to madvise()";
369     }
370 
371     if (num_flushed > 0) {
372       ICING_VLOG(1) << "Flushing " << num_flushed << "/" << dirty_pages_size << " " << num_contiguous << " contiguous pages in " << timer.Elapsed() * 1000 << "ms.";
373     }
374 
375     return num_flushed;
376   } else {
377     // Changes have been applied. msync() to ensure they are written out.
378     // Don't sync 0-length, which is an error in iOS and a no-op on Android
379     const size_t sync_length =
380         IcingMMapper::page_aligned_size(cur_num_ * elt_size_);
381     if (sync_length > 0) {
382       if (msync(MakeVoidPtr(array()), sync_length, MS_SYNC) != 0) {
383         ICING_LOG(FATAL) << "Failed to msync()";
384       }
385     }
386 
387     return 0;
388   }
389 }
390 
391 }  // namespace lib
392 }  // namespace icing
393