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 #define STATS_DATA_DIR "/data/misc/stats-data" 40 #define STATS_SERVICE_DIR "/data/misc/stats-service" 41 42 // for ConfigMetricsReportList 43 const int FIELD_ID_REPORTS = 2; 44 45 using android::base::StringPrintf; 46 using std::unique_ptr; 47 48 // Returns array of int64_t which contains timestamp in seconds, uid, and 49 // configID. 50 static void parseFileName(char* name, int64_t* result) { 51 int index = 0; 52 char* substr = strtok(name, "_"); 53 while (substr != nullptr && index < 3) { 54 result[index] = StrToInt64(substr); 55 index++; 56 substr = strtok(nullptr, "_"); 57 } 58 // When index ends before hitting 3, file name is corrupted. We 59 // intentionally put -1 at index 0 to indicate the error to caller. 60 // TODO: consider removing files with unexpected name format. 61 if (index < 3) { 62 result[0] = -1; 63 } 64 } 65 66 static string getFilePath(const char* path, int64_t timestamp, int64_t uid, int64_t configID) { 67 return StringPrintf("%s/%lld_%d_%lld", path, (long long)timestamp, (int)uid, 68 (long long)configID); 69 } 70 71 void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) { 72 int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); 73 if (fd == -1) { 74 VLOG("Attempt to access %s but failed", file); 75 return; 76 } 77 trimToFit(STATS_SERVICE_DIR); 78 trimToFit(STATS_DATA_DIR); 79 80 int result = write(fd, buffer, numBytes); 81 if (result == numBytes) { 82 VLOG("Successfully wrote %s", file); 83 } else { 84 VLOG("Failed to write %s", file); 85 } 86 87 result = fchown(fd, AID_STATSD, AID_STATSD); 88 if (result) { 89 VLOG("Failed to chown %s to statsd", file); 90 } 91 92 close(fd); 93 } 94 95 void StorageManager::deleteFile(const char* file) { 96 if (remove(file) != 0) { 97 VLOG("Attempt to delete %s but is not found", file); 98 } else { 99 VLOG("Successfully deleted %s", file); 100 } 101 } 102 103 void StorageManager::deleteAllFiles(const char* path) { 104 unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); 105 if (dir == NULL) { 106 VLOG("Directory does not exist: %s", path); 107 return; 108 } 109 110 dirent* de; 111 while ((de = readdir(dir.get()))) { 112 char* name = de->d_name; 113 if (name[0] == '.') continue; 114 deleteFile(StringPrintf("%s/%s", path, name).c_str()); 115 } 116 } 117 118 void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) { 119 unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); 120 if (dir == NULL) { 121 VLOG("Directory does not exist: %s", path); 122 return; 123 } 124 125 dirent* de; 126 while ((de = readdir(dir.get()))) { 127 char* name = de->d_name; 128 if (name[0] == '.') { 129 continue; 130 } 131 size_t nameLen = strlen(name); 132 size_t suffixLen = strlen(suffix); 133 if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) { 134 deleteFile(StringPrintf("%s/%s", path, name).c_str()); 135 } 136 } 137 } 138 139 void StorageManager::sendBroadcast(const char* path, 140 const std::function<void(const ConfigKey&)>& sendBroadcast) { 141 unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); 142 if (dir == NULL) { 143 VLOG("no stats-data directory on disk"); 144 return; 145 } 146 147 dirent* de; 148 while ((de = readdir(dir.get()))) { 149 char* name = de->d_name; 150 if (name[0] == '.') continue; 151 VLOG("file %s", name); 152 153 int64_t result[3]; 154 parseFileName(name, result); 155 if (result[0] == -1) continue; 156 int64_t uid = result[1]; 157 int64_t configID = result[2]; 158 159 sendBroadcast(ConfigKey((int)uid, configID)); 160 } 161 } 162 163 bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) { 164 unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir); 165 if (dir == NULL) { 166 VLOG("Path %s does not exist", STATS_DATA_DIR); 167 return false; 168 } 169 170 string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); 171 172 dirent* de; 173 while ((de = readdir(dir.get()))) { 174 char* name = de->d_name; 175 if (name[0] == '.') continue; 176 177 size_t nameLen = strlen(name); 178 size_t suffixLen = suffix.length(); 179 if (suffixLen <= nameLen && 180 strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { 181 // Check again that the file name is parseable. 182 int64_t result[3]; 183 parseFileName(name, result); 184 if (result[0] == -1) continue; 185 return true; 186 } 187 } 188 return false; 189 } 190 191 void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto) { 192 unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir); 193 if (dir == NULL) { 194 VLOG("Path %s does not exist", STATS_DATA_DIR); 195 return; 196 } 197 198 string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); 199 200 dirent* de; 201 while ((de = readdir(dir.get()))) { 202 char* name = de->d_name; 203 if (name[0] == '.') continue; 204 205 size_t nameLen = strlen(name); 206 size_t suffixLen = suffix.length(); 207 if (suffixLen <= nameLen && 208 strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { 209 int64_t result[3]; 210 parseFileName(name, result); 211 if (result[0] == -1) continue; 212 int64_t timestamp = result[0]; 213 int64_t uid = result[1]; 214 int64_t configID = result[2]; 215 216 string file_name = getFilePath(STATS_DATA_DIR, timestamp, uid, configID); 217 int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); 218 if (fd != -1) { 219 string content; 220 if (android::base::ReadFdToString(fd, &content)) { 221 proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS, 222 content.c_str(), content.size()); 223 } 224 close(fd); 225 } 226 227 // Remove file from disk after reading. 228 remove(file_name.c_str()); 229 } 230 } 231 } 232 233 bool StorageManager::readFileToString(const char* file, string* content) { 234 int fd = open(file, O_RDONLY | O_CLOEXEC); 235 bool res = false; 236 if (fd != -1) { 237 if (android::base::ReadFdToString(fd, content)) { 238 res = true; 239 } else { 240 VLOG("Failed to read file %s\n", file); 241 } 242 close(fd); 243 } 244 return res; 245 } 246 247 void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap) { 248 unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir); 249 if (dir == NULL) { 250 VLOG("no default config on disk"); 251 return; 252 } 253 trimToFit(STATS_SERVICE_DIR); 254 255 dirent* de; 256 while ((de = readdir(dir.get()))) { 257 char* name = de->d_name; 258 if (name[0] == '.') continue; 259 VLOG("file %s", name); 260 261 int64_t result[3]; 262 parseFileName(name, result); 263 if (result[0] == -1) continue; 264 int64_t timestamp = result[0]; 265 int64_t uid = result[1]; 266 int64_t configID = result[2]; 267 string file_name = getFilePath(STATS_SERVICE_DIR, timestamp, uid, configID); 268 int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC); 269 if (fd != -1) { 270 string content; 271 if (android::base::ReadFdToString(fd, &content)) { 272 StatsdConfig config; 273 if (config.ParseFromString(content)) { 274 configsMap[ConfigKey(uid, configID)] = config; 275 VLOG("map key uid=%lld|configID=%lld", (long long)uid, (long long)configID); 276 } 277 } 278 close(fd); 279 } 280 } 281 } 282 283 bool StorageManager::readConfigFromDisk(const ConfigKey& key, StatsdConfig* config) { 284 string content; 285 return config != nullptr && 286 StorageManager::readConfigFromDisk(key, &content) && config->ParseFromString(content); 287 } 288 289 bool StorageManager::readConfigFromDisk(const ConfigKey& key, string* content) { 290 unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), 291 closedir); 292 if (dir == NULL) { 293 VLOG("Directory does not exist: %s", STATS_SERVICE_DIR); 294 return false; 295 } 296 297 string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); 298 dirent* de; 299 while ((de = readdir(dir.get()))) { 300 char* name = de->d_name; 301 if (name[0] == '.') { 302 continue; 303 } 304 size_t nameLen = strlen(name); 305 size_t suffixLen = suffix.length(); 306 // There can be at most one file that matches this suffix (config key). 307 if (suffixLen <= nameLen && 308 strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { 309 int fd = open(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str(), 310 O_RDONLY | O_CLOEXEC); 311 if (fd != -1) { 312 if (android::base::ReadFdToString(fd, content)) { 313 return true; 314 } 315 close(fd); 316 } 317 } 318 } 319 return false; 320 } 321 322 bool StorageManager::hasIdenticalConfig(const ConfigKey& key, 323 const vector<uint8_t>& config) { 324 string content; 325 if (StorageManager::readConfigFromDisk(key, &content)) { 326 vector<uint8_t> vec(content.begin(), content.end()); 327 if (vec == config) { 328 return true; 329 } 330 } 331 return false; 332 } 333 334 void StorageManager::trimToFit(const char* path) { 335 unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); 336 if (dir == NULL) { 337 VLOG("Path %s does not exist", path); 338 return; 339 } 340 dirent* de; 341 int totalFileSize = 0; 342 vector<string> fileNames; 343 while ((de = readdir(dir.get()))) { 344 char* name = de->d_name; 345 if (name[0] == '.') continue; 346 347 int64_t result[3]; 348 parseFileName(name, result); 349 if (result[0] == -1) continue; 350 int64_t timestamp = result[0]; 351 int64_t uid = result[1]; 352 int64_t configID = result[2]; 353 string file_name = getFilePath(path, timestamp, uid, configID); 354 355 // Check for timestamp and delete if it's too old. 356 long fileAge = getWallClockSec() - timestamp; 357 if (fileAge > StatsdStats::kMaxAgeSecond) { 358 deleteFile(file_name.c_str()); 359 } 360 361 fileNames.push_back(file_name); 362 ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); 363 if (file.is_open()) { 364 file.seekg(0, ios::end); 365 int fileSize = file.tellg(); 366 file.close(); 367 totalFileSize += fileSize; 368 } 369 } 370 371 if (fileNames.size() > StatsdStats::kMaxFileNumber || 372 totalFileSize > StatsdStats::kMaxFileSize) { 373 // Reverse sort to effectively remove from the back (oldest entries). 374 // This will sort files in reverse-chronological order. 375 sort(fileNames.begin(), fileNames.end(), std::greater<std::string>()); 376 } 377 378 // Start removing files from oldest to be under the limit. 379 while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber || 380 totalFileSize > StatsdStats::kMaxFileSize)) { 381 string file_name = fileNames.at(fileNames.size() - 1); 382 ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); 383 if (file.is_open()) { 384 file.seekg(0, ios::end); 385 int fileSize = file.tellg(); 386 file.close(); 387 totalFileSize -= fileSize; 388 } 389 390 deleteFile(file_name.c_str()); 391 fileNames.pop_back(); 392 } 393 } 394 395 void StorageManager::printStats(FILE* out) { 396 printDirStats(out, STATS_SERVICE_DIR); 397 printDirStats(out, STATS_DATA_DIR); 398 } 399 400 void StorageManager::printDirStats(FILE* out, const char* path) { 401 fprintf(out, "Printing stats of %s\n", path); 402 unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); 403 if (dir == NULL) { 404 VLOG("Path %s does not exist", path); 405 return; 406 } 407 dirent* de; 408 int fileCount = 0; 409 int totalFileSize = 0; 410 while ((de = readdir(dir.get()))) { 411 char* name = de->d_name; 412 if (name[0] == '.') { 413 continue; 414 } 415 int64_t result[3]; 416 parseFileName(name, result); 417 if (result[0] == -1) continue; 418 int64_t timestamp = result[0]; 419 int64_t uid = result[1]; 420 int64_t configID = result[2]; 421 fprintf(out, "\t #%d, Last updated: %lld, UID: %d, Config ID: %lld", 422 fileCount + 1, 423 (long long)timestamp, 424 (int)uid, 425 (long long)configID); 426 string file_name = getFilePath(path, timestamp, uid, configID); 427 ifstream file(file_name.c_str(), ifstream::in | ifstream::binary); 428 if (file.is_open()) { 429 file.seekg(0, ios::end); 430 int fileSize = file.tellg(); 431 file.close(); 432 fprintf(out, ", File Size: %d bytes", fileSize); 433 totalFileSize += fileSize; 434 } 435 fprintf(out, "\n"); 436 fileCount++; 437 } 438 fprintf(out, "\tTotal number of files: %d, Total size of files: %d bytes.\n", 439 fileCount, totalFileSize); 440 } 441 442 } // namespace statsd 443 } // namespace os 444 } // namespace android 445