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; 64 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 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 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. 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 631 void StorageManager::printStats(int outFd) { 632 printDirStats(outFd, STATS_SERVICE_DIR); 633 printDirStats(outFd, STATS_DATA_DIR); 634 } 635 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