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-filesystem.h"
16
17 #include <dirent.h>
18 #include <dlfcn.h>
19 #include <fcntl.h>
20 #include <fnmatch.h>
21 #include <pthread.h>
22 #include <sys/mman.h>
23 #include <sys/resource.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27
28 #include <algorithm>
29 #include <cerrno>
30 #include <cstddef>
31 #include <cstdint>
32 #include <cstdio>
33 #include <cstring>
34 #include <memory>
35 #include <string>
36 #include <unordered_set>
37 #include <vector>
38
39 #include "icing/absl_ports/str_cat.h"
40 #include "icing/legacy/index/icing-mmapper.h"
41 #include "icing/legacy/portable/icing-zlib.h"
42 #include "icing/util/logging.h"
43
44 using std::vector;
45
46 namespace icing {
47 namespace lib {
48
49 namespace {
50
51 // The size of the block for st_blksize returned by stat() and as a
52 // consequence also the granularity of GetDiskUsage(). It seems that there is
53 // no appropriate constant for this. See http://linux.die.net/man/2/stat
54 constexpr int kStatBlockSize = 512;
55
56 // Logs information about open file descriptors.
57 //
58 // This function uses getrlimit() to find the maximum number of file
59 // descriptors, then calls readlink("/proc/self/fd/N") for each possible file
60 // descriptor number to get a description of the open file from procfs.
61 //
62 // We don't use readdir() to list the contents of /proc/self/fd (which would be
63 // the more obvious approach) because that would require a free file descriptor
64 // to open the directory, while we call this function when all file descriptors
65 // are in use.
LogOpenFileDescriptors()66 void LogOpenFileDescriptors() {
67 // Determine the limit on file descriptor numbers. RLIMIT_NOFILE should return
68 // the maximum file descriptor + 1, which is 1024 on Android by default. We
69 // restrict the limit to 4096 so we don't take too much time if the value
70 // turns out to be much higher for some reason.
71 constexpr int kMaxFileDescriptorsToStat = 4096;
72 struct rlimit rlim = {0, 0};
73 if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
74 ICING_LOG(ERROR) << "getrlimit() failed (errno=" << errno << ")";
75 return;
76 }
77 int fd_lim = rlim.rlim_cur;
78 if (fd_lim > kMaxFileDescriptorsToStat) {
79 ICING_LOG(ERROR) << "Maximum number of file descriptors (" << fd_lim
80 << ") too large.";
81 fd_lim = kMaxFileDescriptorsToStat;
82 }
83 ICING_LOG(ERROR) << "Listing up to " << fd_lim << " file descriptors.";
84
85 // Verify that /proc/self/fd is a directory. If not, procfs is not mounted or
86 // inaccessible for some other reason. In that case, there's no point trying
87 // to read from it.
88 struct stat statbuf;
89 if (stat("/proc/self/fd", &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) {
90 ICING_LOG(ERROR) << "/proc/self/fd not available. Giving up.";
91 return;
92 }
93
94 // Now read each link individually.
95 const int path_size = 1024;
96 char path[path_size];
97 const int target_size = 1024;
98 char target[target_size];
99 for (int fd = 0; fd < fd_lim; ++fd) {
100 snprintf(path, path_size, "/proc/self/fd/%d", fd);
101 ssize_t len = readlink(path, target, target_size);
102 if (len >= 0) {
103 // Zero-terminate the buffer, because readlink() won't.
104 target[len < target_size ? len : target_size - 1] = '\0';
105 ICING_LOG(ERROR) << "fd " << fd << " -> \"" << target << "\"";
106 } else if (errno != ENOENT) {
107 ICING_LOG(ERROR) << "fd " << fd << " -> ? (errno=" << errno << ")";
108 }
109 }
110 ICING_LOG(ERROR) << "File descriptor list complete.";
111 }
112
113 // Logs an error formatted as: desc1 + file_name + desc2 + strerror(errnum).
114 //
115 // If errnum == EMFILE (too many open files), then it also logs a list of open
116 // file descriptors (see LogOpenFileDescriptors() above).
LogOpenError(const char * desc1,const char * file_name,const char * desc2,int errnum)117 void LogOpenError(const char *desc1, const char *file_name, const char *desc2,
118 int errnum) {
119 ICING_LOG(ERROR) << desc1 << file_name << desc2 << strerror(errnum);
120 if (errnum == EMFILE) {
121 LogOpenFileDescriptors();
122 }
123 }
124
125 // Recursive implementation of ListDirectory. Prefix is used to prepend the
126 // directory name during recursion.
127 // We cannot use scandir due to a bug in old platform versions. See b/7339844.
ListDirectoryInternal(const char * dir_name,const std::unordered_set<std::string> & exclude,bool recursive,const char * prefix,std::vector<std::string> * entries)128 bool ListDirectoryInternal(const char *dir_name,
129 const std::unordered_set<std::string> &exclude,
130 bool recursive, const char *prefix,
131 std::vector<std::string> *entries) {
132 DIR *dir = opendir(dir_name);
133 if (!dir) {
134 LogOpenError("Unable to open directory ", dir_name, ": ", errno);
135 return false;
136 }
137
138 dirent *p;
139 // readdir's implementation seems to be thread safe.
140 while ((p = readdir(dir)) != nullptr) {
141 std::string file_name(p->d_name);
142 if (file_name == "." || file_name == ".." ||
143 exclude.find(file_name) != exclude.end()) {
144 continue;
145 }
146 std::string relative_path = absl_ports::StrCat(prefix, p->d_name);
147 entries->push_back(relative_path);
148 // Recurse down directories, if requested.
149 if (recursive && (p->d_type == DT_DIR)) {
150 std::string sub_dir_name = absl_ports::StrCat(dir_name, "/", p->d_name);
151 std::string relative_path_with_slash =
152 absl_ports::StrCat(relative_path, "/");
153 if (!ListDirectoryInternal(sub_dir_name.c_str(), exclude, recursive,
154 relative_path_with_slash.c_str(), entries)) {
155 return false;
156 }
157 }
158 }
159 if (closedir(dir) != 0) {
160 ICING_LOG(ERROR) << "Error closing " << dir_name << ": " << strerror(errno);
161 }
162 return true;
163 }
164
165 } // namespace
166
~IcingScopedFd()167 IcingScopedFd::~IcingScopedFd() {
168 if (fd_ >= 0) {
169 close(fd_);
170 }
171 }
172
reset(int fd)173 void IcingScopedFd::reset(int fd) {
174 if (fd_ >= 0) {
175 close(fd_);
176 }
177 fd_ = fd;
178 }
179
180 const uint64_t IcingFilesystem::kBadFileSize;
181
DeleteFile(const char * file_name) const182 bool IcingFilesystem::DeleteFile(const char *file_name) const {
183 ICING_VLOG(1) << "Deleting file " << file_name;
184 int ret = unlink(file_name);
185 bool success = (ret == 0) || (errno == ENOENT);
186 if (!success) {
187 ICING_LOG(ERROR) << "Deleting file " << file_name
188 << " failed: " << strerror(errno);
189 }
190 return success;
191 }
192
DeleteDirectory(const char * dir_name) const193 bool IcingFilesystem::DeleteDirectory(const char *dir_name) const {
194 int ret = rmdir(dir_name);
195 bool success = (ret == 0) || (errno == ENOENT);
196 if (!success) {
197 ICING_LOG(ERROR) << "Deleting directory " << dir_name
198 << " failed: " << strerror(errno);
199 }
200 return success;
201 }
202
DeleteDirectoryRecursively(const char * dir_name) const203 bool IcingFilesystem::DeleteDirectoryRecursively(const char *dir_name) const {
204 // Ensure the dir_name really is a directory and exists.
205 struct stat st;
206 if (stat(dir_name, &st) < 0) {
207 if (errno == ENOENT) {
208 return true; // If directory didn't exist, this was successful.
209 }
210 ICING_LOG(ERROR) << "Stat " << dir_name << " failed: " << strerror(errno);
211 return false;
212 }
213 vector<std::string> entries;
214 if (!ListDirectory(dir_name, &entries)) {
215 return false;
216 }
217
218 bool success = true;
219 for (vector<std::string>::iterator i = entries.begin(); i != entries.end();
220 ++i) {
221 std::string filename = std::string(dir_name) + '/' + *i;
222 if (stat(filename.c_str(), &st) < 0) {
223 ICING_LOG(ERROR) << "Stat " << filename << " failed: " << strerror(errno);
224 success = false;
225 } else if (S_ISDIR(st.st_mode)) {
226 success = DeleteDirectoryRecursively(filename.c_str()) && success;
227 } else {
228 success = DeleteFile(filename.c_str()) && success;
229 }
230 }
231
232 if (success) {
233 success = DeleteDirectory(dir_name);
234 }
235
236 return success;
237 }
238
FileExists(const char * file_name) const239 bool IcingFilesystem::FileExists(const char *file_name) const {
240 bool exists = false;
241 struct stat st;
242 if (stat(file_name, &st) == 0) {
243 exists = S_ISREG(st.st_mode) != 0;
244 } else {
245 if (errno != ENOENT) {
246 ICING_LOG(ERROR) << "Unable to stat file " << file_name << ": "
247 << strerror(errno);
248 }
249 exists = false;
250 }
251 return exists;
252 }
253
DirectoryExists(const char * dir_name) const254 bool IcingFilesystem::DirectoryExists(const char *dir_name) const {
255 bool exists = false;
256 struct stat st;
257 if (stat(dir_name, &st) == 0) {
258 exists = S_ISDIR(st.st_mode) != 0;
259 } else {
260 if (errno != ENOENT) {
261 ICING_LOG(ERROR) << "Unable to stat directory " << dir_name << ": "
262 << strerror(errno);
263 }
264 exists = false;
265 }
266 return exists;
267 }
268
GetBasenameIndex(const char * file_name) const269 int IcingFilesystem::GetBasenameIndex(const char *file_name) const {
270 // Find final slash.
271 const char *last_slash = strrchr(file_name, '/');
272 if (!last_slash) {
273 // file_name is just basename.
274 return 0;
275 }
276
277 // Skip slash.
278 return last_slash + 1 - file_name;
279 }
280
GetBasename(const char * file_name) const281 std::string IcingFilesystem::GetBasename(const char *file_name) const {
282 size_t len = strlen(file_name);
283 int idx = GetBasenameIndex(file_name);
284 return std::string(file_name + idx, len - idx);
285 }
286
GetDirname(const char * file_name) const287 std::string IcingFilesystem::GetDirname(const char *file_name) const {
288 int idx = GetBasenameIndex(file_name);
289 // Remove the trailing slash
290 if (idx > 0) {
291 idx -= 1;
292 }
293 return std::string(file_name, idx);
294 }
295
ListDirectory(const char * dir_name,vector<std::string> * entries) const296 bool IcingFilesystem::ListDirectory(const char *dir_name,
297 vector<std::string> *entries) const {
298 entries->clear();
299 return ListDirectory(dir_name, /*exclude=*/{}, /*recursive=*/false, entries);
300 }
301
ListDirectory(const char * dir_name,const std::unordered_set<std::string> & exclude,bool recursive,std::vector<std::string> * entries) const302 bool IcingFilesystem::ListDirectory(
303 const char *dir_name, const std::unordered_set<std::string> &exclude,
304 bool recursive, std::vector<std::string> *entries) const {
305 return ListDirectoryInternal(dir_name, exclude, recursive, /*prefix=*/"",
306 entries);
307 }
308
GetMatchingFiles(const char * glob,vector<std::string> * matches) const309 bool IcingFilesystem::GetMatchingFiles(const char *glob,
310 vector<std::string> *matches) const {
311 matches->clear();
312
313 // Split dirname/basename.
314 int basename_idx = GetBasenameIndex(glob);
315 if (basename_idx == 0) {
316 // We need a directory.
317 ICING_VLOG(1) << "Expected directory, no matching files for: " << glob;
318 return true;
319 }
320 const char *basename_glob = glob + basename_idx;
321 std::string dirname(glob, basename_idx);
322 vector<std::string> entries;
323 if (!ListDirectory(dirname.c_str(), &entries) && errno != ENOENT) {
324 return false;
325 }
326
327 for (vector<std::string>::iterator i = entries.begin(); i != entries.end();
328 ++i) {
329 // The filename needs to match glob following last_slash.
330 if (!fnmatch(basename_glob, i->c_str(), FNM_PATHNAME)) {
331 // Add it to the list.
332 matches->push_back(dirname + *i);
333 }
334 }
335 return true;
336 }
337
OpenForWrite(const char * file_name) const338 int IcingFilesystem::OpenForWrite(const char *file_name) const {
339 int fd = open(file_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
340 if (fd < 0) {
341 LogOpenError("Opening file ", file_name, " for write failed: ", errno);
342 }
343 return fd;
344 }
345
OpenForAppend(const char * file_name) const346 int IcingFilesystem::OpenForAppend(const char *file_name) const {
347 // Don't use the O_APPEND flag because, although it opens for
348 // append, it doesn't set the file cursor to at the end until
349 // first write occurs. This can be confusing if you expect
350 // the file position at the end. Instead, explicitly
351 // seek to end after opening.
352 int fd = open(file_name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
353 if (fd < 0) {
354 LogOpenError("Opening file ", file_name, " for write failed: ", errno);
355 } else {
356 lseek(fd, 0, SEEK_END);
357 }
358 return fd;
359 }
360
OpenForRead(const char * file_name) const361 int IcingFilesystem::OpenForRead(const char *file_name) const {
362 int fd = open(file_name, O_RDONLY);
363 if (fd < 0) {
364 LogOpenError("Opening file ", file_name, " for read failed: ", errno);
365 }
366 return fd;
367 }
368
GetFileSize(int fd) const369 uint64_t IcingFilesystem::GetFileSize(int fd) const {
370 struct stat st;
371 uint64_t size = kBadFileSize;
372 if (fstat(fd, &st) < 0) {
373 ICING_LOG(ERROR) << "Unable to stat file: " << strerror(errno);
374 } else {
375 size = st.st_size;
376 }
377 return size;
378 }
379
GetFileSize(const char * filename) const380 uint64_t IcingFilesystem::GetFileSize(const char *filename) const {
381 struct stat st;
382 uint64_t size = kBadFileSize;
383 if (stat(filename, &st) < 0) {
384 ICING_LOG(ERROR) << "Unable to stat file " << filename << ": "
385 << strerror(errno);
386 } else {
387 size = st.st_size;
388 }
389 return size;
390 }
391
Truncate(int fd,uint64_t new_size) const392 bool IcingFilesystem::Truncate(int fd, uint64_t new_size) const {
393 int ret = ftruncate(fd, new_size);
394 if (ret == 0) {
395 lseek(fd, new_size, SEEK_SET);
396 } else {
397 ICING_LOG(ERROR) << "Unable to truncate file: " << strerror(errno);
398 }
399 return (ret == 0);
400 }
401
Truncate(const char * filename,uint64_t new_size) const402 bool IcingFilesystem::Truncate(const char *filename, uint64_t new_size) const {
403 int fd = OpenForAppend(filename);
404 if (fd == -1) {
405 return false;
406 }
407 bool success = Truncate(fd, new_size);
408 close(fd);
409 return success;
410 }
411
Grow(int fd,uint64_t new_size) const412 bool IcingFilesystem::Grow(int fd, uint64_t new_size) const {
413 int ret = ftruncate(fd, new_size);
414 if (ret != 0) {
415 ICING_LOG(ERROR) << "Unable to grow file: " << strerror(errno);
416 }
417 return (ret == 0);
418 }
419
GrowUsingPWrite(int fd,uint64_t new_size) const420 bool IcingFilesystem::GrowUsingPWrite(int fd, uint64_t new_size) const {
421 uint64_t curr_file_size = GetFileSize(fd);
422 if (curr_file_size == kBadFileSize) {
423 return false;
424 }
425 if (new_size <= curr_file_size) {
426 return true;
427 }
428
429 uint64_t page_size = IcingMMapper::system_page_size();
430 auto buf = std::make_unique<uint8_t[]>(page_size);
431 uint64_t size_to_write = std::min(page_size - (curr_file_size % page_size),
432 new_size - curr_file_size);
433 while (size_to_write > 0 && curr_file_size < new_size) {
434 if (!PWrite(fd, curr_file_size, buf.get(), size_to_write)) {
435 ICING_LOG(ERROR) << "Failed to grow file using pwrite.";
436 return false;
437 }
438 curr_file_size += size_to_write;
439 size_to_write = std::min(page_size - (curr_file_size % page_size),
440 new_size - curr_file_size);
441 }
442 return true;
443 }
444
Write(int fd,const void * data,size_t data_size) const445 bool IcingFilesystem::Write(int fd, const void *data, size_t data_size) const {
446 size_t write_len = data_size;
447 do {
448 // Don't try to write too much at once.
449 size_t chunk_size = std::min<size_t>(write_len, 64u * 1024);
450 ssize_t wrote = write(fd, data, chunk_size);
451 if (wrote < 0) {
452 ICING_LOG(ERROR) << "Bad write: " << strerror(errno);
453 return false;
454 }
455 data = static_cast<const uint8_t *>(data) + wrote;
456 write_len -= wrote;
457 } while (write_len > 0);
458 return true;
459 }
460
PWrite(int fd,off_t offset,const void * data,size_t data_size) const461 bool IcingFilesystem::PWrite(int fd, off_t offset, const void *data,
462 size_t data_size) const {
463 size_t write_len = data_size;
464 do {
465 // Don't try to write too much at once.
466 size_t chunk_size = std::min<size_t>(write_len, 64u * 1024);
467 ssize_t wrote = pwrite(fd, data, chunk_size, offset);
468 if (wrote < 0) {
469 ICING_LOG(ERROR) << "Bad write: " << strerror(errno);
470 return false;
471 }
472 data = static_cast<const uint8_t *>(data) + wrote;
473 write_len -= wrote;
474 offset += wrote;
475 } while (write_len > 0);
476 return true;
477 }
478
DataSync(int fd) const479 bool IcingFilesystem::DataSync(int fd) const {
480 #ifdef __APPLE__ // iOS has no fdatasync(), only fsync()
481 int result = fsync(fd);
482 #else
483 int result = fdatasync(fd);
484 #endif
485
486 if (result < 0) {
487 ICING_LOG(ERROR) << "Unable to sync data: " << strerror(errno);
488 return false;
489 }
490 return true;
491 }
492
RenameFile(const char * old_name,const char * new_name) const493 bool IcingFilesystem::RenameFile(const char *old_name,
494 const char *new_name) const {
495 if (rename(old_name, new_name) < 0) {
496 ICING_LOG(ERROR) << "Unable to rename file " << old_name << " to "
497 << new_name << ": " << strerror(errno);
498 return false;
499 }
500 return true;
501 }
502
SwapFiles(const char * one,const char * two) const503 bool IcingFilesystem::SwapFiles(const char *one, const char *two) const {
504 std::string tmp_name = absl_ports::StrCat(one, ".tmp");
505 const char *tmp_cstr = tmp_name.c_str();
506
507 // Blow away a tmp file if it already exists
508 if (FileExists(tmp_cstr) && !DeleteFile(tmp_cstr)) {
509 return false;
510 }
511 if (DirectoryExists(tmp_cstr) && !DeleteDirectoryRecursively(tmp_cstr)) {
512 return false;
513 }
514
515 // Perform the swap
516 if (!RenameFile(one, tmp_cstr)) {
517 return false;
518 }
519 if (!RenameFile(two, one)) {
520 return false;
521 }
522 if (!RenameFile(tmp_cstr, two)) {
523 return false;
524 }
525
526 return true;
527 }
528
CreateDirectory(const char * dir_name) const529 bool IcingFilesystem::CreateDirectory(const char *dir_name) const {
530 bool success = DirectoryExists(dir_name);
531 if (!success) {
532 if (mkdir(dir_name, S_IRUSR | S_IWUSR | S_IXUSR) == 0) {
533 success = true;
534 } else {
535 ICING_LOG(ERROR) << "Creating directory " << dir_name
536 << " failed: " << strerror(errno);
537 }
538 }
539 return success;
540 }
541
CreateDirectoryRecursively(const char * dir_name) const542 bool IcingFilesystem::CreateDirectoryRecursively(const char *dir_name) const {
543 if ((strlen(dir_name) == 0) || DirectoryExists(dir_name)) {
544 return true;
545 }
546 std::string path_before = GetDirname(dir_name);
547 if (!CreateDirectoryRecursively(path_before.c_str())) {
548 return false;
549 }
550 return CreateDirectory(dir_name);
551 }
552
CopyFile(const char * src,const char * dst) const553 bool IcingFilesystem::CopyFile(const char *src, const char *dst) const {
554 bool success = false;
555
556 int src_fd = -1;
557 int dst_fd = -1;
558 uint64_t size = 0;
559 IcingMMapper mapper(true, MAP_PRIVATE);
560
561 if ((src_fd = OpenForRead(src)) < 0) {
562 goto end;
563 }
564 if ((dst_fd = OpenForWrite(dst)) < 0) {
565 goto end;
566 }
567 size = GetFileSize(src_fd);
568 mapper.Remap(src_fd, 0, size);
569 if (!mapper.is_valid()) {
570 goto end;
571 }
572 success = Write(dst_fd, mapper.address(), mapper.len());
573
574 end:
575 if (src_fd > 0) close(src_fd);
576 if (dst_fd > 0) close(dst_fd);
577 if (!success) {
578 ICING_LOG(ERROR) << "Couldn't copy file " << src << " to " << dst;
579 }
580 return success;
581 }
582
ComputeChecksum(int fd,uint32_t * checksum,uint64_t offset,uint64_t length) const583 bool IcingFilesystem::ComputeChecksum(int fd, uint32_t *checksum,
584 uint64_t offset, uint64_t length) const {
585 if (length == 0) {
586 return true;
587 }
588 IcingMMapper mapper(fd, true, offset, length, MAP_PRIVATE);
589 if (!mapper.is_valid()) {
590 return false;
591 }
592 *checksum = adler32(*checksum, mapper.address(), mapper.len());
593 return true;
594 }
595
GetDiskUsage(int fd) const596 uint64_t IcingFilesystem::GetDiskUsage(int fd) const {
597 struct stat st;
598 if (fstat(fd, &st) < 0) {
599 ICING_LOG(ERROR) << "Unable to stat file: " << strerror(errno);
600 return kBadFileSize;
601 }
602 return st.st_blocks * kStatBlockSize;
603 }
604
GetFileDiskUsage(const char * path) const605 uint64_t IcingFilesystem::GetFileDiskUsage(const char *path) const {
606 struct stat st;
607 if (stat(path, &st) != 0) {
608 ICING_LOG(ERROR) << "Unable to stat " << path << ": " << strerror(errno);
609 return kBadFileSize;
610 }
611 return st.st_blocks * kStatBlockSize;
612 }
613
GetDiskUsage(const char * path) const614 uint64_t IcingFilesystem::GetDiskUsage(const char *path) const {
615 struct stat st;
616 if (stat(path, &st) != 0) {
617 ICING_LOG(ERROR) << "Unable to stat " << path << ": " << strerror(errno);
618 return kBadFileSize;
619 }
620 uint64_t result = st.st_blocks * kStatBlockSize;
621 if (S_ISDIR(st.st_mode)) {
622 vector<std::string> list;
623 if (!ListDirectory(path, &list)) {
624 return kBadFileSize;
625 }
626 for (vector<std::string>::iterator i = list.begin(); i != list.end(); ++i) {
627 std::string sub_path = std::string(path) + '/' + *i;
628 uint64_t sub_usage = GetDiskUsage(sub_path.c_str());
629 if (sub_usage != kBadFileSize) {
630 result += sub_usage;
631 } // Else just ignore the failing entry.
632 }
633 }
634 return result;
635 }
636
IncrementByOrSetInvalid(uint64_t size,uint64_t * to_increment)637 void IcingFilesystem::IncrementByOrSetInvalid(uint64_t size,
638 uint64_t *to_increment) {
639 if (*to_increment == kBadFileSize) {
640 return;
641 }
642 if (size == kBadFileSize) {
643 *to_increment = kBadFileSize;
644 return;
645 }
646 *to_increment += size;
647 }
648
649 } // namespace lib
650 } // namespace icing
651