+ Remove the spawned thread inside the ReportFile for filter_and_write_report
because it leads to accessing freed memory
Instead, let the caller of ReportFile::startFileteringData create the thread.
ReportFile class shouldn't care about whether it's writing to a pipe for IPC
or regular file.
+ Add uri building in incidentd
+ Add metadata and headers to incident reports
Test: existing passed tests in incidentd_test still pass.
Manually tested with statsd
Change-Id: I5fef900d31f5d181275814f1e1c8c98443f201a7
837 lines
25 KiB
C++
837 lines
25 KiB
C++
/*
|
|
* 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 "WorkDirectory.h"
|
|
|
|
#include "proto_util.h"
|
|
#include "PrivacyFilter.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;
|
|
|
|
/**
|
|
* 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=%lld", 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());
|
|
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);
|
|
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);
|
|
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);
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
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);
|
|
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(30 * 1024 * 1024) { // Incident reports can take up to 30MB 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, ×tampNs)) {
|
|
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 = (spec.tv_sec) * 1000 + spec.tv_nsec;
|
|
} while (file_exists_locked(timestampNs));
|
|
return 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, ×tampNs)) {
|
|
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());
|
|
|
|
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
|
|
|