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