Write statsd configuration to disk and add cmd to clear it

Test: statsd, statsd_test
Change-Id: Iba37a7f295256d24969185bdde6cbf28f9b89a55
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index a0b2340..747a571 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -17,6 +17,7 @@
 #define DEBUG true
 #include "Log.h"
 
+#include "android-base/stringprintf.h"
 #include "StatsService.h"
 #include "config/ConfigKey.h"
 #include "config/ConfigManager.h"
@@ -25,11 +26,11 @@
 #include <android-base/file.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
+#include <dirent.h>
 #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
 #include <private/android_filesystem_config.h>
 #include <utils/Looper.h>
 #include <utils/String16.h>
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/system_properties.h>
@@ -42,6 +43,7 @@
 namespace statsd {
 
 constexpr const char* kPermissionDump = "android.permission.DUMP";
+#define STATS_SERVICE_DIR "/data/system/stats-service"
 
 // ======================================================================
 /**
@@ -206,6 +208,10 @@
         if (!args[0].compare(String8("send-broadcast"))) {
             return cmd_trigger_broadcast(args);
         }
+
+        if (!args[0].compare(String8("clear-config"))) {
+            return cmd_remove_config_files(out);
+        }
     }
 
     print_cmd_help(out);
@@ -223,6 +229,11 @@
     fprintf(out, "  Prints the UID, app name, version mapping.\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats clear-config \n");
+    fprintf(out, "\n");
+    fprintf(out, "  Removes all configs from disk.\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
     fprintf(out, "usage: adb shell cmds stats pull-source [int] \n");
     fprintf(out, "\n");
     fprintf(out, "  Prints the output of a pulled metrics source (int indicates source)\n");
@@ -405,6 +416,27 @@
     return UNKNOWN_ERROR;
 }
 
+status_t StatsService::cmd_remove_config_files(FILE* out) {
+    fprintf(out, "Trying to remove config files...\n");
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
+    if (dir == NULL) {
+        fprintf(out, "No existing config files found exiting...\n");
+        return NO_ERROR;
+    }
+
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') continue;
+        string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name);
+        fprintf(out, "Deleting file %s\n", file_name.c_str());
+        if (remove(file_name.c_str())) {
+            fprintf(out, "Error deleting file %s\n", file_name.c_str());
+        }
+    }
+    return NO_ERROR;
+}
+
 Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
                                       const vector<String16>& app) {
     if (DEBUG) ALOGD("StatsService::informAllUidData was called");
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index c3729de..0163f94 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -152,6 +152,11 @@
     status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
 
     /**
+     * Removes all configs stored on disk.
+     */
+    status_t cmd_remove_config_files(FILE* out);
+
+    /**
      * Update a configuration.
      */
     void set_config(int uid, const string& name, const StatsdConfig& config);
diff --git a/cmds/statsd/src/config/ConfigKey.h b/cmds/statsd/src/config/ConfigKey.h
index bbf20fd..3489c43 100644
--- a/cmds/statsd/src/config/ConfigKey.h
+++ b/cmds/statsd/src/config/ConfigKey.h
@@ -78,7 +78,7 @@
 
 /**
  * A hash function for ConfigKey so it can be used for unordered_map/set.
- * Unfortunately this hast to go in std namespace because C++ is fun!
+ * Unfortunately this has to go in std namespace because C++ is fun!
  */
 namespace std {
 
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index a9ce4a3..bc3a7b2 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -18,16 +18,23 @@
 
 #include "stats_util.h"
 
-#include <vector>
-
+#include <android-base/file.h>
+#include <dirent.h>
 #include <stdio.h>
+#include <vector>
+#include "android-base/stringprintf.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
+#define STATS_SERVICE_DIR "/data/system/stats-service"
+
 static StatsdConfig build_fake_config();
 
+using android::base::StringPrintf;
+using std::unique_ptr;
+
 ConfigManager::ConfigManager() {
 }
 
@@ -35,8 +42,7 @@
 }
 
 void ConfigManager::Startup() {
-    // TODO: Implement me -- read from storage and call onto all of the listeners.
-    // Instead, we'll just make a fake one.
+    readConfigFromDisk();
 
     // this should be called from StatsService when it receives a statsd_config
     UpdateConfig(ConfigKey(0, "fake"), build_fake_config());
@@ -52,7 +58,7 @@
     // Why doesn't this work? mConfigs.insert({key, config});
 
     // Save to disk
-    update_saved_configs();
+    update_saved_configs(key, config);
 
     // Tell everyone
     for (auto& listener : mListeners) {
@@ -74,8 +80,8 @@
         // Remove from map
         mConfigs.erase(it);
 
-        // Save to disk
-        update_saved_configs();
+        // Remove from disk
+        remove_saved_configs(key);
 
         // Tell everyone
         for (auto& listener : mListeners) {
@@ -85,6 +91,26 @@
     // If we didn't find it, just quietly ignore it.
 }
 
+void ConfigManager::remove_saved_configs(const ConfigKey& key) {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
+    if (dir == NULL) {
+        ALOGD("no default config on disk");
+        return;
+    }
+    string prefix = StringPrintf("%d-%s", key.GetUid(), key.GetName().c_str());
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] != '.' && strncmp(name, prefix.c_str(), prefix.size()) == 0) {
+            if (remove(StringPrintf("%s/%d-%s", STATS_SERVICE_DIR, key.GetUid(),
+                                    key.GetName().c_str())
+                               .c_str()) != 0) {
+                ALOGD("no file found");
+            }
+        }
+    }
+}
+
 void ConfigManager::RemoveConfigs(int uid) {
     vector<ConfigKey> removed;
 
@@ -118,8 +144,64 @@
     }
 }
 
-void ConfigManager::update_saved_configs() {
-    // TODO: Implement me -- write to disk.
+void ConfigManager::readConfigFromDisk() {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
+    if (dir == NULL) {
+        ALOGD("no default config on disk");
+        return;
+    }
+
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') continue;
+        ALOGD("file %s", name);
+
+        int index = 0;
+        int uid = 0;
+        string configName;
+        char* substr = strtok(name, "-");
+        // Timestamp lives at index 2 but we skip parsing it as it's not needed.
+        while (substr != nullptr && index < 2) {
+            if (index) {
+                uid = atoi(substr);
+            } else {
+                configName = substr;
+            }
+            index++;
+        }
+        if (index < 2) continue;
+        string file_name = StringPrintf("%s/%s", STATS_SERVICE_DIR, name);
+        ALOGD("full file %s", file_name.c_str());
+        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)) {
+                    mConfigs[ConfigKey(uid, configName)] = config;
+                    ALOGD("map key uid=%d|name=%s", uid, name);
+                }
+            }
+            close(fd);
+        }
+    }
+}
+
+void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfig& config) {
+    mkdir(STATS_SERVICE_DIR, S_IRWXU);
+    string file_name = StringPrintf("%s/%d-%s-%ld", STATS_SERVICE_DIR, key.GetUid(),
+                                    key.GetName().c_str(), time(nullptr));
+    int fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
+    if (fd != -1) {
+        const int numBytes = config.ByteSize();
+        vector<uint8_t> buffer(numBytes);
+        config.SerializeToArray(&buffer[0], numBytes);
+        int result = write(fd, &buffer[0], numBytes);
+        close(fd);
+        bool wroteKey = (result == numBytes);
+        ALOGD("wrote to file %d", wroteKey);
+    }
 }
 
 static StatsdConfig build_fake_config() {
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index c21247f9..5b612cc 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -46,9 +46,7 @@
     virtual ~ConfigManager();
 
     /**
-     * Call to load the saved configs from disk.
-     *
-     * TODO: Implement me
+     * Initialize ConfigListener by reading from disk and get updates.
      */
     void Startup();
 
@@ -95,7 +93,12 @@
     /**
      * Save the configs to disk.
      */
-    void update_saved_configs();
+    void update_saved_configs(const ConfigKey& key, const StatsdConfig& config);
+
+    /**
+     * Remove saved configs from disk.
+     */
+    void remove_saved_configs(const ConfigKey& key);
 
     /**
      * The Configs that have been set. Each config should
@@ -112,6 +115,11 @@
      * The ConfigListeners that will be told about changes.
      */
     vector<sp<ConfigListener>> mListeners;
+
+    /**
+     * Call to load the saved configs from disk.
+     */
+    void readConfigFromDisk();
 };
 
 }  // namespace statsd