/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define STATSD_DEBUG false  // STOPSHIP if true
#include "Log.h"

#include "storage/StorageManager.h"

#include <android-base/file.h>
#include <private/android_filesystem_config.h>
#include <sys/stat.h>

#include <fstream>

#include "android-base/stringprintf.h"
#include "guardrail/StatsdStats.h"
#include "stats_log_util.h"
#include "utils/DbUtils.h"

namespace android {
namespace os {
namespace statsd {

using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_MESSAGE;
using std::map;

/**
 * NOTE: these directories are protected by SELinux, any changes here must also update
 * the SELinux policies.
 */
#define STATS_DATA_DIR "/data/misc/stats-data"
#define STATS_SERVICE_DIR "/data/misc/stats-service"

// for ConfigMetricsReportList
const int FIELD_ID_REPORTS = 2;

std::mutex StorageManager::sTrainInfoMutex;

using android::base::StringPrintf;
using std::unique_ptr;

struct FileName {
    int64_t mTimestampSec;
    int mUid;
    int64_t mConfigId;
    bool mIsHistory;
    string getFullFileName(const char* path) {
        return StringPrintf("%s/%lld_%d_%lld%s", path, (long long)mTimestampSec, (int)mUid,
                            (long long)mConfigId, (mIsHistory ? "_history" : ""));
    };
};

string StorageManager::getDataFileName(long wallClockSec, int uid, int64_t id) {
    return StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, wallClockSec, uid,
                        (long long)id);
}

string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_t id) {
    return StringPrintf("%s/%ld_%d_%lld_history", STATS_DATA_DIR, wallClockSec, uid,
                        (long long)id);
}

static string findTrainInfoFileNameLocked(const string& trainName) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
    if (dir == NULL) {
        VLOG("Path %s does not exist", TRAIN_INFO_DIR);
        return "";
    }
    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* fileName = de->d_name;
        if (fileName[0] == '.' || de->d_type == DT_DIR) continue;

        size_t fileNameLength = strlen(fileName);
        if (fileNameLength >= trainName.length()) {
            if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(),
                             trainName.length())) {
              return string(fileName);
            }
        }
    }

    return "";
}

// Returns array of int64_t which contains timestamp in seconds, uid,
// configID and whether the file is a local history file.
static void parseFileName(char* name, FileName* output) {
    int64_t result[3];
    int index = 0;
    char* substr = strtok(name, "_");
    while (substr != nullptr && index < 3) {
        result[index] = StrToInt64(substr);
        index++;
        substr = strtok(nullptr, "_");
    }
    // When index ends before hitting 3, file name is corrupted. We
    // intentionally put -1 at index 0 to indicate the error to caller.
    // TODO(b/110563137): consider removing files with unexpected name format.
    if (index < 3) {
        result[0] = -1;
    }

    output->mTimestampSec = result[0];
    output->mUid = result[1];
    output->mConfigId = result[2];
    // check if the file is a local history.
    output->mIsHistory = (substr != nullptr && strcmp("history", substr) == 0);
}

// Returns array of int64_t which contains a sqlite db's uid and configId
static ConfigKey parseDbName(char* name) {
    char* uid = strtok(name, "_");
    char* configId = strtok(nullptr, ".");
    if (uid == nullptr || configId == nullptr) {
        return ConfigKey(-1, -1);
    }
    return ConfigKey(StrToInt64(uid), StrToInt64(configId));
}

void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) {
    int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC | O_TRUNC, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        VLOG("Attempt to access %s but failed", file);
        return;
    }
    trimToFit(STATS_SERVICE_DIR);
    trimToFit(STATS_DATA_DIR);

    if (android::base::WriteFully(fd, buffer, numBytes)) {
        VLOG("Successfully wrote %s", file);
    } else {
        ALOGE("Failed to write %s", file);
    }

    int result = fchown(fd, AID_STATSD, AID_STATSD);
    if (result) {
        VLOG("Failed to chown %s to statsd", file);
    }

    close(fd);
}

bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) {
    std::lock_guard<std::mutex> lock(sTrainInfoMutex);

    if (trainInfo.trainName.empty()) {
      return false;
    }
    deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str());

    std::string fileName =
            StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(),
                         trainInfo.trainName.c_str());

    int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        VLOG("Attempt to access %s but failed", fileName.c_str());
        return false;
    }

    size_t result;
    // Write the magic word
    result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC));
    if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) {
        VLOG("Failed to wrtie train info magic");
        close(fd);
        return false;
    }

    // Write the train version
    const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode);
    result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount);
    if (result != trainVersionCodeByteCount) {
        VLOG("Failed to wrtie train version code");
        close(fd);
        return false;
    }

    // Write # of bytes in trainName to file
    const size_t trainNameSize = trainInfo.trainName.size();
    const size_t trainNameSizeByteCount = sizeof(trainNameSize);
    result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount);
    if (result != trainNameSizeByteCount) {
        VLOG("Failed to write train name size");
        close(fd);
        return false;
    }

    // Write trainName to file
    result = write(fd, trainInfo.trainName.c_str(), trainNameSize);
    if (result != trainNameSize) {
        VLOG("Failed to write train name");
        close(fd);
        return false;
    }

    // Write status to file
    const size_t statusByteCount = sizeof(trainInfo.status);
    result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount);
    if (result != statusByteCount) {
        VLOG("Failed to write status");
        close(fd);
        return false;
    }

    // Write experiment id count to file.
    const size_t experimentIdsCount = trainInfo.experimentIds.size();
    const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount);
    result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount);
    if (result != experimentIdsCountByteCount) {
        VLOG("Failed to write experiment id count");
        close(fd);
        return false;
    }

    // Write experimentIds to file
    for (size_t i = 0; i < experimentIdsCount; i++) {
        const int64_t experimentId = trainInfo.experimentIds[i];
        const size_t experimentIdByteCount = sizeof(experimentId);
        result = write(fd, &experimentId, experimentIdByteCount);
        if (result == experimentIdByteCount) {
            VLOG("Successfully wrote experiment IDs");
        } else {
            VLOG("Failed to write experiment ids");
            close(fd);
            return false;
        }
    }

    // Write bools to file
    const size_t boolByteCount = sizeof(trainInfo.requiresStaging);
    result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount);
    if (result != boolByteCount) {
      VLOG("Failed to write requires staging");
      close(fd);
      return false;
    }

    result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount);
    if (result != boolByteCount) {
      VLOG("Failed to write rollback enabled");
      close(fd);
      return false;
    }

    result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount);
    if (result != boolByteCount) {
      VLOG("Failed to write requires log latency monitor");
      close(fd);
      return false;
    }

    close(fd);
    return true;
}

bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) {
    std::lock_guard<std::mutex> lock(sTrainInfoMutex);
    return readTrainInfoLocked(trainName, trainInfo);
}

bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) {
    trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true);
    string fileName = findTrainInfoFileNameLocked(trainName);
    if (fileName.empty()) {
        return false;
    }
    int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName.c_str()).c_str(), O_RDONLY | O_CLOEXEC);
    if (fd == -1) {
        VLOG("Failed to open %s", fileName.c_str());
        return false;
    }

    // Read the magic word
    uint32_t magic;
    size_t result = read(fd, &magic, sizeof(magic));
    if (result != sizeof(magic)) {
        VLOG("Failed to read train info magic");
        close(fd);
        return false;
    }

    if (magic != TRAIN_INFO_FILE_MAGIC) {
        VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC);
        close(fd);
        return false;
    }

    // Read the train version code
    const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode));
    result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount);
    if (result != trainVersionCodeByteCount) {
        VLOG("Failed to read train version code from train info file");
        close(fd);
        return false;
    }

    // Read # of bytes taken by trainName in the file.
    size_t trainNameSize;
    result = read(fd, &trainNameSize, sizeof(size_t));
    if (result != sizeof(size_t)) {
        VLOG("Failed to read train name size from train info file");
        close(fd);
        return false;
    }

    // On devices that are upgrading from 32 to 64 bit, reading size_t bytes will result in reading
    // the wrong amount of data. We try to detect that issue here and read the file correctly.
    // In the normal case on 64-bit devices, the trainNameSize's upper 4 bytes should be 0, but if
    // a 32-bit device wrote the file, these would be the first 4 bytes of the train name.
    bool is32To64BitUpgrade = false;
    if ((sizeof(size_t) == sizeof(int64_t)) && (trainNameSize & 0xFFFFFFFF00000000) != 0) {
        is32To64BitUpgrade = true;
        // Reset the file offset to the magic + version code, and reread from the train name size.
        off_t seekResult = lseek(fd, sizeof(magic) + sizeof(trainInfo.trainVersionCode), SEEK_SET);

        if (seekResult != sizeof(magic) + sizeof(trainInfo.trainVersionCode)) {
            VLOG("Failed to reset file offset when reading 32 to 64 bit train info");
            close(fd);
            return false;
        }

        int32_t trainNameSize32Bit;
        result = read(fd, &trainNameSize32Bit, sizeof(int32_t));
        if (result != sizeof(int32_t)) {
            VLOG("Failed to read train name size 32 bit from train info file");
            close(fd);
            return false;
        }
        trainNameSize = trainNameSize32Bit;
    }

    // Read trainName
    trainInfo.trainName.resize(trainNameSize);
    result = read(fd, trainInfo.trainName.data(), trainNameSize);
    if (result != trainNameSize) {
        VLOG("Failed to read train name from train info file");
        close(fd);
        return false;
    }

    // Read status
    const size_t statusByteCount = sizeof(trainInfo.status);
    result = read(fd, &trainInfo.status, statusByteCount);
    if (result != statusByteCount) {
        VLOG("Failed to read train status from train info file");
        close(fd);
        return false;
    }

    // Read experiment ids count.
    size_t experimentIdsCount;
    int32_t experimentIdsCount32Bit;
    if (is32To64BitUpgrade) {
        result = read(fd, &experimentIdsCount32Bit, sizeof(int32_t));
        if (result != sizeof(int32_t)) {
            VLOG("Failed to read train experiment id count 32 bit from train info file");
            close(fd);
            return false;
        }
        experimentIdsCount = experimentIdsCount32Bit;
    } else {
        result = read(fd, &experimentIdsCount, sizeof(size_t));
        if (result != sizeof(size_t)) {
            VLOG("Failed to read train experiment id count from train info file");
            close(fd);
            return false;
        }
    }

    // Read experimentIds
    for (size_t i = 0; i < experimentIdsCount; i++) {
        int64_t experimentId;
        result = read(fd, &experimentId, sizeof(experimentId));
        if (result != sizeof(experimentId)) {
            VLOG("Failed to read train experiment id from train info file");
            close(fd);
            return false;
        }
        trainInfo.experimentIds.push_back(experimentId);
    }

    // Read bools
    const size_t boolByteCount = sizeof(trainInfo.requiresStaging);
    result = read(fd, &trainInfo.requiresStaging, boolByteCount);
    if (result != boolByteCount) {
        VLOG("Failed to read requires requires staging from train info file");
        close(fd);
        return false;
    }

    result = read(fd, &trainInfo.rollbackEnabled, boolByteCount);
    if (result != boolByteCount) {
        VLOG("Failed to read requires rollback enabled from train info file");
        close(fd);
        return false;
    }

    result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount);
    if (result != boolByteCount) {
        VLOG("Failed to read requires requires low latency monitor from train info file");
        close(fd);
        return false;
    }

    // Expect to be at EOF.
    char c;
    result = read(fd, &c, 1);
    if (result != 0) {
        VLOG("Failed to read train info from file. Did not get expected EOF.");
        close(fd);
        return false;
    }

    VLOG("Read train info file successful");
    close(fd);
    return true;
}

vector<InstallTrainInfo> StorageManager::readAllTrainInfo() {
    std::lock_guard<std::mutex> lock(sTrainInfoMutex);
    vector<InstallTrainInfo> trainInfoList;
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
    if (dir == NULL) {
        VLOG("Directory does not exist: %s", TRAIN_INFO_DIR);
        return trainInfoList;
    }

    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) {
            continue;
        }

        InstallTrainInfo trainInfo;
        bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo);
        if (!readSuccess) {
            continue;
        }
        trainInfoList.push_back(trainInfo);
    }
    return trainInfoList;
}

void StorageManager::deleteFile(const char* file) {
    if (remove(file) != 0) {
        VLOG("Attempt to delete %s but is not found", file);
    } else {
        VLOG("Successfully deleted %s", file);
    }
}

void StorageManager::deleteAllFiles(const char* path) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
    if (dir == NULL) {
        VLOG("Directory does not exist: %s", path);
        return;
    }

    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) continue;
        deleteFile(StringPrintf("%s/%s", path, name).c_str());
    }
}

void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
    if (dir == NULL) {
        VLOG("Directory does not exist: %s", path);
        return;
    }

    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) {
            continue;
        }
        size_t nameLen = strlen(name);
        size_t suffixLen = strlen(suffix);
        if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) {
            deleteFile(StringPrintf("%s/%s", path, name).c_str());
        }
    }
}

void StorageManager::sendBroadcast(const char* path,
                                   const std::function<void(const ConfigKey&)>& sendBroadcast) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
    if (dir == NULL) {
        VLOG("no stats-data directory on disk");
        return;
    }

    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) continue;
        VLOG("file %s", name);

        FileName output;
        parseFileName(name, &output);
        if (output.mTimestampSec == -1 || output.mIsHistory) continue;
        sendBroadcast(ConfigKey((int)output.mUid, output.mConfigId));
    }
}

bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
    if (dir == NULL) {
        VLOG("Path %s does not exist", STATS_DATA_DIR);
        return false;
    }

    string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId());

    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) continue;

        size_t nameLen = strlen(name);
        size_t suffixLen = suffix.length();
        if (suffixLen <= nameLen &&
            strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) {
            // Check again that the file name is parseable.
            FileName output;
            parseFileName(name, &output);
            if (output.mTimestampSec == -1 || output.mIsHistory) continue;
            return true;
        }
    }
    return false;
}

void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto,
                                               bool erase_data, bool isAdb) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
    if (dir == NULL) {
        VLOG("Path %s does not exist", STATS_DATA_DIR);
        return;
    }

    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        string fileName(name);
        if (name[0] == '.' || de->d_type == DT_DIR) continue;
        FileName output;
        parseFileName(name, &output);

        if (output.mTimestampSec == -1 || (output.mIsHistory && !isAdb) ||
            output.mUid != key.GetUid() || output.mConfigId != key.GetId()) {
            continue;
        }

        auto fullPathName = StringPrintf("%s/%s", STATS_DATA_DIR, fileName.c_str());
        int fd = open(fullPathName.c_str(), O_RDONLY | O_CLOEXEC);
        if (fd != -1) {
            string content;
            if (android::base::ReadFdToString(fd, &content)) {
                proto->write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS,
                             content.c_str(), content.size());
            }
            close(fd);
        } else {
            ALOGE("file cannot be opened");
        }

        if (erase_data) {
            remove(fullPathName.c_str());
        } else if (!output.mIsHistory && !isAdb) {
            // This means a real data owner has called to get this data. But the config says it
            // wants to keep a local history. So now this file must be renamed as a history file.
            // So that next time, when owner calls getData() again, this data won't be uploaded
            // again. rename returns 0 on success
            if (rename(fullPathName.c_str(), (fullPathName + "_history").c_str())) {
                ALOGE("Failed to rename file %s", fullPathName.c_str());
            }
        }
    }
}

bool StorageManager::readFileToString(const char* file, string* content) {
    int fd = open(file, O_RDONLY | O_CLOEXEC);
    bool res = false;
    if (fd != -1) {
        if (android::base::ReadFdToString(fd, content)) {
            res = true;
        } else {
            VLOG("Failed to read file %s\n", file);
        }
        close(fd);
    }
    return res;
}

void StorageManager::readConfigFromDisk(map<ConfigKey, StatsdConfig>& configsMap) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR), closedir);
    if (dir == NULL) {
        VLOG("no default config on disk");
        return;
    }
    trimToFit(STATS_SERVICE_DIR);

    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) continue;

        FileName output;
        parseFileName(name, &output);
        if (output.mTimestampSec == -1) continue;
        string file_name = output.getFullFileName(STATS_SERVICE_DIR);
        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)) {
                    configsMap[ConfigKey(output.mUid, output.mConfigId)] = config;
                    VLOG("map key uid=%lld|configID=%lld", (long long)output.mUid,
                         (long long)output.mConfigId);
                }
            }
            close(fd);
        }
    }
}

bool StorageManager::readConfigFromDisk(const ConfigKey& key, StatsdConfig* config) {
    string content;
    return config != nullptr &&
        StorageManager::readConfigFromDisk(key, &content) && config->ParseFromString(content);
}

bool StorageManager::readConfigFromDisk(const ConfigKey& key, string* content) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_SERVICE_DIR),
                                             closedir);
    if (dir == NULL) {
        VLOG("Directory does not exist: %s", STATS_SERVICE_DIR);
        return false;
    }

    string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId());
    dirent* de;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) {
            continue;
        }
        size_t nameLen = strlen(name);
        size_t suffixLen = suffix.length();
        // There can be at most one file that matches this suffix (config key).
        if (suffixLen <= nameLen &&
            strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) {
            int fd = open(StringPrintf("%s/%s", STATS_SERVICE_DIR, name).c_str(),
                                  O_RDONLY | O_CLOEXEC);
            if (fd != -1) {
                bool success = android::base::ReadFdToString(fd, content);
                close(fd);
                return success;
            }
        }
    }
    return false;
}

bool StorageManager::hasIdenticalConfig(const ConfigKey& key,
                                        const vector<uint8_t>& config) {
    string content;
    if (StorageManager::readConfigFromDisk(key, &content)) {
        vector<uint8_t> vec(content.begin(), content.end());
        if (vec == config) {
            return true;
        }
    }
    return false;
}

void StorageManager::sortFiles(vector<FileInfo>* fileNames) {
    // Reverse sort to effectively remove from the back (oldest entries).
    // This will sort files in reverse-chronological order. Local history files have lower
    // priority than regular data files.
    sort(fileNames->begin(), fileNames->end(), [](FileInfo& lhs, FileInfo& rhs) {
        // first consider if the file is a local history
        if (lhs.mIsHistory && !rhs.mIsHistory) {
            return false;
        } else if (rhs.mIsHistory && !lhs.mIsHistory) {
            return true;
        }

        // then consider the age.
        if (lhs.mFileAgeSec < rhs.mFileAgeSec) {
            return true;
        } else if (lhs.mFileAgeSec > rhs.mFileAgeSec) {
            return false;
        }

        // then good luck.... use string::compare
        return lhs.mFileName.compare(rhs.mFileName) > 0;
    });
}

void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) {
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
    if (dir == NULL) {
        VLOG("Path %s does not exist", path);
        return;
    }
    dirent* de;
    int totalFileSize = 0;
    vector<FileInfo> fileNames;
    auto nowSec = getWallClockSec();
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) continue;

        FileName output;
        string file_name;
        if (parseTimestampOnly) {
            file_name = StringPrintf("%s/%s", path, name);
            output.mTimestampSec = StrToInt64(strtok(name, "_"));
            output.mIsHistory = false;
        } else {
            parseFileName(name, &output);
            file_name = output.getFullFileName(path);
        }
        if (output.mTimestampSec == -1) continue;

        // Check for timestamp and delete if it's too old.
        long fileAge = nowSec - output.mTimestampSec;
        if (fileAge > StatsdStats::kMaxAgeSecond ||
            (output.mIsHistory && fileAge > StatsdStats::kMaxLocalHistoryAgeSecond)) {
            deleteFile(file_name.c_str());
            continue;
        }

        ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
        int fileSize = 0;
        if (file.is_open()) {
            file.seekg(0, ios::end);
            fileSize = file.tellg();
            file.close();
            totalFileSize += fileSize;
        }
        fileNames.emplace_back(file_name, output.mIsHistory, fileSize, fileAge);
    }

    if (fileNames.size() > StatsdStats::kMaxFileNumber ||
        totalFileSize > StatsdStats::kMaxFileSize) {
        sortFiles(&fileNames);
    }

    // Start removing files from oldest to be under the limit.
    while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber ||
                                    totalFileSize > StatsdStats::kMaxFileSize)) {
        totalFileSize -= fileNames.at(fileNames.size() - 1).mFileSizeBytes;
        deleteFile(fileNames.at(fileNames.size() - 1).mFileName.c_str());
        fileNames.pop_back();
    }
}

void StorageManager::printStats(int outFd) {
    printDirStats(outFd, STATS_SERVICE_DIR);
    printDirStats(outFd, STATS_DATA_DIR);
}

void StorageManager::printDirStats(int outFd, const char* path) {
    dprintf(outFd, "Printing stats of %s\n", path);
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
    if (dir == NULL) {
        VLOG("Path %s does not exist", path);
        return;
    }
    dirent* de;
    int fileCount = 0;
    int totalFileSize = 0;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) {
            continue;
        }
        FileName output;
        parseFileName(name, &output);
        if (output.mTimestampSec == -1) continue;
        dprintf(outFd, "\t #%d, Last updated: %lld, UID: %d, Config ID: %lld, %s", fileCount + 1,
                (long long)output.mTimestampSec, output.mUid, (long long)output.mConfigId,
                (output.mIsHistory ? "local history" : ""));
        string file_name = output.getFullFileName(path);
        ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
        if (file.is_open()) {
            file.seekg(0, ios::end);
            int fileSize = file.tellg();
            file.close();
            dprintf(outFd, ", File Size: %d bytes", fileSize);
            totalFileSize += fileSize;
        }
        dprintf(outFd, "\n");
        fileCount++;
    }
    dprintf(outFd, "\tTotal number of files: %d, Total size of files: %d bytes.\n", fileCount,
            totalFileSize);
}

void StorageManager::enforceDbGuardrails(const char* path, const int64_t currWallClockSec,
                                         const int64_t maxBytes) {
    if (!isAtLeastU()) {
        return;
    }
    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
    if (dir == NULL) {
        VLOG("Path %s does not exist", path);
        return;
    }

    dirent* de;
    int64_t deleteThresholdSec = currWallClockSec - StatsdStats::kMaxAgeSecond;
    while ((de = readdir(dir.get()))) {
        char* name = de->d_name;
        if (name[0] == '.' || de->d_type == DT_DIR) continue;
        string fullPathName = StringPrintf("%s/%s", path, name);
        struct stat fileInfo;
        const ConfigKey key = parseDbName(name);
        if (stat(fullPathName.c_str(), &fileInfo) != 0) {
            StatsdStats::getInstance().noteDbStatFailed(key);
            // Remove file if stat fails.
            remove(fullPathName.c_str());
            continue;
        }
        StatsdStats::getInstance().noteRestrictedConfigDbSize(key, currWallClockSec,
                                                              fileInfo.st_size);
        if (fileInfo.st_mtime <= deleteThresholdSec) {
            StatsdStats::getInstance().noteDbTooOld(key);
            remove(fullPathName.c_str());
        }
        if (fileInfo.st_size >= maxBytes) {
            StatsdStats::getInstance().noteDbSizeExceeded(key);
            remove(fullPathName.c_str());
        }
        if (hasFile(dbutils::getDbName(key).c_str())) {
            dbutils::verifyIntegrityAndDeleteIfNecessary(key);
        } else {
            // Remove file if the file name fails to parse.
            remove(fullPathName.c_str());
        }
    }
}

bool StorageManager::hasFile(const char* file) {
    struct stat fileInfo;
    return stat(file, &fileInfo) == 0;
}

}  // namespace statsd
}  // namespace os
}  // namespace android
