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