1 // Copyright (C) 2024 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/store/blob-store.h"
16
17 #include <fcntl.h>
18
19 #include <algorithm>
20 #include <array>
21 #include <cerrno>
22 #include <cstdint>
23 #include <iterator>
24 #include <limits>
25 #include <memory>
26 #include <string>
27 #include <string_view>
28 #include <unordered_map>
29 #include <unordered_set>
30 #include <utility>
31 #include <vector>
32
33 #include "icing/text_classifier/lib3/utils/base/status.h"
34 #include "icing/text_classifier/lib3/utils/base/statusor.h"
35 #include "icing/absl_ports/canonical_errors.h"
36 #include "icing/absl_ports/str_cat.h"
37 #include "icing/file/constants.h"
38 #include "icing/file/filesystem.h"
39 #include "icing/file/portable-file-backed-proto-log.h"
40 #include "icing/proto/blob.pb.h"
41 #include "icing/proto/document.pb.h"
42 #include "icing/util/clock.h"
43 #include "icing/util/encode-util.h"
44 #include "icing/util/logging.h"
45 #include "icing/util/sha256.h"
46 #include "icing/util/status-macros.h"
47
48 namespace icing {
49 namespace lib {
50
51 static constexpr std::string_view kBlobFileDir = "blob_files";
52 static constexpr std::string_view kBlobInfoProtoLogFileName =
53 "blob_info_proto_file";
54 static constexpr int32_t kSha256LengthBytes = 32;
55 static constexpr int32_t kReadBufferSize = 8192;
56
57 namespace {
58
MakeBlobInfoProtoLogFileName(const std::string & base_dir)59 std::string MakeBlobInfoProtoLogFileName(const std::string& base_dir) {
60 return absl_ports::StrCat(base_dir, "/", kBlobInfoProtoLogFileName);
61 }
62
MakeBlobFileDir(const std::string & base_dir)63 std::string MakeBlobFileDir(const std::string& base_dir) {
64 return absl_ports::StrCat(base_dir, "/", kBlobFileDir);
65 }
66
MakeBlobFileName(const std::string & base_dir,int64_t creation_time_ms)67 std::string MakeBlobFileName(const std::string& base_dir,
68 int64_t creation_time_ms) {
69 return absl_ports::StrCat(MakeBlobFileDir(base_dir), "/",
70 std::to_string(creation_time_ms));
71 }
72
ValidateBlobHandle(const PropertyProto::BlobHandleProto & blob_handle)73 libtextclassifier3::Status ValidateBlobHandle(
74 const PropertyProto::BlobHandleProto& blob_handle) {
75 if (blob_handle.digest().size() != kSha256LengthBytes) {
76 return absl_ports::InvalidArgumentError(
77 "Invalid blob handle. The digest is not sha 256 digest.");
78 }
79 if (blob_handle.namespace_().empty()) {
80 return absl_ports::InvalidArgumentError(
81 "Invalid blob handle. The namespace is empty.");
82 }
83 return libtextclassifier3::Status::OK;
84 }
85
86 libtextclassifier3::StatusOr<std::unordered_map<std::string, int32_t>>
LoadBlobHandleToOffsetMapper(PortableFileBackedProtoLog<BlobInfoProto> * blob_info_log)87 LoadBlobHandleToOffsetMapper(
88 PortableFileBackedProtoLog<BlobInfoProto>* blob_info_log) {
89 std::unordered_map<std::string, int32_t> blob_handle_to_offset;
90 auto itr = blob_info_log->GetIterator();
91 while (itr.Advance().ok()) {
92 auto blob_info_proto_or = blob_info_log->ReadProto(itr.GetOffset());
93 if (!blob_info_proto_or.ok()) {
94 if (absl_ports::IsNotFound(blob_info_proto_or.status())) {
95 // Skip erased proto.
96 continue;
97 }
98
99 // Return real error.
100 return std::move(blob_info_proto_or).status();
101 }
102 BlobInfoProto blob_info_proto = std::move(blob_info_proto_or).ValueOrDie();
103
104 std::string blob_handle_str =
105 BlobStore::BuildBlobHandleStr(blob_info_proto.blob_handle());
106 blob_handle_to_offset.insert({std::move(blob_handle_str), itr.GetOffset()});
107 }
108 return blob_handle_to_offset;
109 }
110
111 } // namespace
112
BuildBlobHandleStr(const PropertyProto::BlobHandleProto & blob_handle)113 /* static */ std::string BlobStore::BuildBlobHandleStr(
114 const PropertyProto::BlobHandleProto& blob_handle) {
115 return encode_util::EncodeStringToCString(blob_handle.digest() +
116 blob_handle.namespace_());
117 }
118
Create(const Filesystem * filesystem,std::string base_dir,const Clock * clock,int64_t orphan_blob_time_to_live_ms,int32_t compression_level)119 libtextclassifier3::StatusOr<BlobStore> BlobStore::Create(
120 const Filesystem* filesystem, std::string base_dir, const Clock* clock,
121 int64_t orphan_blob_time_to_live_ms, int32_t compression_level) {
122 ICING_RETURN_ERROR_IF_NULL(filesystem);
123 ICING_RETURN_ERROR_IF_NULL(clock);
124
125 // Make sure the blob file directory exists.
126 if (!filesystem->CreateDirectoryRecursively(
127 MakeBlobFileDir(base_dir).c_str())) {
128 return absl_ports::InternalError(
129 absl_ports::StrCat("Could not create blob file directory."));
130 }
131
132 // Load existing file names (excluding the directory of key mapper).
133 std::vector<std::string> file_names;
134 if (!filesystem->ListDirectory(MakeBlobFileDir(base_dir).c_str(),
135 &file_names)) {
136 return absl_ports::InternalError("Failed to list directory.");
137 }
138 std::unordered_set<std::string> known_file_names(
139 std::make_move_iterator(file_names.begin()),
140 std::make_move_iterator(file_names.end()));
141 if (orphan_blob_time_to_live_ms <= 0) {
142 orphan_blob_time_to_live_ms = std::numeric_limits<int64_t>::max();
143 }
144
145 std::string blob_info_proto_file_name =
146 MakeBlobInfoProtoLogFileName(base_dir);
147
148 ICING_ASSIGN_OR_RETURN(
149 PortableFileBackedProtoLog<BlobInfoProto>::CreateResult log_create_result,
150 PortableFileBackedProtoLog<BlobInfoProto>::Create(
151 filesystem, blob_info_proto_file_name,
152 PortableFileBackedProtoLog<BlobInfoProto>::Options(
153 /*compress_in=*/true, constants::kMaxProtoSize,
154 compression_level)));
155
156 std::unordered_map<std::string, int> blob_handle_to_offset;
157 ICING_ASSIGN_OR_RETURN(
158 blob_handle_to_offset,
159 LoadBlobHandleToOffsetMapper(log_create_result.proto_log.get()));
160
161 return BlobStore(
162 filesystem, std::move(base_dir), clock, orphan_blob_time_to_live_ms,
163 compression_level, std::move(log_create_result.proto_log),
164 std::move(blob_handle_to_offset), std::move(known_file_names));
165 }
166
OpenWrite(const PropertyProto::BlobHandleProto & blob_handle)167 libtextclassifier3::StatusOr<int> BlobStore::OpenWrite(
168 const PropertyProto::BlobHandleProto& blob_handle) {
169 ICING_RETURN_IF_ERROR(ValidateBlobHandle(blob_handle));
170 std::string blob_handle_str = BuildBlobHandleStr(blob_handle);
171
172 auto blob_info_itr = blob_handle_to_offset_.find(blob_handle_str);
173 if (blob_info_itr != blob_handle_to_offset_.end()) {
174 ICING_ASSIGN_OR_RETURN(BlobInfoProto blob_info,
175 blob_info_log_->ReadProto(blob_info_itr->second));
176 if (blob_info.is_committed()) {
177 // The blob is already committed, return error.
178 return absl_ports::AlreadyExistsError(absl_ports::StrCat(
179 "Rewriting the committed blob is not allowed for blob handle: ",
180 blob_handle.digest()));
181 }
182 }
183
184 // Create a new blob info and blob file.
185 ICING_ASSIGN_OR_RETURN(BlobInfoProto blob_info,
186 GetOrCreateBlobInfo(blob_handle_str, blob_handle));
187
188 std::string file_name =
189 MakeBlobFileName(base_dir_, blob_info.creation_time_ms());
190 int file_descriptor = filesystem_.OpenForWrite(file_name.c_str());
191 if (file_descriptor < 0) {
192 return absl_ports::InternalError(absl_ports::StrCat(
193 "Failed to open blob file for handle: ", blob_handle.digest()));
194 }
195 return file_descriptor;
196 }
197
RemoveBlob(const PropertyProto::BlobHandleProto & blob_handle)198 libtextclassifier3::Status BlobStore::RemoveBlob(
199 const PropertyProto::BlobHandleProto& blob_handle) {
200 ICING_RETURN_IF_ERROR(ValidateBlobHandle(blob_handle));
201 std::string blob_handle_str = BuildBlobHandleStr(blob_handle);
202
203 auto blob_info_itr = blob_handle_to_offset_.find(blob_handle_str);
204 if (blob_info_itr == blob_handle_to_offset_.end()) {
205 return absl_ports::NotFoundError(absl_ports::StrCat(
206 "Cannot find the blob for handle: ", blob_handle.digest()));
207 }
208
209 int64_t blob_info_offset = blob_info_itr->second;
210 ICING_ASSIGN_OR_RETURN(BlobInfoProto blob_info,
211 blob_info_log_->ReadProto(blob_info_offset));
212
213 std::string file_name =
214 MakeBlobFileName(base_dir_, blob_info.creation_time_ms());
215 if (!filesystem_.DeleteFile(file_name.c_str())) {
216 return absl_ports::InternalError(absl_ports::StrCat(
217 "Failed to abandon blob file for handle: ", blob_handle.digest()));
218 }
219 ICING_RETURN_IF_ERROR(blob_info_log_->EraseProto(blob_info_offset));
220 blob_handle_to_offset_.erase(blob_info_itr);
221
222 has_mutated_ = true;
223 return libtextclassifier3::Status::OK;
224 }
225
OpenRead(const PropertyProto::BlobHandleProto & blob_handle)226 libtextclassifier3::StatusOr<int> BlobStore::OpenRead(
227 const PropertyProto::BlobHandleProto& blob_handle) {
228 ICING_RETURN_IF_ERROR(ValidateBlobHandle(blob_handle));
229 std::string blob_handle_str = BuildBlobHandleStr(blob_handle);
230 auto itr = blob_handle_to_offset_.find(blob_handle_str);
231 if (itr == blob_handle_to_offset_.end()) {
232 return absl_ports::NotFoundError(absl_ports::StrCat(
233 "Cannot find the blob for handle: ", blob_handle.digest()));
234 }
235 ICING_ASSIGN_OR_RETURN(BlobInfoProto blob_info,
236 blob_info_log_->ReadProto(itr->second));
237 if (!blob_info.is_committed()) {
238 // The blob is not committed, return error.
239 return absl_ports::NotFoundError(absl_ports::StrCat(
240 "Cannot find the blob for handle: ", blob_handle.digest()));
241 }
242
243 std::string file_name =
244 MakeBlobFileName(base_dir_, blob_info.creation_time_ms());
245 int file_descriptor = filesystem_.OpenForRead(file_name.c_str());
246 if (file_descriptor < 0) {
247 return absl_ports::InternalError(absl_ports::StrCat(
248 "Failed to open blob file for handle: ", blob_handle.digest()));
249 }
250 return file_descriptor;
251 }
252
CommitBlob(const PropertyProto::BlobHandleProto & blob_handle)253 libtextclassifier3::Status BlobStore::CommitBlob(
254 const PropertyProto::BlobHandleProto& blob_handle) {
255 ICING_RETURN_IF_ERROR(ValidateBlobHandle(blob_handle));
256
257 std::string blob_handle_str = BuildBlobHandleStr(blob_handle);
258
259 auto pending_blob_info_itr = blob_handle_to_offset_.find(blob_handle_str);
260 if (pending_blob_info_itr == blob_handle_to_offset_.end()) {
261 return absl_ports::NotFoundError(absl_ports::StrCat(
262 "Cannot find the blob for handle: ", blob_handle.digest()));
263 }
264 int64_t pending_blob_info_offset = pending_blob_info_itr->second;
265
266 ICING_ASSIGN_OR_RETURN(BlobInfoProto blob_info_proto,
267 blob_info_log_->ReadProto(pending_blob_info_offset));
268
269 // Check if the blob is already committed.
270 if (blob_info_proto.is_committed()) {
271 return absl_ports::AlreadyExistsError(absl_ports::StrCat(
272 "The blob is already committed for handle: ", blob_handle.digest()));
273 }
274
275 // Read the file and verify the digest.
276
277 std::string file_name =
278 MakeBlobFileName(base_dir_, blob_info_proto.creation_time_ms());
279 Sha256 sha256;
280 {
281 ScopedFd sfd(filesystem_.OpenForRead(file_name.c_str()));
282 if (!sfd.is_valid()) {
283 return absl_ports::InternalError(absl_ports::StrCat(
284 "Failed to open blob file for handle: ", blob_handle.digest()));
285 }
286
287 int64_t file_size = filesystem_.GetFileSize(sfd.get());
288 if (file_size == Filesystem::kBadFileSize) {
289 return absl_ports::InternalError(absl_ports::StrCat(
290 "Failed to get file size for handle: ", blob_handle.digest()));
291 }
292
293 // Read 8 KiB per iteration
294 int64_t prev_total_read_size = 0;
295 uint8_t buffer[kReadBufferSize];
296 while (prev_total_read_size < file_size) {
297 int32_t size_to_read =
298 std::min<int32_t>(kReadBufferSize, file_size - prev_total_read_size);
299 if (!filesystem_.Read(sfd.get(), buffer, size_to_read)) {
300 return absl_ports::InternalError(absl_ports::StrCat(
301 "Failed to read blob file for handle: ", blob_handle.digest()));
302 }
303
304 sha256.Update(buffer, size_to_read);
305 prev_total_read_size += size_to_read;
306 }
307 }
308
309 std::array<uint8_t, 32> hash = std::move(sha256).Finalize();
310 const std::string& digest = blob_handle.digest();
311
312 // Close active file descriptor and cached pending blob info for the pending
313 // blob handle before we verify the digest. This is needed anyway. We will add
314 // cached pending blob info back if the digest is valid.
315
316 ICING_RETURN_IF_ERROR(blob_info_log_->EraseProto(pending_blob_info_offset));
317
318 if (digest.length() != hash.size() ||
319 digest.compare(0, digest.length(),
320 reinterpret_cast<const char*>(hash.data()),
321 hash.size()) != 0) {
322 // The blob content doesn't match to the digest. Delete this corrupted blob.
323 if (!filesystem_.DeleteFile(file_name.c_str())) {
324 return absl_ports::InternalError(absl_ports::StrCat(
325 "Failed to delete corrupted blob file for handle: ",
326 blob_handle.digest()));
327 }
328 return absl_ports::InvalidArgumentError(
329 "The blob content doesn't match to the digest.");
330 }
331
332 has_mutated_ = true;
333 blob_info_proto.set_is_committed(true);
334 auto blob_info_offset_or = blob_info_log_->WriteProto(blob_info_proto);
335 if (!blob_info_offset_or.ok()) {
336 ICING_LOG(ERROR) << blob_info_offset_or.status().error_message()
337 << "Failed to write blob info";
338 return blob_info_offset_or.status();
339 }
340 blob_handle_to_offset_[blob_handle_str] = blob_info_offset_or.ValueOrDie();
341
342 return libtextclassifier3::Status::OK;
343 }
344
PersistToDisk()345 libtextclassifier3::Status BlobStore::PersistToDisk() {
346 if (has_mutated_) {
347 ICING_RETURN_IF_ERROR(blob_info_log_->PersistToDisk());
348 has_mutated_ = false;
349 }
350 return libtextclassifier3::Status::OK;
351 }
352
GetOrCreateBlobInfo(const std::string & blob_handle_str,const PropertyProto::BlobHandleProto & blob_handle)353 libtextclassifier3::StatusOr<BlobInfoProto> BlobStore::GetOrCreateBlobInfo(
354 const std::string& blob_handle_str,
355 const PropertyProto::BlobHandleProto& blob_handle) {
356 auto itr = blob_handle_to_offset_.find(blob_handle_str);
357 if (itr != blob_handle_to_offset_.end()) {
358 return blob_info_log_->ReadProto(itr->second);
359 }
360
361 // Create a new blob info, we are using creation time as the unique file
362 // name.
363 int64_t timestamp = clock_.GetSystemTimeMilliseconds();
364 std::string file_name = std::to_string(timestamp);
365 while (known_file_names_.find(file_name) != known_file_names_.end()) {
366 ++timestamp;
367 file_name = std::to_string(timestamp);
368 }
369 known_file_names_.insert(file_name);
370
371 BlobInfoProto blob_info_proto;
372 *blob_info_proto.mutable_blob_handle() = blob_handle;
373 blob_info_proto.set_creation_time_ms(timestamp);
374 blob_info_proto.set_is_committed(false);
375
376 auto blob_info_offset_or = blob_info_log_->WriteProto(blob_info_proto);
377 if (!blob_info_offset_or.ok()) {
378 ICING_LOG(ERROR) << blob_info_offset_or.status().error_message()
379 << "Failed to write blob info";
380 return blob_info_offset_or.status();
381 }
382
383 has_mutated_ = true;
384 blob_handle_to_offset_[blob_handle_str] = blob_info_offset_or.ValueOrDie();
385
386 return blob_info_proto;
387 }
388
389 std::unordered_set<std::string>
GetPotentiallyOptimizableBlobHandles()390 BlobStore::GetPotentiallyOptimizableBlobHandles() {
391 int64_t current_time_ms = clock_.GetSystemTimeMilliseconds();
392 if (orphan_blob_time_to_live_ms_ > current_time_ms) {
393 // Nothing to optimize, return empty set.
394 return std::unordered_set<std::string>();
395 }
396 int64_t expired_threshold = current_time_ms - orphan_blob_time_to_live_ms_;
397 std::unordered_set<std::string> expired_blob_handles;
398 auto itr = blob_info_log_->GetIterator();
399 while (itr.Advance().ok()) {
400 auto blob_info_proto_or = blob_info_log_->ReadProto(itr.GetOffset());
401 if (!blob_info_proto_or.ok()) {
402 continue;
403 }
404 BlobInfoProto blob_info_proto = std::move(blob_info_proto_or).ValueOrDie();
405 if (blob_info_proto.creation_time_ms() < expired_threshold) {
406 expired_blob_handles.insert(
407 BuildBlobHandleStr(blob_info_proto.blob_handle()));
408 }
409 }
410 return expired_blob_handles;
411 }
412
Optimize(const std::unordered_set<std::string> & dead_blob_handles)413 libtextclassifier3::Status BlobStore::Optimize(
414 const std::unordered_set<std::string>& dead_blob_handles) {
415 // Create the temp blob info log file.
416 std::string temp_blob_info_proto_file_name =
417 absl_ports::StrCat(MakeBlobInfoProtoLogFileName(base_dir_), "_temp");
418 if (!filesystem_.DeleteFile(temp_blob_info_proto_file_name.c_str())) {
419 return absl_ports::InternalError(
420 "Unable to delete temp file to prepare to build new blob proto file.");
421 }
422
423 ICING_ASSIGN_OR_RETURN(PortableFileBackedProtoLog<BlobInfoProto>::CreateResult
424 temp_log_create_result,
425 PortableFileBackedProtoLog<BlobInfoProto>::Create(
426 &filesystem_, temp_blob_info_proto_file_name,
427 PortableFileBackedProtoLog<BlobInfoProto>::Options(
428 /*compress_in=*/true, constants::kMaxProtoSize,
429 compression_level_)));
430 std::unique_ptr<PortableFileBackedProtoLog<BlobInfoProto>> new_blob_info_log =
431 std::move(temp_log_create_result.proto_log);
432
433 auto itr = blob_info_log_->GetIterator();
434 std::unordered_map<std::string, int32_t> new_blob_handle_to_offset;
435 while (itr.Advance().ok()) {
436 auto blob_info_proto_or = blob_info_log_->ReadProto(itr.GetOffset());
437 if (!blob_info_proto_or.ok()) {
438 if (absl_ports::IsNotFound(blob_info_proto_or.status())) {
439 // Skip erased proto.
440 continue;
441 }
442
443 // Return real error.
444 return std::move(blob_info_proto_or).status();
445 }
446 BlobInfoProto blob_info_proto = std::move(blob_info_proto_or).ValueOrDie();
447 std::string blob_handle_str =
448 BuildBlobHandleStr(blob_info_proto.blob_handle());
449 if (dead_blob_handles.find(blob_handle_str) != dead_blob_handles.end()) {
450 // Delete all dead blob files.
451
452 std::string file_name =
453 MakeBlobFileName(base_dir_, blob_info_proto.creation_time_ms());
454 if (!filesystem_.DeleteFile(file_name.c_str())) {
455 return absl_ports::InternalError(
456 absl_ports::StrCat("Failed to delete blob file: ", file_name));
457 }
458 } else {
459 // Write the alive blob info to the new blob info log file.
460 ICING_ASSIGN_OR_RETURN(int32_t new_offset,
461 new_blob_info_log->WriteProto(blob_info_proto));
462 new_blob_handle_to_offset[blob_handle_str] = new_offset;
463 }
464 }
465 new_blob_info_log->PersistToDisk();
466 new_blob_info_log.reset();
467 blob_info_log_.reset();
468 std::string old_blob_info_proto_file_name =
469 MakeBlobInfoProtoLogFileName(base_dir_);
470 // Then we swap the new key mapper directory with the old one.
471 if (!filesystem_.SwapFiles(old_blob_info_proto_file_name.c_str(),
472 temp_blob_info_proto_file_name.c_str())) {
473 return absl_ports::InternalError(
474 "Unable to apply new blob store due to failed swap!");
475 }
476
477 // Delete the temp file, don't need to throw error if it fails, it will be
478 // deleted in the next run.
479 filesystem_.DeleteFile(temp_blob_info_proto_file_name.c_str());
480
481 ICING_ASSIGN_OR_RETURN(
482 PortableFileBackedProtoLog<BlobInfoProto>::CreateResult log_create_result,
483 PortableFileBackedProtoLog<BlobInfoProto>::Create(
484 &filesystem_, old_blob_info_proto_file_name,
485 PortableFileBackedProtoLog<BlobInfoProto>::Options(
486 /*compress_in=*/true, constants::kMaxProtoSize,
487 compression_level_)));
488 blob_info_log_ = std::move(log_create_result.proto_log);
489 blob_handle_to_offset_ = std::move(new_blob_handle_to_offset);
490 return libtextclassifier3::Status::OK;
491 }
492
493 libtextclassifier3::StatusOr<std::vector<NamespaceBlobStorageInfoProto>>
GetStorageInfo() const494 BlobStore::GetStorageInfo() const {
495 // Get the file size of each namespace offset.
496 std::unordered_map<std::string, NamespaceBlobStorageInfoProto>
497 namespace_to_storage_info;
498 auto itr = blob_info_log_->GetIterator();
499 while (itr.Advance().ok()) {
500 auto blob_info_proto_or = blob_info_log_->ReadProto(itr.GetOffset());
501 if (!blob_info_proto_or.ok()) {
502 if (absl_ports::IsNotFound(blob_info_proto_or.status())) {
503 // Skip erased proto.
504 continue;
505 }
506
507 // Return real error.
508 return std::move(blob_info_proto_or).status();
509 }
510 BlobInfoProto blob_info_proto = std::move(blob_info_proto_or).ValueOrDie();
511
512 std::string file_name =
513 MakeBlobFileName(base_dir_, blob_info_proto.creation_time_ms());
514 int64_t file_size = filesystem_.GetFileSize(file_name.c_str());
515 if (file_size == Filesystem::kBadFileSize) {
516 ICING_LOG(WARNING) << "Bad file size for blob file: " << file_name;
517 continue;
518 }
519 std::string name_space = blob_info_proto.blob_handle().namespace_();
520 NamespaceBlobStorageInfoProto namespace_blob_storage_info =
521 namespace_to_storage_info[name_space];
522 namespace_blob_storage_info.set_namespace_(name_space);
523 namespace_blob_storage_info.set_blob_size(
524 namespace_blob_storage_info.blob_size() + file_size);
525 namespace_blob_storage_info.set_num_blobs(
526 namespace_blob_storage_info.num_blobs() + 1);
527 namespace_to_storage_info[name_space] =
528 std::move(namespace_blob_storage_info);
529 }
530
531 // Create the namespace blob storage info for each namespace.
532 std::vector<NamespaceBlobStorageInfoProto> namespace_blob_storage_infos;
533 namespace_blob_storage_infos.reserve(namespace_to_storage_info.size());
534 for (const auto& [_, namespace_blob_storage_info] :
535 namespace_to_storage_info) {
536 namespace_blob_storage_infos.push_back(
537 std::move(namespace_blob_storage_info));
538 }
539
540 return namespace_blob_storage_infos;
541 }
542
543 } // namespace lib
544 } // namespace icing
545