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.
parseFileName(char * name,int64_t * result)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
getFilePath(const char * path,int64_t timestamp,int64_t uid,int64_t configID)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
writeFile(const char * file,const void * buffer,int numBytes)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
deleteFile(const char * file)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
deleteAllFiles(const char * path)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
deleteSuffixedFiles(const char * path,const char * suffix)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
sendBroadcast(const char * path,const std::function<void (const ConfigKey &)> & sendBroadcast)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
hasConfigMetricsReport(const ConfigKey & key)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
appendConfigMetricsReport(const ConfigKey & key,ProtoOutputStream * proto)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
readFileToString(const char * file,string * content)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
readConfigFromDisk(map<ConfigKey,StatsdConfig> & configsMap)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
readConfigFromDisk(const ConfigKey & key,StatsdConfig * config)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
readConfigFromDisk(const ConfigKey & key,string * content)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
hasIdenticalConfig(const ConfigKey & key,const vector<uint8_t> & config)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
trimToFit(const char * path)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
printStats(FILE * out)395 void StorageManager::printStats(FILE* out) {
396 printDirStats(out, STATS_SERVICE_DIR);
397 printDirStats(out, STATS_DATA_DIR);
398 }
399
printDirStats(FILE * out,const char * path)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