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
31namespace android {
32namespace os {
33namespace statsd {
34
35using android::util::FIELD_COUNT_REPEATED;
36using android::util::FIELD_TYPE_MESSAGE;
37using 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
43const int FIELD_ID_REPORTS = 2;
44
45using android::base::StringPrintf;
46using std::unique_ptr;
47
48// Returns array of int64_t which contains timestamp in seconds, uid, and
49// configID.
50static 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
66static 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
71void 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
95void 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
103void 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
118void 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
139void 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
163bool 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
191void 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
233bool 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
247void 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
283bool StorageManager::readConfigFromDisk(const ConfigKey& key, StatsdConfig* config) {
284    string content;
285    return config != nullptr &&
286        StorageManager::readConfigFromDisk(key, &content) && config->ParseFromString(content);
287}
288
289bool 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
322bool 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
334void 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
395void StorageManager::printStats(FILE* out) {
396    printDirStats(out, STATS_SERVICE_DIR);
397    printDirStats(out, STATS_DATA_DIR);
398}
399
400void 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