/*
 * Copyright (C) 2019 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.
 */

#include "Log.h"

#include "incidentd_util.h"
#include "proto_util.h"
#include "PrivacyFilter.h"
#include "WorkDirectory.h"

#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <private/android_filesystem_config.h>

#include <iomanip>
#include <map>
#include <sstream>
#include <thread>
#include <vector>

#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>

namespace android {
namespace os {
namespace incidentd {

using std::thread;
using google::protobuf::MessageLite;
using google::protobuf::RepeatedPtrField;
using google::protobuf::io::FileInputStream;
using google::protobuf::io::FileOutputStream;

/**
 * Turn off to skip removing files for debugging.
 */
static const bool DO_UNLINK = true;

/**
 * File extension for envelope files.
 */
static const string EXTENSION_ENVELOPE(".envelope");

/**
 * File extension for data files.
 */
static const string EXTENSION_DATA(".data");

/**
 * Send these reports to dropbox.
 */
const ComponentName DROPBOX_SENTINEL("android", "DROPBOX");

/** metadata field id in IncidentProto */
const int FIELD_ID_INCIDENT_METADATA = 2;

// Args for exec gzip
static const char* GZIP[] = {"/system/bin/gzip", NULL};

/**
 * Read a protobuf from disk into the message.
 */
static status_t read_proto(MessageLite* msg, const string& filename) {
    int fd = open(filename.c_str(), O_RDONLY | O_CLOEXEC);
    if (fd < 0) {
        return -errno;
    }

    FileInputStream stream(fd);
    stream.SetCloseOnDelete(fd);

    if (!msg->ParseFromZeroCopyStream(&stream)) {
        return BAD_VALUE;
    }

    return stream.GetErrno();
}

/**
 * Write a protobuf to disk.
 */
static status_t write_proto(const MessageLite& msg, const string& filename) {
    int fd = open(filename.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660);
    if (fd < 0) {
        return -errno;
    }

    FileOutputStream stream(fd);
    stream.SetCloseOnDelete(fd);

    if (!msg.SerializeToZeroCopyStream(&stream)) {
        ALOGW("write_proto: error writing to %s", filename.c_str());
        return BAD_VALUE;
    }

    return stream.GetErrno();
}

static string strip_extension(const string& filename) {
    return filename.substr(0, filename.find('.'));
}

static bool ends_with(const string& str, const string& ending) {
    if (str.length() >= ending.length()) {
        return str.compare(str.length()-ending.length(), ending.length(), ending) == 0;
    } else {
        return false;
    }
}

// Returns true if it was a valid timestamp.
static bool parse_timestamp_ns(const string& id, int64_t* result) {
    char* endptr;
    *result = strtoll(id.c_str(), &endptr, 10);
    return id.length() != 0 && *endptr == '\0';
}

static bool has_section(const ReportFileProto_Report& report, int section) {
    const size_t sectionCount = report.section_size();
    for (int i = 0; i < sectionCount; i++) {
        if (report.section(i) == section) {
            return true;
        }
    }
    return false;
}

status_t create_directory(const char* directory) {
    struct stat st;
    status_t err = NO_ERROR;
    char* dir = strdup(directory);

    // Skip first slash
    char* d = dir + 1;

    // Create directories, assigning them to the system user
    bool last = false;
    while (!last) {
        d = strchr(d, '/');
        if (d != NULL) {
            *d = '\0';
        } else {
            last = true;
        }
        if (stat(dir, &st) == 0) {
            if (!S_ISDIR(st.st_mode)) {
                err = ALREADY_EXISTS;
                goto done;
            }
        } else {
            ALOGE("No such directory %s, something wrong.", dir);
            err = -1;
            goto done;
        }
        if (!last) {
            *d++ = '/';
        }
    }

    // Ensure that the final directory is owned by the system with 0770. If it isn't
    // we won't write into it.
    if (stat(directory, &st) != 0) {
        ALOGE("No incident reports today. Can't stat: %s", directory);
        err = -errno;
        goto done;
    }
    if ((st.st_mode & 0777) != 0770) {
        ALOGE("No incident reports today. Mode is %0o on report directory %s", st.st_mode,
              directory);
        err = BAD_VALUE;
        goto done;
    }
    if (st.st_uid != AID_INCIDENTD || st.st_gid != AID_INCIDENTD) {
        ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s",
              st.st_uid, st.st_gid, directory);
        err = BAD_VALUE;
        goto done;
    }

done:
    free(dir);
    return err;
}

void log_envelope(const ReportFileProto& envelope) {
    ALOGD("Envelope: {");
    for (int i=0; i<envelope.report_size(); i++) {
        ALOGD("  report {");
        ALOGD("    pkg=%s", envelope.report(i).pkg().c_str());
        ALOGD("    cls=%s", envelope.report(i).cls().c_str());
        ALOGD("    share_approved=%d", envelope.report(i).share_approved());
        ALOGD("    privacy_policy=%d", envelope.report(i).privacy_policy());
        ALOGD("    all_sections=%d", envelope.report(i).all_sections());
        for (int j=0; j<envelope.report(i).section_size(); j++) {
            ALOGD("    section[%d]=%d", j, envelope.report(i).section(j));
        }
        ALOGD("  }");
    }
    ALOGD("  data_file=%s", envelope.data_file().c_str());
    ALOGD("  privacy_policy=%d", envelope.privacy_policy());
    ALOGD("  data_file_size=%" PRIi64, (int64_t)envelope.data_file_size());
    ALOGD("  completed=%d", envelope.completed());
    ALOGD("}");
}

// ================================================================================
struct WorkDirectoryEntry {
    WorkDirectoryEntry();
    explicit WorkDirectoryEntry(const WorkDirectoryEntry& that);
    ~WorkDirectoryEntry();

    string envelope;
    string data;
    int64_t timestampNs;
    off_t size;
};

WorkDirectoryEntry::WorkDirectoryEntry()
        :envelope(),
         data(),
         size(0) {
}

WorkDirectoryEntry::WorkDirectoryEntry(const WorkDirectoryEntry& that)
        :envelope(that.envelope),
         data(that.data),
         size(that.size) {
}

WorkDirectoryEntry::~WorkDirectoryEntry() {
}

// ================================================================================
ReportFile::ReportFile(const sp<WorkDirectory>& workDirectory, int64_t timestampNs,
            const string& envelopeFileName, const string& dataFileName)
        :mWorkDirectory(workDirectory),
         mTimestampNs(timestampNs),
         mEnvelopeFileName(envelopeFileName),
         mDataFileName(dataFileName),
         mEnvelope(),
         mDataFd(-1),
         mError(NO_ERROR) {
    // might get overwritten when we read but that's ok
    mEnvelope.set_data_file(mDataFileName);
}

ReportFile::~ReportFile() {
    if (mDataFd >= 0) {
        close(mDataFd);
    }
}

int64_t ReportFile::getTimestampNs() const {
    return mTimestampNs;
}

void ReportFile::addReport(const IncidentReportArgs& args) {
    // There is only one report per component.  Merge into an existing one if necessary.
    ReportFileProto_Report* report;
    const int reportCount = mEnvelope.report_size();
    int i = 0;
    for (; i < reportCount; i++) {
        report = mEnvelope.mutable_report(i);
        if (report->pkg() == args.receiverPkg() && report->cls() == args.receiverCls()) {
            if (args.getPrivacyPolicy() < report->privacy_policy()) {
                // Lower privacy policy (less restrictive) wins.
                report->set_privacy_policy(args.getPrivacyPolicy());
            }
            report->set_all_sections(report->all_sections() || args.all());
            for (int section: args.sections()) {
                if (!has_section(*report, section)) {
                    report->add_section(section);
                }
            }
            break;
        }
    }
    if (i >= reportCount) {
        report = mEnvelope.add_report();
        report->set_pkg(args.receiverPkg());
        report->set_cls(args.receiverCls());
        report->set_privacy_policy(args.getPrivacyPolicy());
        report->set_all_sections(args.all());
        report->set_gzip(args.gzip());
        for (int section: args.sections()) {
            report->add_section(section);
        }
    }

    for (const vector<uint8_t>& header: args.headers()) {
        report->add_header(header.data(), header.size());
    }
}

void ReportFile::removeReport(const string& pkg, const string& cls) {
    RepeatedPtrField<ReportFileProto_Report>* reports = mEnvelope.mutable_report();
    const int reportCount = reports->size();
    for (int i = 0; i < reportCount; i++) {
        const ReportFileProto_Report& r = reports->Get(i);
        if (r.pkg() == pkg && r.cls() == cls) {
            reports->DeleteSubrange(i, 1);
            return;
        }
    }
}

void ReportFile::removeReports(const string& pkg) {
    RepeatedPtrField<ReportFileProto_Report>* reports = mEnvelope.mutable_report();
    const int reportCount = reports->size();
    for (int i = reportCount-1; i >= 0; i--) {
        const ReportFileProto_Report& r = reports->Get(i);
        if (r.pkg() == pkg) {
            reports->DeleteSubrange(i, 1);
        }
    }
}

void ReportFile::setMetadata(const IncidentMetadata& metadata) {
    *mEnvelope.mutable_metadata() = metadata;
}

void ReportFile::markCompleted() {
    mEnvelope.set_completed(true);
}

status_t ReportFile::markApproved(const string& pkg, const string& cls) {
    size_t const reportCount = mEnvelope.report_size();
    for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {
        ReportFileProto_Report* report = mEnvelope.mutable_report(reportIndex);
        if (report->pkg() == pkg && report->cls() == cls) {
            report->set_share_approved(true);
            return NO_ERROR;
        }
    }
    return NAME_NOT_FOUND;
}

void ReportFile::setMaxPersistedPrivacyPolicy(int persistedPrivacyPolicy) {
    mEnvelope.set_privacy_policy(persistedPrivacyPolicy);
}

status_t ReportFile::saveEnvelope() {
    return save_envelope_impl(true);
}

status_t ReportFile::trySaveEnvelope() {
    return save_envelope_impl(false);
}

status_t ReportFile::loadEnvelope() {
    return load_envelope_impl(true);
}

status_t ReportFile::tryLoadEnvelope() {
    return load_envelope_impl(false);
}

const ReportFileProto& ReportFile::getEnvelope() {
    return mEnvelope;
}

status_t ReportFile::startWritingDataFile() {
    if (mDataFd >= 0) {
        ALOGW("ReportFile::startWritingDataFile called with the file already open: %s",
                mDataFileName.c_str());
        return ALREADY_EXISTS;
    }
    mDataFd = open(mDataFileName.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660);
    if (mDataFd < 0) {
        return -errno;
    }
    return NO_ERROR;
}

void ReportFile::closeDataFile() {
    if (mDataFd >= 0) {
        mEnvelope.set_data_file_size(lseek(mDataFd, 0, SEEK_END));
        close(mDataFd);
        mDataFd = -1;
    }
}

status_t ReportFile::startFilteringData(int writeFd, const IncidentReportArgs& args) {
    // Open data file.
    int dataFd = open(mDataFileName.c_str(), O_RDONLY | O_CLOEXEC);
    if (dataFd < 0) {
        ALOGW("Error opening incident report '%s' %s", getDataFileName().c_str(), strerror(-errno));
        close(writeFd);
        close(dataFd);
        return -errno;
    }

    // Check that the size on disk is what we thought we wrote.
    struct stat st;
    if (fstat(dataFd, &st) != 0) {
        ALOGW("Error running fstat incident report '%s' %s", getDataFileName().c_str(),
              strerror(-errno));
        close(writeFd);
        close(dataFd);
        return -errno;
    }
    if (st.st_size != mEnvelope.data_file_size()) {
        ALOGW("File size mismatch. Envelope says %" PRIi64 " bytes but data file is %" PRIi64
              " bytes: %s",
              (int64_t)mEnvelope.data_file_size(), st.st_size, mDataFileName.c_str());
        ALOGW("Removing incident report");
        mWorkDirectory->remove(this);
        close(writeFd);
        close(dataFd);
        return BAD_VALUE;
    }

    pid_t zipPid = 0;
    if (args.gzip()) {
        Fpipe zipPipe;
        if (!zipPipe.init()) {
            ALOGE("[ReportFile] Failed to setup pipe for gzip");
            close(writeFd);
            close(dataFd);
            return -errno;
        }
        int status = 0;
        zipPid = fork_execute_cmd((char* const*)GZIP, zipPipe.readFd().release(), writeFd, &status);
        close(writeFd);
        close(dataFd);
        if (zipPid < 0 || status != 0) {
            ALOGE("[ReportFile] Failed to fork and exec gzip");
            return status;
        }
        writeFd = zipPipe.writeFd().release();
    }

    status_t err;

    for (const auto& report : mEnvelope.report()) {
        for (const auto& header : report.header()) {
           write_header_section(writeFd,
               reinterpret_cast<const uint8_t*>(header.c_str()), header.size());
        }
    }

    if (mEnvelope.has_metadata()) {
        write_section(writeFd, FIELD_ID_INCIDENT_METADATA, mEnvelope.metadata());
    }

    err = filter_and_write_report(writeFd, dataFd, mEnvelope.privacy_policy(), args);
    if (err != NO_ERROR) {
        ALOGW("Error writing incident report '%s' to dropbox: %s", getDataFileName().c_str(),
                strerror(-err));
    }

    close(writeFd);
    close(dataFd);
    if (zipPid > 0) {
        status_t err = wait_child(zipPid, /* timeout_ms= */ 10 * 1000);
        if (err != 0) {
            ALOGE("[ReportFile] abnormal child process: %s", strerror(-err));
        }
        return err;
    }
    return NO_ERROR;
}

string ReportFile::getDataFileName() const {
    return mDataFileName;
}

string ReportFile::getEnvelopeFileName() const {
    return mEnvelopeFileName;
}

int ReportFile::getDataFileFd() {
    return mDataFd;
}

void ReportFile::setWriteError(status_t err) {
    mError = err;
}

status_t ReportFile::getWriteError() {
    return mError;
}

string ReportFile::getId() {
    return to_string(mTimestampNs);
}

status_t ReportFile::save_envelope_impl(bool cleanup) {
    status_t err;
    err = write_proto(mEnvelope, mEnvelopeFileName);
    if (err != NO_ERROR) {
        // If there was an error writing the envelope, then delete the whole thing.
        if (cleanup) {
            mWorkDirectory->remove(this);
        }
        return err;
    }
    return NO_ERROR;
}

status_t ReportFile::load_envelope_impl(bool cleanup) {
    status_t err;
    err = read_proto(&mEnvelope, mEnvelopeFileName);
    if (err != NO_ERROR) {
        // If there was an error reading the envelope, then delete the whole thing.
        if (cleanup) {
            mWorkDirectory->remove(this);
        }
        return err;
    }
    return NO_ERROR;
}



// ================================================================================
//

WorkDirectory::WorkDirectory()
        :mDirectory("/data/misc/incidents"),
         mMaxFileCount(100),
         mMaxDiskUsageBytes(400 * 1024 * 1024) {  // Incident reports can take up to 400MB on disk.
                                                 // TODO: Should be a flag.
    create_directory(mDirectory.c_str());
}

WorkDirectory::WorkDirectory(const string& dir, int maxFileCount, long maxDiskUsageBytes)
        :mDirectory(dir),
         mMaxFileCount(maxFileCount),
         mMaxDiskUsageBytes(maxDiskUsageBytes) {
    create_directory(mDirectory.c_str());
}

sp<ReportFile> WorkDirectory::createReportFile() {
    unique_lock<mutex> lock(mLock);
    status_t err;

    clean_directory_locked();

    int64_t timestampNs = make_timestamp_ns_locked();
    string envelopeFileName = make_filename(timestampNs, EXTENSION_ENVELOPE);
    string dataFileName = make_filename(timestampNs, EXTENSION_DATA);

    sp<ReportFile> result = new ReportFile(this, timestampNs, envelopeFileName, dataFileName);

    err = result->trySaveEnvelope();
    if (err != NO_ERROR) {
        ALOGW("Can't save envelope file %s: %s", strerror(-errno), envelopeFileName.c_str());
        return nullptr;
    }

    return result;
}

status_t WorkDirectory::getReports(vector<sp<ReportFile>>* result, int64_t after) {
    unique_lock<mutex> lock(mLock);

    const bool DBG = true;

    if (DBG) {
        ALOGD("WorkDirectory::getReports");
    }

    map<string,WorkDirectoryEntry> files;
    get_directory_contents_locked(&files, after);
    for (map<string,WorkDirectoryEntry>::iterator it = files.begin();
            it != files.end(); it++) {
        sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs,
                it->second.envelope, it->second.data);
        if (DBG) {
            ALOGD("  %s", reportFile->getId().c_str());
        }
        result->push_back(reportFile);
    }
    return NO_ERROR;
}

sp<ReportFile> WorkDirectory::getReport(const string& pkg, const string& cls, const string& id,
            IncidentReportArgs* args) {
    unique_lock<mutex> lock(mLock);

    status_t err;
    int64_t timestampNs;
    if (!parse_timestamp_ns(id, &timestampNs)) {
        return nullptr;
    }

    // Make the ReportFile object, and then see if it's valid and for pkg and cls.
    sp<ReportFile> result = new ReportFile(this, timestampNs,
            make_filename(timestampNs, EXTENSION_ENVELOPE),
            make_filename(timestampNs, EXTENSION_DATA));

    err = result->tryLoadEnvelope();
    if (err != NO_ERROR) {
        ALOGW("Can't open envelope file for report %s/%s %s", pkg.c_str(), cls.c_str(), id.c_str());
        return nullptr;
    }

    const ReportFileProto& envelope = result->getEnvelope();
    const size_t reportCount = envelope.report_size();
    for (int i = 0; i < reportCount; i++) {
        const ReportFileProto_Report& report = envelope.report(i);
        if (report.pkg() == pkg && report.cls() == cls) {
            if (args != nullptr) {
                get_args_from_report(args, report);
            }
            return result;
        }

    }

    return nullptr;
}

bool WorkDirectory::hasMore(int64_t after) {
    unique_lock<mutex> lock(mLock);

    map<string,WorkDirectoryEntry> files;
    get_directory_contents_locked(&files, after);
    return files.size() > 0;
}

void WorkDirectory::commit(const sp<ReportFile>& report, const string& pkg, const string& cls) {
    status_t err;
    ALOGI("Committing report %s for %s/%s", report->getId().c_str(), pkg.c_str(), cls.c_str());

    unique_lock<mutex> lock(mLock);

    // Load the envelope here inside the lock.
    err = report->loadEnvelope();

    report->removeReport(pkg, cls);

    delete_files_for_report_if_necessary(report);
}

void WorkDirectory::commitAll(const string& pkg) {
    status_t err;
    ALOGI("All reports for %s", pkg.c_str());

    unique_lock<mutex> lock(mLock);

    map<string,WorkDirectoryEntry> files;
    get_directory_contents_locked(&files, 0);

    for (map<string,WorkDirectoryEntry>::iterator it = files.begin();
            it != files.end(); it++) {
        sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs,
                it->second.envelope, it->second.data);

        err = reportFile->loadEnvelope();
        if (err != NO_ERROR) {
            continue;
        }

        reportFile->removeReports(pkg);

        delete_files_for_report_if_necessary(reportFile);
    }
}

void WorkDirectory::remove(const sp<ReportFile>& report) {
    unique_lock<mutex> lock(mLock);
    // Set this to false to leave files around for debugging.
    if (DO_UNLINK) {
        unlink(report->getDataFileName().c_str());
        unlink(report->getEnvelopeFileName().c_str());
    }
}

int64_t WorkDirectory::make_timestamp_ns_locked() {
    // Guarantee that we don't have duplicate timestamps.
    // This is a little bit lame, but since reports are created on the
    // same thread and are kinda slow we'll seldomly actually hit the
    // condition.  The bigger risk is the clock getting reset and causing
    // a collision.  In that case, we'll just make incident reporting a
    // little bit slower.  Nobody will notice if we just loop until we
    // have a unique file name.
    int64_t timestampNs = 0;
    do {
        struct timespec spec;
        if (timestampNs > 0) {
            spec.tv_sec = 0;
            spec.tv_nsec = 1;
            nanosleep(&spec, nullptr);
        }
        clock_gettime(CLOCK_REALTIME, &spec);
        timestampNs = int64_t(spec.tv_sec) * 1000 + spec.tv_nsec;
    } while (file_exists_locked(timestampNs));
    return (timestampNs >= 0)? timestampNs : -timestampNs;
}

/**
 * It is required to hold the lock here so in case someone else adds it
 * our result is still correct for the caller.
 */
bool WorkDirectory::file_exists_locked(int64_t timestampNs) {
    const string filename = make_filename(timestampNs, EXTENSION_ENVELOPE);
    struct stat st;
    return stat(filename.c_str(), &st) == 0;
}

string WorkDirectory::make_filename(int64_t timestampNs, const string& extension) {
    // Zero pad the timestamp so it can also be alpha sorted.
    stringstream result;
    result << mDirectory << '/' << setfill('0') << setw(20) << timestampNs << extension;
    return result.str();
}

off_t WorkDirectory::get_directory_contents_locked(map<string,WorkDirectoryEntry>* files,
        int64_t after) {
    DIR* dir;
    struct dirent* entry;

    if ((dir = opendir(mDirectory.c_str())) == NULL) {
        ALOGE("Couldn't open incident directory: %s", mDirectory.c_str());
        return -1;
    }

    string dirbase(mDirectory);
    if (mDirectory[dirbase.size() - 1] != '/') dirbase += "/";

    off_t totalSize = 0;

    // Enumerate, count and add up size
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_name[0] == '.') {
            continue;
        }
        string entryname = entry->d_name;  // local to this dir
        string filename = dirbase + entryname;  // fully qualified

        bool isEnvelope = ends_with(entryname, EXTENSION_ENVELOPE);
        bool isData = ends_with(entryname, EXTENSION_DATA);

        // If the file isn't one of our files, just ignore it.  Otherwise,
        // sum up the sizes.
        if (isEnvelope || isData) {
            string timestamp = strip_extension(entryname);

            int64_t timestampNs;
            if (!parse_timestamp_ns(timestamp, &timestampNs)) {
                continue;
            }

            if (after == 0 || timestampNs > after) {
                struct stat st;
                if (stat(filename.c_str(), &st) != 0) {
                    ALOGE("Unable to stat file %s", filename.c_str());
                    continue;
                }
                if (!S_ISREG(st.st_mode)) {
                    continue;
                }

                WorkDirectoryEntry& entry = (*files)[timestamp];
                if (isEnvelope) {
                    entry.envelope = filename;
                } else if (isData) {
                    entry.data = filename;
                }
                entry.timestampNs = timestampNs;
                entry.size += st.st_size;
                totalSize += st.st_size;
            }
        }
    }

    closedir(dir);

    // Now check if there are any data files that don't have envelope files.
    // If there are, then just go ahead and delete them now.  Don't wait for
    // a cleaning.

    if (DO_UNLINK) {
        map<string,WorkDirectoryEntry>::iterator it = files->begin();
        while (it != files->end()) {
            if (it->second.envelope.length() == 0) {
                unlink(it->second.data.c_str());
                it = files->erase(it);
            } else {
                it++;
            }
        }
    }

    return totalSize;
}

void WorkDirectory::clean_directory_locked() {
    DIR* dir;
    struct dirent* entry;
    struct stat st;

    // Map of filename without extension to the entries about it.  Conveniently,
    // this also keeps the list sorted by filename, which is a timestamp.
    map<string,WorkDirectoryEntry> files;
    off_t totalSize = get_directory_contents_locked(&files, 0);
    if (totalSize < 0) {
        return;
    }
    int totalCount = files.size();

    // Count or size is less than max, then we're done.
    if (totalSize < mMaxDiskUsageBytes && totalCount < mMaxFileCount) {
        return;
    }

    // Remove files until we're under our limits.
    if (DO_UNLINK) {
        for (map<string, WorkDirectoryEntry>::const_iterator it = files.begin();
                it != files.end() && (totalSize >= mMaxDiskUsageBytes
                    || totalCount >= mMaxFileCount);
                it++) {
            unlink(it->second.envelope.c_str());
            unlink(it->second.data.c_str());
            totalSize -= it->second.size;
            totalCount--;
        }
    }
}

void WorkDirectory::delete_files_for_report_if_necessary(const sp<ReportFile>& report) {
    if (report->getEnvelope().report_size() == 0) {
        ALOGI("Report %s is finished. Deleting from storage.", report->getId().c_str());
        if (DO_UNLINK) {
            unlink(report->getDataFileName().c_str());
            unlink(report->getEnvelopeFileName().c_str());
        }
    }
}

// ================================================================================
void get_args_from_report(IncidentReportArgs* out, const ReportFileProto_Report& report) {
    out->setPrivacyPolicy(report.privacy_policy());
    out->setAll(report.all_sections());
    out->setReceiverPkg(report.pkg());
    out->setReceiverCls(report.cls());
    out->setGzip(report.gzip());

    const int sectionCount = report.section_size();
    for (int i = 0; i < sectionCount; i++) {
        out->addSection(report.section(i));
    }

    const int headerCount = report.header_size();
    for (int i = 0; i < headerCount; i++) {
        const string& header  = report.header(i);
        vector<uint8_t> vec(header.begin(), header.end());
        out->addHeader(vec);
    }
}


}  // namespace incidentd
}  // namespace os
}  // namespace android

