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