/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define DEBUG false // STOPSHIP if true #include "Log.h" #include "android-base/stringprintf.h" #include "guardrail/StatsdStats.h" #include "storage/StorageManager.h" #include "stats_log_util.h" #include #include #include #include #include namespace android { namespace os { namespace statsd { using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_MESSAGE; using std::map; /** * NOTE: these directories are protected by SELinux, any changes here must also update * the SELinux policies. */ #define STATS_DATA_DIR "/data/misc/stats-data" #define STATS_SERVICE_DIR "/data/misc/stats-service" #define TRAIN_INFO_DIR "/data/misc/train-info" #define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" // Magic word at the start of the train info file, change this if changing the file format const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff; // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; std::mutex StorageManager::sTrainInfoMutex; using android::base::StringPrintf; using std::unique_ptr; struct FileName { int64_t mTimestampSec; int mUid; int64_t mConfigId; bool mIsHistory; string getFullFileName(const char* path) { return StringPrintf("%s/%lld_%d_%lld%s", path, (long long)mTimestampSec, (int)mUid, (long long)mConfigId, (mIsHistory ? "_history" : "")); }; }; string StorageManager::getDataFileName(long wallClockSec, int uid, int64_t id) { return StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, wallClockSec, uid, (long long)id); } string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_t id) { return StringPrintf("%s/%ld_%d_%lld_history", STATS_DATA_DIR, wallClockSec, uid, (long long)id); } // Returns array of int64_t which contains timestamp in seconds, uid, // configID and whether the file is a local history file. static void parseFileName(char* name, FileName* output) { int64_t result[3]; int index = 0; char* substr = strtok(name, "_"); while (substr != nullptr && index < 3) { result[index] = StrToInt64(substr); index++; substr = strtok(nullptr, "_"); } // When index ends before hitting 3, file name is corrupted. We // intentionally put -1 at index 0 to indicate the error to caller. // TODO(b/110563137): consider removing files with unexpected name format. if (index < 3) { result[0] = -1; } output->mTimestampSec = result[0]; output->mUid = result[1]; output->mConfigId = result[2]; // check if the file is a local history. output->mIsHistory = (substr != nullptr && strcmp("history", substr) == 0); } void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) { int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { VLOG("Attempt to access %s but failed", file); return; } trimToFit(STATS_SERVICE_DIR); trimToFit(STATS_DATA_DIR); if (android::base::WriteFully(fd, buffer, numBytes)) { VLOG("Successfully wrote %s", file); } else { ALOGE("Failed to write %s", file); } int result = fchown(fd, AID_STATSD, AID_STATSD); if (result) { VLOG("Failed to chown %s to statsd", file); } close(fd); } bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, int32_t status, const std::vector& experimentIds) { std::lock_guard lock(sTrainInfoMutex); deleteAllFiles(TRAIN_INFO_DIR); int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH); return false; } size_t result; // Write the magic word result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { VLOG("Failed to wrtie train info magic"); close(fd); return false; } // Write the train version const size_t trainVersionCodeByteCount = sizeof(trainVersionCode); result = write(fd, &trainVersionCode, trainVersionCodeByteCount); if (result != trainVersionCodeByteCount) { VLOG("Failed to wrtie train version code"); close(fd); return false; } // Write # of bytes in trainName to file const size_t trainNameSize = trainName.size(); const size_t trainNameSizeByteCount = sizeof(trainNameSize); result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); if (result != trainNameSizeByteCount) { VLOG("Failed to write train name size"); close(fd); return false; } // Write trainName to file result = write(fd, trainName.c_str(), trainNameSize); if (result != trainNameSize) { VLOG("Failed to write train name"); close(fd); return false; } // Write status to file const size_t statusByteCount = sizeof(status); result = write(fd, (uint8_t*)&status, statusByteCount); if (result != statusByteCount) { VLOG("Failed to write status"); close(fd); return false; } // Write experiment id count to file. const size_t experimentIdsCount = experimentIds.size(); const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); if (result != experimentIdsCountByteCount) { VLOG("Failed to write experiment id count"); close(fd); return false; } // Write experimentIds to file for (size_t i = 0; i < experimentIdsCount; i++) { const int64_t experimentId = experimentIds[i]; const size_t experimentIdByteCount = sizeof(experimentId); result = write(fd, &experimentId, experimentIdByteCount); if (result == experimentIdByteCount) { VLOG("Successfully wrote experiment IDs"); } else { VLOG("Failed to write experiment ids"); close(fd); return false; } } result = fchown(fd, AID_STATSD, AID_STATSD); if (result) { VLOG("Failed to chown train info file to statsd"); close(fd); return false; } close(fd); return true; } bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { std::lock_guard lock(sTrainInfoMutex); int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC); if (fd == -1) { VLOG("Failed to open train-info.bin"); return false; } // Read the magic word uint32_t magic; size_t result = read(fd, &magic, sizeof(magic)); if (result != sizeof(magic)) { VLOG("Failed to read train info magic"); close(fd); return false; } if (magic != TRAIN_INFO_FILE_MAGIC) { VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC); close(fd); return false; } // Read the train version code const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode)); result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); if (result != trainVersionCodeByteCount) { VLOG("Failed to read train version code from train info file"); close(fd); return false; } // Read # of bytes taken by trainName in the file. size_t trainNameSize; result = read(fd, &trainNameSize, sizeof(size_t)); if (result != sizeof(size_t)) { VLOG("Failed to read train name size from train info file"); close(fd); return false; } // Read trainName trainInfo.trainName.resize(trainNameSize); result = read(fd, trainInfo.trainName.data(), trainNameSize); if (result != trainNameSize) { VLOG("Failed to read train name from train info file"); close(fd); return false; } // Read status const size_t statusByteCount = sizeof(trainInfo.status); result = read(fd, &trainInfo.status, statusByteCount); if (result != statusByteCount) { VLOG("Failed to read train status from train info file"); close(fd); return false; } // Read experiment ids count. size_t experimentIdsCount; result = read(fd, &experimentIdsCount, sizeof(size_t)); if (result != sizeof(size_t)) { VLOG("Failed to read train experiment id count from train info file"); close(fd); return false; } // Read experimentIds for (size_t i = 0; i < experimentIdsCount; i++) { int64_t experimentId; result = read(fd, &experimentId, sizeof(experimentId)); if (result != sizeof(experimentId)) { VLOG("Failed to read train experiment id from train info file"); close(fd); return false; } trainInfo.experimentIds.push_back(experimentId); } // Expect to be at EOF. char c; result = read(fd, &c, 1); if (result != 0) { VLOG("Failed to read train info from file. Did not get expected EOF."); close(fd); return false; } VLOG("Read train info file successful"); close(fd); return true; } void StorageManager::deleteFile(const char* file) { if (remove(file) != 0) { VLOG("Attempt to delete %s but is not found", file); } else { VLOG("Successfully deleted %s", file); } } void StorageManager::deleteAllFiles(const char* path) { unique_ptr dir(opendir(path), closedir); if (dir == NULL) { VLOG("Directory does not exist: %s", path); return; } dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') continue; deleteFile(StringPrintf("%s/%s", path, name).c_str()); } } void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) { unique_ptr dir(opendir(path), closedir); if (dir == NULL) { VLOG("Directory does not exist: %s", path); return; } dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') { continue; } size_t nameLen = strlen(name); size_t suffixLen = strlen(suffix); if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) { deleteFile(StringPrintf("%s/%s", path, name).c_str()); } } } void StorageManager::sendBroadcast(const char* path, const std::function& sendBroadcast) { unique_ptr dir(opendir(path), closedir); if (dir == NULL) { VLOG("no stats-data directory on disk"); return; } dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') continue; VLOG("file %s", name); FileName output; parseFileName(name, &output); if (output.mTimestampSec == -1 || output.mIsHistory) continue; sendBroadcast(ConfigKey((int)output.mUid, output.mConfigId)); } } bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) { unique_ptr dir(opendir(STATS_DATA_DIR), closedir); if (dir == NULL) { VLOG("Path %s does not exist", STATS_DATA_DIR); return false; } string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') continue; size_t nameLen = strlen(name); size_t suffixLen = suffix.length(); if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { // Check again that the file name is parseable. FileName output; parseFileName(name, &output); if (output.mTimestampSec == -1 || output.mIsHistory) continue; return true; } } return false; } void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto, bool erase_data, bool isAdb) { unique_ptr dir(opendir(STATS_DATA_DIR), closedir); if (dir == NULL) { VLOG("Path %s does not exist", STATS_DATA_DIR); return; } dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; string fileName(name); if (name[0] == '.') continue; FileName output; parseFileName(name, &output); if (output.mTimestampSec == -1 || (output.mIsHistory && !isAdb) || output.mUid != key.GetUid() || output.mConfigId != key.GetId()) { continue; } auto fullPathName = StringPrintf("%s/%s", STATS_DATA_DIR, fileName.c_str()); int fd = open(fullPathName.c_str(), O_RDONLY | O_CLOEXEC); if (fd != -1) { string content; if (android::base::ReadFdToString(fd, &content)) { proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS, content.c_str(), content.size()); } close(fd); } else { ALOGE("file cannot be opened"); } if (erase_data) { remove(fullPathName.c_str()); } else if (!output.mIsHistory && !isAdb) { // This means a real data owner has called to get this data. But the config says it // wants to keep a local history. So now this file must be renamed as a history file. // So that next time, when owner calls getData() again, this data won't be uploaded // again. rename returns 0 on success if (rename(fullPathName.c_str(), (fullPathName + "_history").c_str())) { ALOGE("Failed to rename file %s", fullPathName.c_str()); } } } } bool StorageManager::readFileToString(const char* file, string* content) { int fd = open(file, O_RDONLY | O_CLOEXEC); bool res = false; if (fd != -1) { if (android::base::ReadFdToString(fd, content)) { res = true; } else { VLOG("Failed to read file %s\n", file); } close(fd); } return res; } void StorageManager::readConfigFromDisk(map& configsMap) { unique_ptr dir(opendir(STATS_SERVICE_DIR), closedir); if (dir == NULL) { VLOG("no default config on disk"); return; } trimToFit(STATS_SERVICE_DIR); dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') continue; FileName output; parseFileName(name, &output); if (output.mTimestampSec == -1) continue; string file_name = output.getFullFileName(STATS_SERVICE_DIR); int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); if (fd != -1) { string content; if (android::base::ReadFdToString(fd, &content)) { StatsdConfig config; if (config.ParseFromString(content)) { configsMap[ConfigKey(output.mUid, output.mConfigId)] = config; VLOG("map key uid=%lld|configID=%lld", (long long)output.mUid, (long long)output.mConfigId); } } close(fd); } } } bool StorageManager::readConfigFromDisk(const ConfigKey& key, StatsdConfig* config) { string content; return config != nullptr && StorageManager::readConfigFromDisk(key, &content) && config->ParseFromString(content); } bool StorageManager::readConfigFromDisk(const ConfigKey& key, string* content) { unique_ptr dir(opendir(STATS_SERVICE_DIR), closedir); if (dir == NULL) { VLOG("Directory does not exist: %s", STATS_SERVICE_DIR); return false; } string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); dirent* de; while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') { continue; } size_t nameLen = strlen(name); size_t suffixLen = suffix.length(); // There can be at most one file that matches this suffix (config key). if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { int fd = open(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str(), O_RDONLY | O_CLOEXEC); if (fd != -1) { if (android::base::ReadFdToString(fd, content)) { return true; } close(fd); } } } return false; } bool StorageManager::hasIdenticalConfig(const ConfigKey& key, const vector& config) { string content; if (StorageManager::readConfigFromDisk(key, &content)) { vector vec(content.begin(), content.end()); if (vec == config) { return true; } } return false; } void StorageManager::sortFiles(vector* fileNames) { // Reverse sort to effectively remove from the back (oldest entries). // This will sort files in reverse-chronological order. Local history files have lower // priority than regular data files. sort(fileNames->begin(), fileNames->end(), [](FileInfo& lhs, FileInfo& rhs) { // first consider if the file is a local history if (lhs.mIsHistory && !rhs.mIsHistory) { return false; } else if (rhs.mIsHistory && !lhs.mIsHistory) { return true; } // then consider the age. if (lhs.mFileAgeSec < rhs.mFileAgeSec) { return true; } else if (lhs.mFileAgeSec > rhs.mFileAgeSec) { return false; } // then good luck.... use string::compare return lhs.mFileName.compare(rhs.mFileName) > 0; }); } void StorageManager::trimToFit(const char* path) { unique_ptr dir(opendir(path), closedir); if (dir == NULL) { VLOG("Path %s does not exist", path); return; } dirent* de; int totalFileSize = 0; vector fileNames; auto nowSec = getWallClockSec(); while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') continue; FileName output; parseFileName(name, &output); if (output.mTimestampSec == -1) continue; string file_name = output.getFullFileName(path); // Check for timestamp and delete if it's too old. long fileAge = nowSec - output.mTimestampSec; if (fileAge > StatsdStats::kMaxAgeSecond || (output.mIsHistory && fileAge > StatsdStats::kMaxLocalHistoryAgeSecond)) { deleteFile(file_name.c_str()); continue; } ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); int fileSize = 0; if (file.is_open()) { file.seekg(0, ios::end); fileSize = file.tellg(); file.close(); totalFileSize += fileSize; } fileNames.emplace_back(file_name, output.mIsHistory, fileSize, fileAge); } if (fileNames.size() > StatsdStats::kMaxFileNumber || totalFileSize > StatsdStats::kMaxFileSize) { sortFiles(&fileNames); } // Start removing files from oldest to be under the limit. while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber || totalFileSize > StatsdStats::kMaxFileSize)) { totalFileSize -= fileNames.at(fileNames.size() - 1).mFileSizeBytes; deleteFile(fileNames.at(fileNames.size() - 1).mFileName.c_str()); fileNames.pop_back(); } } void StorageManager::printStats(int outFd) { printDirStats(outFd, STATS_SERVICE_DIR); printDirStats(outFd, STATS_DATA_DIR); } void StorageManager::printDirStats(int outFd, const char* path) { dprintf(outFd, "Printing stats of %s\n", path); unique_ptr dir(opendir(path), closedir); if (dir == NULL) { VLOG("Path %s does not exist", path); return; } dirent* de; int fileCount = 0; int totalFileSize = 0; while ((de = readdir(dir.get()))) { char* name = de->d_name; if (name[0] == '.') { continue; } FileName output; parseFileName(name, &output); if (output.mTimestampSec == -1) continue; dprintf(outFd, "\t #%d, Last updated: %lld, UID: %d, Config ID: %lld, %s", fileCount + 1, (long long)output.mTimestampSec, output.mUid, (long long)output.mConfigId, (output.mIsHistory ? "local history" : "")); string file_name = output.getFullFileName(path); ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); if (file.is_open()) { file.seekg(0, ios::end); int fileSize = file.tellg(); file.close(); dprintf(outFd, ", File Size: %d bytes", fileSize); totalFileSize += fileSize; } dprintf(outFd, "\n"); fileCount++; } dprintf(outFd, "\tTotal number of files: %d, Total size of files: %d bytes.\n", fileCount, totalFileSize); } } // namespace statsd } // namespace os } // namespace android