1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define DEBUG false  // STOPSHIP if true
18 #include "Log.h"
19 
20 #include "android-base/stringprintf.h"
21 #include "guardrail/StatsdStats.h"
22 #include "storage/StorageManager.h"
23 #include "stats_log_util.h"
24 
25 #include <android-base/file.h>
26 #include <private/android_filesystem_config.h>
27 #include <fstream>
28 
29 namespace android {
30 namespace os {
31 namespace statsd {
32 
33 using android::util::FIELD_COUNT_REPEATED;
34 using android::util::FIELD_TYPE_MESSAGE;
35 using std::map;
36 
37 /**
38  * NOTE: these directories are protected by SELinux, any changes here must also update
39  * the SELinux policies.
40  */
41 #define STATS_DATA_DIR "/data/misc/stats-data"
42 #define STATS_SERVICE_DIR "/data/misc/stats-service"
43 #define TRAIN_INFO_DIR "/data/misc/train-info"
44 #define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin"
45 
46 // Magic word at the start of the train info file, change this if changing the file format
47 const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff;
48 
49 // for ConfigMetricsReportList
50 const int FIELD_ID_REPORTS = 2;
51 
52 std::mutex StorageManager::sTrainInfoMutex;
53 
54 using android::base::StringPrintf;
55 using std::unique_ptr;
56 
57 struct FileName {
58     int64_t mTimestampSec;
59     int mUid;
60     int64_t mConfigId;
61     bool mIsHistory;
getFullFileNameandroid::os::statsd::FileName62     string getFullFileName(const char* path) {
63         return StringPrintf("%s/%lld_%d_%lld%s", path, (long long)mTimestampSec, (int)mUid,
64                             (long long)mConfigId, (mIsHistory ? "_history" : ""));
65     };
66 };
67 
getDataFileName(long wallClockSec,int uid,int64_t id)68 string StorageManager::getDataFileName(long wallClockSec, int uid, int64_t id) {
69     return StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, wallClockSec, uid,
70                         (long long)id);
71 }
72 
getDataHistoryFileName(long wallClockSec,int uid,int64_t id)73 string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_t id) {
74     return StringPrintf("%s/%ld_%d_%lld_history", STATS_DATA_DIR, wallClockSec, uid,
75                         (long long)id);
76 }
77 
78 // Returns array of int64_t which contains timestamp in seconds, uid,
79 // configID and whether the file is a local history file.
parseFileName(char * name,FileName * output)80 static void parseFileName(char* name, FileName* output) {
81     int64_t result[3];
82     int index = 0;
83     char* substr = strtok(name, "_");
84     while (substr != nullptr && index < 3) {
85         result[index] = StrToInt64(substr);
86         index++;
87         substr = strtok(nullptr, "_");
88     }
89     // When index ends before hitting 3, file name is corrupted. We
90     // intentionally put -1 at index 0 to indicate the error to caller.
91     // TODO(b/110563137): consider removing files with unexpected name format.
92     if (index < 3) {
93         result[0] = -1;
94     }
95 
96     output->mTimestampSec = result[0];
97     output->mUid = result[1];
98     output->mConfigId = result[2];
99     // check if the file is a local history.
100     output->mIsHistory = (substr != nullptr && strcmp("history", substr) == 0);
101 }
102 
writeFile(const char * file,const void * buffer,int numBytes)103 void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) {
104     int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
105     if (fd == -1) {
106         VLOG("Attempt to access %s but failed", file);
107         return;
108     }
109     trimToFit(STATS_SERVICE_DIR);
110     trimToFit(STATS_DATA_DIR);
111 
112     if (android::base::WriteFully(fd, buffer, numBytes)) {
113         VLOG("Successfully wrote %s", file);
114     } else {
115         ALOGE("Failed to write %s", file);
116     }
117 
118     int result = fchown(fd, AID_STATSD, AID_STATSD);
119     if (result) {
120         VLOG("Failed to chown %s to statsd", file);
121     }
122 
123     close(fd);
124 }
125 
writeTrainInfo(int64_t trainVersionCode,const std::string & trainName,int32_t status,const std::vector<int64_t> & experimentIds)126 bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName,
127                                     int32_t status, const std::vector<int64_t>& experimentIds) {
128     std::lock_guard<std::mutex> lock(sTrainInfoMutex);
129 
130     deleteAllFiles(TRAIN_INFO_DIR);
131 
132     int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
133     if (fd == -1) {
134         VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH);
135         return false;
136     }
137 
138     size_t result;
139 
140     // Write the magic word
141     result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC));
142     if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) {
143         VLOG("Failed to wrtie train info magic");
144         close(fd);
145         return false;
146     }
147 
148     // Write the train version
149     const size_t trainVersionCodeByteCount = sizeof(trainVersionCode);
150     result = write(fd, &trainVersionCode, trainVersionCodeByteCount);
151     if (result != trainVersionCodeByteCount) {
152         VLOG("Failed to wrtie train version code");
153         close(fd);
154         return false;
155     }
156 
157     // Write # of bytes in trainName to file
158     const size_t trainNameSize = trainName.size();
159     const size_t trainNameSizeByteCount = sizeof(trainNameSize);
160     result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount);
161     if (result != trainNameSizeByteCount) {
162         VLOG("Failed to write train name size");
163         close(fd);
164         return false;
165     }
166 
167     // Write trainName to file
168     result = write(fd, trainName.c_str(), trainNameSize);
169     if (result != trainNameSize) {
170         VLOG("Failed to write train name");
171         close(fd);
172         return false;
173     }
174 
175     // Write status to file
176     const size_t statusByteCount = sizeof(status);
177     result = write(fd, (uint8_t*)&status, statusByteCount);
178     if (result != statusByteCount) {
179         VLOG("Failed to write status");
180         close(fd);
181         return false;
182     }
183 
184     // Write experiment id count to file.
185     const size_t experimentIdsCount = experimentIds.size();
186     const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount);
187     result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount);
188     if (result != experimentIdsCountByteCount) {
189         VLOG("Failed to write experiment id count");
190         close(fd);
191         return false;
192     }
193 
194     // Write experimentIds to file
195     for (size_t i = 0; i < experimentIdsCount; i++) {
196         const int64_t experimentId = experimentIds[i];
197         const size_t experimentIdByteCount = sizeof(experimentId);
198         result = write(fd, &experimentId, experimentIdByteCount);
199         if (result == experimentIdByteCount) {
200             VLOG("Successfully wrote experiment IDs");
201         } else {
202             VLOG("Failed to write experiment ids");
203             close(fd);
204             return false;
205         }
206     }
207 
208     result = fchown(fd, AID_STATSD, AID_STATSD);
209     if (result) {
210         VLOG("Failed to chown train info file to statsd");
211         close(fd);
212         return false;
213     }
214 
215     close(fd);
216     return true;
217 }
218 
readTrainInfo(InstallTrainInfo & trainInfo)219 bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) {
220     std::lock_guard<std::mutex> lock(sTrainInfoMutex);
221 
222     int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC);
223     if (fd == -1) {
224         VLOG("Failed to open train-info.bin");
225         return false;
226     }
227 
228     // Read the magic word
229     uint32_t magic;
230     size_t result = read(fd, &magic, sizeof(magic));
231     if (result != sizeof(magic)) {
232         VLOG("Failed to read train info magic");
233         close(fd);
234         return false;
235     }
236 
237     if (magic != TRAIN_INFO_FILE_MAGIC) {
238         VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC);
239         close(fd);
240         return false;
241     }
242 
243     // Read the train version code
244     const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode));
245     result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount);
246     if (result != trainVersionCodeByteCount) {
247         VLOG("Failed to read train version code from train info file");
248         close(fd);
249         return false;
250     }
251 
252     // Read # of bytes taken by trainName in the file.
253     size_t trainNameSize;
254     result = read(fd, &trainNameSize, sizeof(size_t));
255     if (result != sizeof(size_t)) {
256         VLOG("Failed to read train name size from train info file");
257         close(fd);
258         return false;
259     }
260 
261     // Read trainName
262     trainInfo.trainName.resize(trainNameSize);
263     result = read(fd, trainInfo.trainName.data(), trainNameSize);
264     if (result != trainNameSize) {
265         VLOG("Failed to read train name from train info file");
266         close(fd);
267         return false;
268     }
269 
270     // Read status
271     const size_t statusByteCount = sizeof(trainInfo.status);
272     result = read(fd, &trainInfo.status, statusByteCount);
273     if (result != statusByteCount) {
274         VLOG("Failed to read train status from train info file");
275         close(fd);
276         return false;
277     }
278 
279     // Read experiment ids count.
280     size_t experimentIdsCount;
281     result = read(fd, &experimentIdsCount, sizeof(size_t));
282     if (result != sizeof(size_t)) {
283         VLOG("Failed to read train experiment id count from train info file");
284         close(fd);
285         return false;
286     }
287 
288     // Read experimentIds
289     for (size_t i = 0; i < experimentIdsCount; i++) {
290         int64_t experimentId;
291         result = read(fd, &experimentId, sizeof(experimentId));
292         if (result != sizeof(experimentId)) {
293             VLOG("Failed to read train experiment id from train info file");
294             close(fd);
295             return false;
296         }
297         trainInfo.experimentIds.push_back(experimentId);
298     }
299 
300     // Expect to be at EOF.
301     char c;
302     result = read(fd, &c, 1);
303     if (result != 0) {
304         VLOG("Failed to read train info from file. Did not get expected EOF.");
305         close(fd);
306         return false;
307     }
308 
309     VLOG("Read train info file successful");
310     close(fd);
311     return true;
312 }
313 
deleteFile(const char * file)314 void StorageManager::deleteFile(const char* file) {
315     if (remove(file) != 0) {
316         VLOG("Attempt to delete %s but is not found", file);
317     } else {
318         VLOG("Successfully deleted %s", file);
319     }
320 }
321 
deleteAllFiles(const char * path)322 void StorageManager::deleteAllFiles(const char* path) {
323     unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
324     if (dir == NULL) {
325         VLOG("Directory does not exist: %s", path);
326         return;
327     }
328 
329     dirent* de;
330     while ((de = readdir(dir.get()))) {
331         char* name = de->d_name;
332         if (name[0] == '.') continue;
333         deleteFile(StringPrintf("%s/%s", path, name).c_str());
334     }
335 }
336 
deleteSuffixedFiles(const char * path,const char * suffix)337 void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) {
338     unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
339     if (dir == NULL) {
340         VLOG("Directory does not exist: %s", path);
341         return;
342     }
343 
344     dirent* de;
345     while ((de = readdir(dir.get()))) {
346         char* name = de->d_name;
347         if (name[0] == '.') {
348             continue;
349         }
350         size_t nameLen = strlen(name);
351         size_t suffixLen = strlen(suffix);
352         if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) {
353             deleteFile(StringPrintf("%s/%s", path, name).c_str());
354         }
355     }
356 }
357 
sendBroadcast(const char * path,const std::function<void (const ConfigKey &)> & sendBroadcast)358 void StorageManager::sendBroadcast(const char* path,
359                                    const std::function<void(const ConfigKey&)>& sendBroadcast) {
360     unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
361     if (dir == NULL) {
362         VLOG("no stats-data directory on disk");
363         return;
364     }
365 
366     dirent* de;
367     while ((de = readdir(dir.get()))) {
368         char* name = de->d_name;
369         if (name[0] == '.') continue;
370         VLOG("file %s", name);
371 
372         FileName output;
373         parseFileName(name, &output);
374         if (output.mTimestampSec == -1 || output.mIsHistory) continue;
375         sendBroadcast(ConfigKey((int)output.mUid, output.mConfigId));
376     }
377 }
378 
hasConfigMetricsReport(const ConfigKey & key)379 bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) {
380     unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
381     if (dir == NULL) {
382         VLOG("Path %s does not exist", STATS_DATA_DIR);
383         return false;
384     }
385 
386     string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId());
387 
388     dirent* de;
389     while ((de = readdir(dir.get()))) {
390         char* name = de->d_name;
391         if (name[0] == '.') continue;
392 
393         size_t nameLen = strlen(name);
394         size_t suffixLen = suffix.length();
395         if (suffixLen <= nameLen &&
396             strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) {
397             // Check again that the file name is parseable.
398             FileName output;
399             parseFileName(name, &output);
400             if (output.mTimestampSec == -1 || output.mIsHistory) continue;
401             return true;
402         }
403     }
404     return false;
405 }
406 
appendConfigMetricsReport(const ConfigKey & key,ProtoOutputStream * proto,bool erase_data,bool isAdb)407 void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto,
408                                                bool erase_data, bool isAdb) {
409     unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
410     if (dir == NULL) {
411         VLOG("Path %s does not exist", STATS_DATA_DIR);
412         return;
413     }
414 
415     dirent* de;
416     while ((de = readdir(dir.get()))) {
417         char* name = de->d_name;
418         string fileName(name);
419         if (name[0] == '.') continue;
420         FileName output;
421         parseFileName(name, &output);
422 
423         if (output.mTimestampSec == -1 || (output.mIsHistory && !isAdb) ||
424             output.mUid != key.GetUid() || output.mConfigId != key.GetId()) {
425             continue;
426         }
427 
428         auto fullPathName = StringPrintf("%s/%s", STATS_DATA_DIR, fileName.c_str());
429         int fd = open(fullPathName.c_str(), O_RDONLY | O_CLOEXEC);
430         if (fd != -1) {
431             string content;
432             if (android::base::ReadFdToString(fd, &content)) {
433                 proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS,
434                              content.c_str(), content.size());
435             }
436             close(fd);
437         } else {
438             ALOGE("file cannot be opened");
439         }
440 
441         if (erase_data) {
442             remove(fullPathName.c_str());
443         } else if (!output.mIsHistory && !isAdb) {
444             // This means a real data owner has called to get this data. But the config says it
445             // wants to keep a local history. So now this file must be renamed as a history file.
446             // So that next time, when owner calls getData() again, this data won't be uploaded
447             // again. rename returns 0 on success
448             if (rename(fullPathName.c_str(), (fullPathName + "_history").c_str())) {
449                 ALOGE("Failed to rename file %s", fullPathName.c_str());
450             }
451         }
452     }
453 }
454 
readFileToString(const char * file,string * content)455 bool StorageManager::readFileToString(const char* file, string* content) {
456     int fd = open(file, O_RDONLY | O_CLOEXEC);
457     bool res = false;
458     if (fd != -1) {
459         if (android::base::ReadFdToString(fd, content)) {
460             res = true;
461         } else {
462             VLOG("Failed to read file %s\n", file);
463         }
464         close(fd);
465     }
466     return res;
467 }
468 
readConfigFromDisk(map<ConfigKey,StatsdConfig> & configsMap)469 void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap) {
470     unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
471     if (dir == NULL) {
472         VLOG("no default config on disk");
473         return;
474     }
475     trimToFit(STATS_SERVICE_DIR);
476 
477     dirent* de;
478     while ((de = readdir(dir.get()))) {
479         char* name = de->d_name;
480         if (name[0] == '.') continue;
481 
482         FileName output;
483         parseFileName(name, &output);
484         if (output.mTimestampSec == -1) continue;
485         string file_name = output.getFullFileName(STATS_SERVICE_DIR);
486         int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
487         if (fd != -1) {
488             string content;
489             if (android::base::ReadFdToString(fd, &content)) {
490                 StatsdConfig config;
491                 if (config.ParseFromString(content)) {
492                     configsMap[ConfigKey(output.mUid, output.mConfigId)] = config;
493                     VLOG("map key uid=%lld|configID=%lld", (long long)output.mUid,
494                          (long long)output.mConfigId);
495                 }
496             }
497             close(fd);
498         }
499     }
500 }
501 
readConfigFromDisk(const ConfigKey & key,StatsdConfig * config)502 bool StorageManager::readConfigFromDisk(const ConfigKey& key, StatsdConfig* config) {
503     string content;
504     return config != nullptr &&
505         StorageManager::readConfigFromDisk(key, &content) && config->ParseFromString(content);
506 }
507 
readConfigFromDisk(const ConfigKey & key,string * content)508 bool StorageManager::readConfigFromDisk(const ConfigKey& key, string* content) {
509     unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR),
510                                              closedir);
511     if (dir == NULL) {
512         VLOG("Directory does not exist: %s", STATS_SERVICE_DIR);
513         return false;
514     }
515 
516     string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId());
517     dirent* de;
518     while ((de = readdir(dir.get()))) {
519         char* name = de->d_name;
520         if (name[0] == '.') {
521             continue;
522         }
523         size_t nameLen = strlen(name);
524         size_t suffixLen = suffix.length();
525         // There can be at most one file that matches this suffix (config key).
526         if (suffixLen <= nameLen &&
527             strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) {
528             int fd = open(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str(),
529                                   O_RDONLY | O_CLOEXEC);
530             if (fd != -1) {
531                 if (android::base::ReadFdToString(fd, content)) {
532                     return true;
533                 }
534                 close(fd);
535             }
536         }
537     }
538     return false;
539 }
540 
hasIdenticalConfig(const ConfigKey & key,const vector<uint8_t> & config)541 bool StorageManager::hasIdenticalConfig(const ConfigKey& key,
542                                         const vector<uint8_t>& config) {
543     string content;
544     if (StorageManager::readConfigFromDisk(key, &content)) {
545         vector<uint8_t> vec(content.begin(), content.end());
546         if (vec == config) {
547             return true;
548         }
549     }
550     return false;
551 }
552 
sortFiles(vector<FileInfo> * fileNames)553 void StorageManager::sortFiles(vector<FileInfo>* fileNames) {
554     // Reverse sort to effectively remove from the back (oldest entries).
555     // This will sort files in reverse-chronological order. Local history files have lower
556     // priority than regular data files.
557     sort(fileNames->begin(), fileNames->end(), [](FileInfo& lhs, FileInfo& rhs) {
558         // first consider if the file is a local history
559         if (lhs.mIsHistory && !rhs.mIsHistory) {
560             return false;
561         } else if (rhs.mIsHistory && !lhs.mIsHistory) {
562             return true;
563         }
564 
565         // then consider the age.
566         if (lhs.mFileAgeSec < rhs.mFileAgeSec) {
567             return true;
568         } else if (lhs.mFileAgeSec > rhs.mFileAgeSec) {
569             return false;
570         }
571 
572         // then good luck.... use string::compare
573         return lhs.mFileName.compare(rhs.mFileName) > 0;
574     });
575 }
576 
trimToFit(const char * path)577 void StorageManager::trimToFit(const char* path) {
578     unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
579     if (dir == NULL) {
580         VLOG("Path %s does not exist", path);
581         return;
582     }
583     dirent* de;
584     int totalFileSize = 0;
585     vector<FileInfo> fileNames;
586     auto nowSec = getWallClockSec();
587     while ((de = readdir(dir.get()))) {
588         char* name = de->d_name;
589         if (name[0] == '.') continue;
590 
591         FileName output;
592         parseFileName(name, &output);
593         if (output.mTimestampSec == -1) continue;
594         string file_name = output.getFullFileName(path);
595 
596         // Check for timestamp and delete if it's too old.
597         long fileAge = nowSec - output.mTimestampSec;
598         if (fileAge > StatsdStats::kMaxAgeSecond ||
599             (output.mIsHistory && fileAge > StatsdStats::kMaxLocalHistoryAgeSecond)) {
600             deleteFile(file_name.c_str());
601             continue;
602         }
603 
604         ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
605         int fileSize = 0;
606         if (file.is_open()) {
607             file.seekg(0, ios::end);
608             fileSize = file.tellg();
609             file.close();
610             totalFileSize += fileSize;
611         }
612         fileNames.emplace_back(file_name, output.mIsHistory, fileSize, fileAge);
613     }
614 
615     if (fileNames.size() > StatsdStats::kMaxFileNumber ||
616         totalFileSize > StatsdStats::kMaxFileSize) {
617         sortFiles(&fileNames);
618     }
619 
620     // Start removing files from oldest to be under the limit.
621     while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber ||
622                                     totalFileSize > StatsdStats::kMaxFileSize)) {
623         totalFileSize -= fileNames.at(fileNames.size() - 1).mFileSizeBytes;
624         deleteFile(fileNames.at(fileNames.size() - 1).mFileName.c_str());
625         fileNames.pop_back();
626     }
627 }
628 
printStats(int outFd)629 void StorageManager::printStats(int outFd) {
630     printDirStats(outFd, STATS_SERVICE_DIR);
631     printDirStats(outFd, STATS_DATA_DIR);
632 }
633 
printDirStats(int outFd,const char * path)634 void StorageManager::printDirStats(int outFd, const char* path) {
635     dprintf(outFd, "Printing stats of %s\n", path);
636     unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
637     if (dir == NULL) {
638         VLOG("Path %s does not exist", path);
639         return;
640     }
641     dirent* de;
642     int fileCount = 0;
643     int totalFileSize = 0;
644     while ((de = readdir(dir.get()))) {
645         char* name = de->d_name;
646         if (name[0] == '.') {
647             continue;
648         }
649         FileName output;
650         parseFileName(name, &output);
651         if (output.mTimestampSec == -1) continue;
652         dprintf(outFd, "\t #%d, Last updated: %lld, UID: %d, Config ID: %lld, %s", fileCount + 1,
653                 (long long)output.mTimestampSec, output.mUid, (long long)output.mConfigId,
654                 (output.mIsHistory ? "local history" : ""));
655         string file_name = output.getFullFileName(path);
656         ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
657         if (file.is_open()) {
658             file.seekg(0, ios::end);
659             int fileSize = file.tellg();
660             file.close();
661             dprintf(outFd, ", File Size: %d bytes", fileSize);
662             totalFileSize += fileSize;
663         }
664         dprintf(outFd, "\n");
665         fileCount++;
666     }
667     dprintf(outFd, "\tTotal number of files: %d, Total size of files: %d bytes.\n", fileCount,
668             totalFileSize);
669 }
670 
671 }  // namespace statsd
672 }  // namespace os
673 }  // namespace android
674