xref: /aosp_15_r20/external/icing/icing/store/blob-store.cc (revision 8b6cd535a057e39b3b86660c4aa06c99747c2136)
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