blob: 02b6bbe6c9b1d1b5ef4d53fccbef032937616e42 [file] [log] [blame]
/*
* Copyright (C) 2016 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 DEBUG false
#include "Log.h"
#include "Reporter.h"
#include "incidentd_util.h"
#include "Privacy.h"
#include "PrivacyFilter.h"
#include "proto_util.h"
#include "report_directory.h"
#include "section_list.h"
#include <android-base/file.h>
#include <android/os/DropBoxManager.h>
#include <android/util/protobuf.h>
#include <android/util/ProtoOutputStream.h>
#include <private/android_filesystem_config.h>
#include <utils/SystemClock.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <time.h>
namespace android {
namespace os {
namespace incidentd {
using namespace android::util;
/**
* The field id of the metadata section from
* frameworks/base/core/proto/android/os/incident.proto
*/
const int FIELD_ID_METADATA = 2;
IncidentMetadata_Destination privacy_policy_to_dest(uint8_t privacyPolicy) {
switch (privacyPolicy) {
case PRIVACY_POLICY_AUTOMATIC:
return IncidentMetadata_Destination_AUTOMATIC;
case PRIVACY_POLICY_EXPLICIT:
return IncidentMetadata_Destination_EXPLICIT;
case PRIVACY_POLICY_LOCAL:
return IncidentMetadata_Destination_LOCAL;
default:
// Anything else reverts to automatic
return IncidentMetadata_Destination_AUTOMATIC;
}
}
static bool contains_section(const IncidentReportArgs& args, int sectionId) {
return args.containsSection(sectionId, section_requires_specific_mention(sectionId));
}
static bool contains_section(const sp<ReportRequest>& args, int sectionId) {
return args->containsSection(sectionId);
}
// ARGS must have a containsSection(int) method
template <typename ARGS>
void make_metadata(IncidentMetadata* result, const IncidentMetadata& full,
int64_t reportId, int32_t privacyPolicy, ARGS args) {
result->set_report_id(reportId);
result->set_dest(privacy_policy_to_dest(privacyPolicy));
size_t sectionCount = full.sections_size();
for (int sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) {
const IncidentMetadata::SectionStats& sectionStats = full.sections(sectionIndex);
if (contains_section(args, sectionStats.id())) {
*result->add_sections() = sectionStats;
}
}
}
// ================================================================================
class StreamingFilterFd : public FilterFd {
public:
StreamingFilterFd(uint8_t privacyPolicy, int fd, const sp<ReportRequest>& request);
virtual void onWriteError(status_t err);
private:
sp<ReportRequest> mRequest;
};
StreamingFilterFd::StreamingFilterFd(uint8_t privacyPolicy, int fd,
const sp<ReportRequest>& request)
:FilterFd(privacyPolicy, fd),
mRequest(request) {
}
void StreamingFilterFd::onWriteError(status_t err) {
mRequest->setStatus(err);
}
// ================================================================================
class PersistedFilterFd : public FilterFd {
public:
PersistedFilterFd(uint8_t privacyPolicy, int fd, const sp<ReportFile>& reportFile);
virtual void onWriteError(status_t err);
private:
sp<ReportFile> mReportFile;
};
PersistedFilterFd::PersistedFilterFd(uint8_t privacyPolicy, int fd,
const sp<ReportFile>& reportFile)
:FilterFd(privacyPolicy, fd),
mReportFile(reportFile) {
}
void PersistedFilterFd::onWriteError(status_t err) {
mReportFile->setWriteError(err);
}
// ================================================================================
ReportRequest::ReportRequest(const IncidentReportArgs& a,
const sp<IIncidentReportStatusListener>& listener, int fd)
:args(a),
mListener(listener),
mFd(fd),
mIsStreaming(fd >= 0),
mStatus(NO_ERROR) {
}
ReportRequest::~ReportRequest() {
if (mIsStreaming && mFd >= 0) {
// clean up the opened file descriptor
close(mFd);
}
}
bool ReportRequest::ok() {
return mFd >= 0 && mStatus == NO_ERROR;
}
bool ReportRequest::containsSection(int sectionId) const {
return args.containsSection(sectionId, section_requires_specific_mention(sectionId));
}
void ReportRequest::closeFd() {
if (mIsStreaming && mFd >= 0) {
close(mFd);
mFd = -1;
}
}
// ================================================================================
ReportBatch::ReportBatch() {}
ReportBatch::~ReportBatch() {}
void ReportBatch::addPersistedReport(const IncidentReportArgs& args) {
ComponentName component(args.receiverPkg(), args.receiverCls());
map<ComponentName, sp<ReportRequest>>::iterator found = mPersistedRequests.find(component);
if (found == mPersistedRequests.end()) {
// not found
mPersistedRequests[component] = new ReportRequest(args, nullptr, -1);
} else {
// found
sp<ReportRequest> request = found->second;
request->args.merge(args);
}
}
void ReportBatch::addStreamingReport(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener, int streamFd) {
mStreamingRequests.push_back(new ReportRequest(args, listener, streamFd));
}
bool ReportBatch::empty() const {
return mPersistedRequests.size() == 0 && mStreamingRequests.size() == 0;
}
sp<ReportRequest> ReportBatch::getPersistedRequest(const ComponentName& component) {
map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.find(component);
if (it != mPersistedRequests.find(component)) {
return it->second;
} else {
return nullptr;
}
}
void ReportBatch::forEachPersistedRequest(const function<void (const sp<ReportRequest>&)>& func) {
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
func(it->second);
}
}
void ReportBatch::forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func) {
for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin();
request != mStreamingRequests.end(); request++) {
func(*request);
}
}
void ReportBatch::forEachListener(
const function<void (const sp<IIncidentReportStatusListener>&)>& func) {
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
sp<IIncidentReportStatusListener> listener = it->second->getListener();
if (listener != nullptr) {
func(listener);
}
}
for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin();
request != mStreamingRequests.end(); request++) {
sp<IIncidentReportStatusListener> listener = (*request)->getListener();
if (listener != nullptr) {
func(listener);
}
}
}
void ReportBatch::forEachListener(int sectionId,
const function<void (const sp<IIncidentReportStatusListener>&)>& func) {
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
if (it->second->containsSection(sectionId)) {
sp<IIncidentReportStatusListener> listener = it->second->getListener();
if (listener != nullptr) {
func(listener);
}
}
}
for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin();
request != mStreamingRequests.end(); request++) {
if ((*request)->containsSection(sectionId)) {
sp<IIncidentReportStatusListener> listener = (*request)->getListener();
if (listener != nullptr) {
func(listener);
}
}
}
}
void ReportBatch::getCombinedPersistedArgs(IncidentReportArgs* result) {
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
result->merge(it->second->args);
}
}
bool ReportBatch::containsSection(int sectionId) {
// We don't cache this, because in case of error, we remove requests
// from the batch, and this is easier than recomputing the set.
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
if (it->second->containsSection(sectionId)) {
return true;
}
}
for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin();
request != mStreamingRequests.end(); request++) {
if ((*request)->containsSection(sectionId)) {
return true;
}
}
return false;
}
void ReportBatch::clearPersistedRequests() {
mPersistedRequests.clear();
}
void ReportBatch::transferStreamingRequests(const sp<ReportBatch>& that) {
for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin();
request != mStreamingRequests.end(); request++) {
that->mStreamingRequests.push_back(*request);
}
mStreamingRequests.clear();
}
void ReportBatch::transferPersistedRequests(const sp<ReportBatch>& that) {
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
that->mPersistedRequests[it->first] = it->second;
}
mPersistedRequests.clear();
}
void ReportBatch::getFailedRequests(vector<sp<ReportRequest>>* requests) {
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
if (it->second->getStatus() != NO_ERROR) {
requests->push_back(it->second);
}
}
for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin();
request != mStreamingRequests.end(); request++) {
if ((*request)->getStatus() != NO_ERROR) {
requests->push_back(*request);
}
}
}
void ReportBatch::removeRequest(const sp<ReportRequest>& request) {
for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin();
it != mPersistedRequests.end(); it++) {
if (it->second == request) {
mPersistedRequests.erase(it);
return;
}
}
for (vector<sp<ReportRequest>>::iterator it = mStreamingRequests.begin();
it != mStreamingRequests.end(); it++) {
if (*it == request) {
mStreamingRequests.erase(it);
return;
}
}
}
// ================================================================================
ReportWriter::ReportWriter(const sp<ReportBatch>& batch)
:mBatch(batch),
mPersistedFile(),
mMaxPersistedPrivacyPolicy(PRIVACY_POLICY_UNSET) {
}
ReportWriter::~ReportWriter() {
}
void ReportWriter::setPersistedFile(sp<ReportFile> file) {
mPersistedFile = file;
}
void ReportWriter::setMaxPersistedPrivacyPolicy(uint8_t privacyPolicy) {
mMaxPersistedPrivacyPolicy = privacyPolicy;
}
void ReportWriter::startSection(int sectionId) {
mCurrentSectionId = sectionId;
mSectionStartTimeMs = uptimeMillis();
mSectionStatsCalledForSectionId = -1;
mDumpSizeBytes = 0;
mDumpDurationMs = 0;
mSectionTimedOut = false;
mSectionTruncated = false;
mSectionBufferSuccess = false;
mHadError = false;
mSectionErrors.clear();
}
void ReportWriter::setSectionStats(const FdBuffer& buffer) {
mSectionStatsCalledForSectionId = mCurrentSectionId;
mDumpSizeBytes = buffer.size();
mDumpDurationMs = buffer.durationMs();
mSectionTimedOut = buffer.timedOut();
mSectionTruncated = buffer.truncated();
mSectionBufferSuccess = !buffer.timedOut() && !buffer.truncated();
}
void ReportWriter::endSection(IncidentMetadata::SectionStats* sectionMetadata) {
long endTime = uptimeMillis();
if (mSectionStatsCalledForSectionId != mCurrentSectionId) {
ALOGW("setSectionStats not called for section %d", mCurrentSectionId);
}
sectionMetadata->set_id(mCurrentSectionId);
sectionMetadata->set_success((!mHadError) && mSectionBufferSuccess);
sectionMetadata->set_report_size_bytes(mMaxSectionDataFilteredSize);
sectionMetadata->set_exec_duration_ms(endTime - mSectionStartTimeMs);
sectionMetadata->set_dump_size_bytes(mDumpSizeBytes);
sectionMetadata->set_dump_duration_ms(mDumpDurationMs);
sectionMetadata->set_timed_out(mSectionTimedOut);
sectionMetadata->set_is_truncated(mSectionTruncated);
sectionMetadata->set_error_msg(mSectionErrors);
}
void ReportWriter::warning(const Section* section, status_t err, const char* format, ...) {
va_list args;
va_start(args, format);
vflog(section, err, ANDROID_LOG_ERROR, "error", format, args);
va_end(args);
}
void ReportWriter::error(const Section* section, status_t err, const char* format, ...) {
va_list args;
va_start(args, format);
vflog(section, err, ANDROID_LOG_WARN, "warning", format, args);
va_end(args);
}
void ReportWriter::vflog(const Section* section, status_t err, int level, const char* levelText,
const char* format, va_list args) {
const char* prefixFormat = "%s in section %d (%d) '%s': ";
int prefixLen = snprintf(NULL, 0, prefixFormat, levelText, section->id,
err, strerror(-err));
va_list measureArgs;
va_copy(measureArgs, args);
int messageLen = vsnprintf(NULL, 0, format, args);
va_end(measureArgs);
char* line = (char*)malloc(prefixLen + messageLen + 1);
if (line == NULL) {
// All hope is lost, just give up.
return;
}
sprintf(line, prefixFormat, levelText, section->id, err, strerror(-err));
vsprintf(line + prefixLen, format, args);
__android_log_write(level, LOG_TAG, line);
if (mSectionErrors.length() == 0) {
mSectionErrors = line;
} else {
mSectionErrors += '\n';
mSectionErrors += line;
}
free(line);
if (level >= ANDROID_LOG_ERROR) {
mHadError = true;
}
}
// Reads data from FdBuffer and writes it to the requests file descriptor.
status_t ReportWriter::writeSection(const FdBuffer& buffer) {
PrivacyFilter filter(mCurrentSectionId, get_privacy_of_section(mCurrentSectionId));
// Add the fd for the persisted requests
if (mPersistedFile != nullptr) {
filter.addFd(new PersistedFilterFd(mMaxPersistedPrivacyPolicy,
mPersistedFile->getDataFileFd(), mPersistedFile));
}
// Add the fds for the streamed requests
mBatch->forEachStreamingRequest([&filter, this](const sp<ReportRequest>& request) {
if (request->ok()
&& request->args.containsSection(mCurrentSectionId,
section_requires_specific_mention(mCurrentSectionId))) {
filter.addFd(new StreamingFilterFd(request->args.getPrivacyPolicy(),
request->getFd(), request));
}
});
return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize);
}
// ================================================================================
Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
:mWorkDirectory(workDirectory),
mWriter(batch),
mBatch(batch) {
}
Reporter::~Reporter() {
}
void Reporter::runReport(size_t* reportByteSize) {
status_t err = NO_ERROR;
IncidentMetadata metadata;
int persistedPrivacyPolicy = PRIVACY_POLICY_UNSET;
(*reportByteSize) = 0;
// Tell everyone that we're starting.
ALOGI("Starting incident report");
mBatch->forEachListener([](const auto& listener) { listener->onReportStarted(); });
if (mBatch->hasPersistedReports()) {
// Open a work file to contain the contents of all of the persisted reports.
// For this block, if we can't initialize the report file for some reason,
// then we will remove the persisted ReportRequests from the report, but
// continue with the streaming ones.
mPersistedFile = mWorkDirectory->createReportFile();
ALOGI("Report will be persisted: envelope: %s data: %s",
mPersistedFile->getEnvelopeFileName().c_str(),
mPersistedFile->getDataFileName().c_str());
// Record all of the metadata to the persisted file's metadata file.
// It will be read from there and reconstructed as the actual reports
// are sent out.
if (mPersistedFile != nullptr) {
mBatch->forEachPersistedRequest([this, &persistedPrivacyPolicy](
const sp<ReportRequest>& request) {
mPersistedFile->addReport(request->args);
if (request->args.getPrivacyPolicy() < persistedPrivacyPolicy) {
persistedPrivacyPolicy = request->args.getPrivacyPolicy();
}
});
mPersistedFile->setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy);
err = mPersistedFile->saveEnvelope();
if (err != NO_ERROR) {
mWorkDirectory->remove(mPersistedFile);
mPersistedFile = nullptr;
}
mWriter.setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy);
}
if (mPersistedFile != nullptr) {
err = mPersistedFile->startWritingDataFile();
if (err != NO_ERROR) {
mWorkDirectory->remove(mPersistedFile);
mPersistedFile = nullptr;
}
}
if (mPersistedFile != nullptr) {
mWriter.setPersistedFile(mPersistedFile);
} else {
ALOGW("Error creating the persisted file, so clearing persisted reports.");
// If we couldn't open the file (permissions err, etc), then
// we still want to proceed with any streaming reports, but
// cancel all of the persisted ones.
mBatch->forEachPersistedRequest([](const sp<ReportRequest>& request) {
sp<IIncidentReportStatusListener> listener = request->getListener();
if (listener != nullptr) {
listener->onReportFailed();
}
});
mBatch->clearPersistedRequests();
}
}
// If we have a persisted ID, then we allow all the readers to see that. There's
// enough in the data to allow for a join, and nothing in here that intrisincally
// could ever prevent that, so just give them the ID. If we don't have that then we
// make and ID that's extremely likely to be unique, but clock resetting could allow
// it to be duplicate.
int64_t reportId;
if (mPersistedFile != nullptr) {
reportId = mPersistedFile->getTimestampNs();
} else {
struct timespec spec;
clock_gettime(CLOCK_REALTIME, &spec);
reportId = (spec.tv_sec) * 1000 + spec.tv_nsec;
}
// Write the incident report headers - each request gets its own headers. It's different
// from the other top-level fields in IncidentReport that are the sections where the rest
// is all shared data (although with their own individual privacy filtering).
mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) {
const vector<vector<uint8_t>>& headers = request->args.headers();
for (vector<vector<uint8_t>>::const_iterator buf = headers.begin(); buf != headers.end();
buf++) {
// If there was an error now, there will be an error later and we will remove
// it from the list then.
write_header_section(request->getFd(), buf->data(), buf->size());
}
});
// If writing to any of the headers failed, we don't want to keep processing
// sections for it.
cancel_and_remove_failed_requests();
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
for (const Section** section = SECTION_LIST; *section; section++) {
const int sectionId = (*section)->id;
// If nobody wants this section, skip it.
if (!mBatch->containsSection(sectionId)) {
continue;
}
ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
// Notify listener of starting
mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
listener->onReportSectionStatus(
sectionId, IIncidentReportStatusListener::STATUS_STARTING);
});
// Go get the data and write it into the file descriptors.
mWriter.startSection(sectionId);
err = (*section)->Execute(&mWriter);
mWriter.endSection(sectionMetadata);
// Sections returning errors are fatal. Most errors should not be fatal.
if (err != NO_ERROR) {
mWriter.error((*section), err, "Section failed. Stopping report.");
goto DONE;
}
// The returned max data size is used for throttling too many incident reports.
(*reportByteSize) += sectionMetadata->report_size_bytes();
// For any requests that failed during this section, remove them now. We do this
// before calling back about section finished, so listeners do not erroniously get the
// impression that the section succeeded. But we do it here instead of inside
// writeSection so that the callback is done from a known context and not from the
// bowels of a section, where changing the batch could cause odd errors.
cancel_and_remove_failed_requests();
// Notify listener of finishing
mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
listener->onReportSectionStatus(
sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
});
ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
}
DONE:
// Finish up the persisted file.
if (mPersistedFile != nullptr) {
mPersistedFile->closeDataFile();
// Set the stored metadata
IncidentReportArgs combinedArgs;
mBatch->getCombinedPersistedArgs(&combinedArgs);
IncidentMetadata persistedMetadata;
make_metadata(&persistedMetadata, metadata, mPersistedFile->getTimestampNs(),
persistedPrivacyPolicy, combinedArgs);
mPersistedFile->setMetadata(persistedMetadata);
mPersistedFile->markCompleted();
err = mPersistedFile->saveEnvelope();
if (err != NO_ERROR) {
ALOGW("mPersistedFile->saveEnvelope returned %s. Won't send broadcast",
strerror(-err));
// Abandon ship.
mWorkDirectory->remove(mPersistedFile);
}
}
// Write the metadata to the streaming ones
mBatch->forEachStreamingRequest([reportId, &metadata](const sp<ReportRequest>& request) {
IncidentMetadata streamingMetadata;
make_metadata(&streamingMetadata, metadata, reportId,
request->args.getPrivacyPolicy(), request);
status_t nonFatalErr = write_section(request->getFd(), FIELD_ID_METADATA,
streamingMetadata);
if (nonFatalErr != NO_ERROR) {
ALOGW("Error writing the metadata to streaming incident report. This is the last"
" thing so we won't return an error: %s", strerror(nonFatalErr));
}
});
// Finish up the streaming ones.
mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) {
request->closeFd();
});
// Tell the listeners that we're done.
if (err == NO_ERROR) {
mBatch->forEachListener([](const auto& listener) {
listener->onReportFinished();
});
} else {
mBatch->forEachListener([](const auto& listener) {
listener->onReportFailed();
});
}
ALOGI("Done taking incident report err=%s", strerror(-err));
}
void Reporter::cancel_and_remove_failed_requests() {
// Handle a failure in the persisted file
if (mPersistedFile != nullptr) {
if (mPersistedFile->getWriteError() != NO_ERROR) {
ALOGW("Error writing to the persisted file (%s). Closing it and canceling.",
strerror(-mPersistedFile->getWriteError()));
mBatch->forEachPersistedRequest([this](const sp<ReportRequest>& request) {
sp<IIncidentReportStatusListener> listener = request->getListener();
if (listener != nullptr) {
listener->onReportFailed();
}
mBatch->removeRequest(request);
});
mWriter.setPersistedFile(nullptr);
mPersistedFile->closeDataFile();
mWorkDirectory->remove(mPersistedFile);
mPersistedFile = nullptr;
}
}
// Handle failures in the streaming files
vector<sp<ReportRequest>> failed;
mBatch->getFailedRequests(&failed);
for (sp<ReportRequest>& request: failed) {
ALOGW("Error writing to a request stream (%s). Closing it and canceling.",
strerror(-request->getStatus()));
sp<IIncidentReportStatusListener> listener = request->getListener();
if (listener != nullptr) {
listener->onReportFailed();
}
request->closeFd(); // Will only close the streaming ones.
mBatch->removeRequest(request);
}
}
} // namespace incidentd
} // namespace os
} // namespace android