incidentd can now handle multiple callers asking it for incident reports
Test: bit incident_test:* GtsIncidentManagerTestCases:*
Bug: 123543706
Change-Id: I9f671dd5d8b2ad139f952a23e575c2be16120459
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index f8fb4a6..4ba31b4 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -19,7 +19,7 @@
#include "IncidentService.h"
#include "FdBuffer.h"
-#include "PrivacyBuffer.h"
+#include "PrivacyFilter.h"
#include "Reporter.h"
#include "incidentd_util.h"
#include "section_list.h"
@@ -35,9 +35,12 @@
#include <unistd.h>
-enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 };
+enum {
+ WHAT_TAKE_REPORT = 1,
+ WHAT_SEND_BROADCASTS = 2
+};
-#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL)
+#define DEFAULT_DELAY_NS (1000000000LL)
#define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024) // 20MB
#define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day
@@ -53,6 +56,7 @@
namespace os {
namespace incidentd {
+String16 const APPROVE_INCIDENT_REPORTS("android.permission.APPROVE_INCIDENT_REPORTS");
String16 const DUMP_PERMISSION("android.permission.DUMP");
String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS");
@@ -60,7 +64,14 @@
uid_t callingUid = IPCThreadState::self()->getCallingUid();
pid_t callingPid = IPCThreadState::self()->getCallingPid();
if (callingUid == AID_ROOT || callingUid == AID_SHELL) {
- // root doesn't have permission.DUMP if don't do this!
+ // Root and shell are ok.
+ return Status::ok();
+ }
+
+ if (checkCallingPermission(APPROVE_INCIDENT_REPORTS)) {
+ // Permission controller (this is a singleton permission that is always granted
+ // exactly for PermissionController) is allowed to access incident reports
+ // so it can show the user info about what they are approving.
return Status::ok();
}
@@ -81,8 +92,8 @@
}
// checking calling request uid permission.
- switch (args.dest()) {
- case DEST_LOCAL:
+ switch (args.getPrivacyPolicy()) {
+ case PRIVACY_POLICY_LOCAL:
if (callingUid != AID_SHELL && callingUid != AID_ROOT) {
ALOGW("Calling pid %d and uid %d does not have permission to get local data.",
callingPid, callingUid);
@@ -91,7 +102,7 @@
"Calling process does not have permission to get local data.");
}
break;
- case DEST_EXPLICIT:
+ case PRIVACY_POLICY_EXPLICIT:
if (callingUid != AID_SHELL && callingUid != AID_ROOT && callingUid != AID_STATSD &&
callingUid != AID_SYSTEM) {
ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.",
@@ -105,78 +116,79 @@
return Status::ok();
}
-// ================================================================================
-ReportRequestQueue::ReportRequestQueue() {}
-
-ReportRequestQueue::~ReportRequestQueue() {}
-
-void ReportRequestQueue::addRequest(const sp<ReportRequest>& request) {
- unique_lock<mutex> lock(mLock);
- mQueue.push_back(request);
-}
-
-sp<ReportRequest> ReportRequestQueue::getNextRequest() {
- unique_lock<mutex> lock(mLock);
- if (mQueue.empty()) {
- return NULL;
- } else {
- sp<ReportRequest> front(mQueue.front());
- mQueue.pop_front();
- return front;
- }
+static string build_uri(const string& pkg, const string& cls, const string& id) {
+ return "build_uri not implemented " + pkg + "/" + cls + "/" + id;
}
// ================================================================================
-ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue,
- const sp<Throttler>& throttler)
- : mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS),
- mHandlerLooper(handlerLooper),
- mQueue(queue),
- mThrottler(throttler) {}
+ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
+ const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler)
+ :mLock(),
+ mWorkDirectory(workDirectory),
+ mBroadcaster(broadcaster),
+ mHandlerLooper(handlerLooper),
+ mBacklogDelay(DEFAULT_DELAY_NS),
+ mThrottler(throttler),
+ mBatch(new ReportBatch()) {
+}
-ReportHandler::~ReportHandler() {}
+ReportHandler::~ReportHandler() {
+}
void ReportHandler::handleMessage(const Message& message) {
switch (message.what) {
- case WHAT_RUN_REPORT:
- run_report();
+ case WHAT_TAKE_REPORT:
+ take_report();
break;
- case WHAT_SEND_BACKLOG_TO_DROPBOX:
- send_backlog_to_dropbox();
+ case WHAT_SEND_BROADCASTS:
+ send_broadcasts();
break;
}
}
-void ReportHandler::scheduleRunReport(const sp<ReportRequest>& request) {
- mQueue->addRequest(request);
- mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT);
- mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT));
+void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
+ mBatch->addPersistedReport(args);
+ mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
+ mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
}
-void ReportHandler::scheduleSendBacklogToDropbox() {
+void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args,
+ const sp<IIncidentReportStatusListener>& listener, int streamFd) {
+ mBatch->addStreamingReport(args, listener, streamFd);
+ mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
+ mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
+}
+
+void ReportHandler::scheduleSendBacklog() {
unique_lock<mutex> lock(mLock);
- mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS;
- schedule_send_backlog_to_dropbox_locked();
+ mBacklogDelay = DEFAULT_DELAY_NS;
+ schedule_send_broadcasts_locked();
}
-void ReportHandler::schedule_send_backlog_to_dropbox_locked() {
- mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX);
- mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BACKLOG_TO_DROPBOX));
+void ReportHandler::schedule_send_broadcasts_locked() {
+ mHandlerLooper->removeMessages(this, WHAT_SEND_BROADCASTS);
+ mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BROADCASTS));
}
-void ReportHandler::run_report() {
- sp<Reporter> reporter = new Reporter();
-
- // Merge all of the requests into one that has all of the
- // requested fields.
- while (true) {
- sp<ReportRequest> request = mQueue->getNextRequest();
- if (request == NULL) {
- break;
- }
- reporter->batch.add(request);
+void ReportHandler::take_report() {
+ // Cycle the batch
+ sp<ReportBatch> batch;
+ {
+ unique_lock<mutex> lock(mLock);
+ batch = mBatch;
+ mBatch = new ReportBatch();
}
+ if (batch->empty()) {
+ // Nothing to do.
+ return;
+ }
+
+ sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+
+ // TODO: Do we really want to clear the reports if we throttle? Should we only throttle
+ // requests going to dropbox? How do we reconcile throttling with testing?
if (mThrottler->shouldThrottle()) {
ALOGW("RunReport got throttled.");
return;
@@ -185,46 +197,76 @@
// Take the report, which might take a while. More requests might queue
// up while we're doing this, and we'll handle them in their next batch.
// TODO: We should further rate-limit the reports to no more than N per time-period.
+ // TODO: Move this inside reporter.
size_t reportByteSize = 0;
- Reporter::run_report_status_t reportStatus = reporter->runReport(&reportByteSize);
+ reporter->runReport(&reportByteSize);
+
mThrottler->addReportSize(reportByteSize);
- if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) {
+
+ // Kick off the next steps, one of which is to send any new or otherwise remaining
+ // approvals, and one of which is to send any new or remaining broadcasts.
+ {
unique_lock<mutex> lock(mLock);
- schedule_send_backlog_to_dropbox_locked();
+ schedule_send_broadcasts_locked();
}
}
-void ReportHandler::send_backlog_to_dropbox() {
- if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) {
+void ReportHandler::send_broadcasts() {
+ Broadcaster::broadcast_status_t result = mBroadcaster->sendBroadcasts();
+ if (result == Broadcaster::BROADCASTS_FINISHED) {
+ // We're done.
+ unique_lock<mutex> lock(mLock);
+ mBacklogDelay = DEFAULT_DELAY_NS;
+ } else if (result == Broadcaster::BROADCASTS_REPEAT) {
+ // It worked, but there are more.
+ unique_lock<mutex> lock(mLock);
+ mBacklogDelay = DEFAULT_DELAY_NS;
+ schedule_send_broadcasts_locked();
+ } else if (result == Broadcaster::BROADCASTS_BACKOFF) {
// There was a failure. Exponential backoff.
unique_lock<mutex> lock(mLock);
mBacklogDelay *= 2;
ALOGI("Error sending to dropbox. Trying again in %lld minutes",
(mBacklogDelay / (1000000000LL * 60)));
- schedule_send_backlog_to_dropbox_locked();
- } else {
- mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS;
+ schedule_send_broadcasts_locked();
}
}
// ================================================================================
-IncidentService::IncidentService(const sp<Looper>& handlerLooper)
- : mQueue(new ReportRequestQueue()),
- mThrottler(new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS)) {
- mHandler = new ReportHandler(handlerLooper, mQueue, mThrottler);
+IncidentService::IncidentService(const sp<Looper>& handlerLooper) {
+ mThrottler = new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS);
+ mWorkDirectory = new WorkDirectory();
+ mBroadcaster = new Broadcaster(mWorkDirectory);
+ mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
+ mThrottler);
+ mBroadcaster->setHandler(mHandler);
}
IncidentService::~IncidentService() {}
Status IncidentService::reportIncident(const IncidentReportArgs& args) {
- ALOGI("reportIncident");
+ // TODO: Validate that the privacy policy is one of the real ones.
+ // If it isn't, clamp it to the next more restrictive real one.
+ // TODO: This function should reject the LOCAL privacy policy.
+ // Those have to stream.
+
+ // TODO: Check that the broadcast recevier has the proper permissions
+ // TODO: Maybe we should consider relaxing the permissions if it's going to
+ // dropbox, but definitely not if it's going to the broadcaster.
Status status = checkIncidentPermissions(args);
if (!status.isOk()) {
return status;
}
- mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1));
+ // If they didn't specify a component, use dropbox.
+ IncidentReportArgs argsCopy(args);
+ if (argsCopy.receiverPkg().length() == 0 && argsCopy.receiverCls().length() == 0) {
+ argsCopy.setReceiverPkg(DROPBOX_SENTINEL.getPackageName());
+ argsCopy.setReceiverCls(DROPBOX_SENTINEL.getClassName());
+ }
+
+ mHandler->schedulePersistedReport(argsCopy);
return Status::ok();
}
@@ -232,19 +274,29 @@
Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener,
const unique_fd& stream) {
- ALOGI("reportIncidentToStream");
+ // TODO: Validate that the privacy policy is one of the real ones.
+ // If it isn't, clamp it to the next more restrictive real one.
- Status status = checkIncidentPermissions(args);
+ // TODO: Only shell should be able to do a LOCAL privacy policy report.
+
+ // Streaming reports can not also be broadcast.
+ IncidentReportArgs argsCopy(args);
+ argsCopy.setReceiverPkg("");
+ argsCopy.setReceiverCls("");
+
+ Status status = checkIncidentPermissions(argsCopy);
if (!status.isOk()) {
return status;
}
+
+ // The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
if (fd < 0) {
return Status::fromStatusT(-errno);
}
- mHandler->scheduleRunReport(new ReportRequest(args, listener, fd));
+ mHandler->scheduleStreamingReport(argsCopy, listener, fd);
return Status::ok();
}
@@ -256,7 +308,92 @@
}
// When system_server is up and running, schedule the dropbox task to run.
- mHandler->scheduleSendBacklogToDropbox();
+ mBroadcaster->reset();
+ mHandler->scheduleSendBacklog();
+
+ return Status::ok();
+}
+
+Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16,
+ vector<String16>* result) {
+ status_t err;
+ const string pkg(String8(pkg16).string());
+ const string cls(String8(cls16).string());
+
+ // List the reports
+ vector<sp<ReportFile>> all;
+ err = mWorkDirectory->getReports(&all, 0);
+ if (err != NO_ERROR) {
+ return Status::fromStatusT(err);
+ }
+
+ // Find the ones that match pkg and cls.
+ for (sp<ReportFile>& file: all) {
+ err = file->loadEnvelope();
+ if (err != NO_ERROR) {
+ continue;
+ }
+ const ReportFileProto& envelope = file->getEnvelope();
+ size_t reportCount = envelope.report_size();
+ for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {
+ const ReportFileProto_Report& report = envelope.report(reportIndex);
+ if (pkg == report.pkg() && cls == report.cls()) {
+ result->push_back(String16(build_uri(pkg, cls, file->getId()).c_str()));
+ break;
+ }
+ }
+ }
+
+ return Status::ok();
+}
+
+Status IncidentService::getIncidentReport(const String16& pkg16, const String16& cls16,
+ const String16& id16, IncidentManager::IncidentReport* result) {
+ status_t err;
+
+ const string pkg(String8(pkg16).string());
+ const string cls(String8(cls16).string());
+ const string id(String8(id16).string());
+
+ IncidentReportArgs args;
+ sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args);
+ if (file != nullptr) {
+ int fd;
+ err = file->startFilteringData(&fd, args);
+ if (err != 0) {
+ ALOGW("Error reading data file that we think should exist: %s",
+ file->getDataFileName().c_str());
+ return Status::ok();
+ }
+
+ result->setTimestampNs(file->getTimestampNs());
+ result->setPrivacyPolicy(file->getEnvelope().privacy_policy());
+ result->takeFileDescriptor(fd);
+ }
+
+ return Status::ok();
+}
+
+Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16,
+ const String16& id16) {
+ const string pkg(String8(pkg16).string());
+ const string cls(String8(cls16).string());
+ const string id(String8(id16).string());
+
+ sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr);
+ if (file != nullptr) {
+ mWorkDirectory->commit(file, pkg, cls);
+ }
+ mBroadcaster->clearBroadcasts(pkg, cls, id);
+
+ return Status::ok();
+}
+
+Status IncidentService::deleteAllIncidentReports(const String16& pkg16) {
+ const string pkg(String8(pkg16).string());
+
+ mWorkDirectory->commitAll(pkg);
+ mBroadcaster->clearPackageBroadcasts(pkg);
return Status::ok();
}
@@ -354,7 +491,7 @@
static void printPrivacy(const Privacy* p, FILE* out, String8 indent) {
if (p == NULL) return;
- fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->dest);
+ fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy);
if (p->children == NULL) return;
for (int i = 0; p->children[i] != NULL; i++) { // NULL-terminated.
printPrivacy(p->children[i], out, indent + " ");
@@ -362,6 +499,8 @@
}
status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
+ (void)in;
+
const int argCount = args.size();
if (argCount >= 3) {
String8 opt = args[1];
@@ -376,6 +515,7 @@
if (opt == "print") {
printPrivacy(p, out, String8(""));
} else if (opt == "parse") {
+ /*
FdBuffer buf;
status_t error = buf.read(fileno(in), 60000);
if (error != NO_ERROR) {
@@ -383,15 +523,17 @@
return error;
}
fprintf(err, "Read %zu bytes\n", buf.size());
- PrivacyBuffer pBuf(p, buf.data());
+ PrivacyFilter pBuf(p, buf.data());
PrivacySpec spec = PrivacySpec::new_spec(argCount > 3 ? atoi(args[3]) : -1);
error = pBuf.strip(spec);
if (error != NO_ERROR) {
- fprintf(err, "Error strip pii fields with spec %d\n", spec.dest);
+ fprintf(err, "Error strip pii fields with spec %d\n", spec.policy);
return error;
}
return pBuf.flush(fileno(out));
+ */
+ return -1;
}
} else {
return cmd_help(out);
@@ -408,7 +550,7 @@
ALOGD("Dump incident proto");
IncidentReportArgs incidentArgs;
- incidentArgs.setDest(DEST_EXPLICIT);
+ incidentArgs.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT);
int skipped[] = SKIPPED_SECTIONS;
for (const Section** section = SECTION_LIST; *section; section++) {
const int id = (*section)->id;
@@ -421,12 +563,16 @@
return PERMISSION_DENIED;
}
+ // The ReportRequest takes ownership of the fd, so we need to dup it.
int fd1 = dup(fd);
if (fd1 < 0) {
return -errno;
}
- mHandler->scheduleRunReport(new ReportRequest(incidentArgs, NULL, fd1));
+ // TODO: Remove this. Someone even dumpstate, wanting to get an incident report
+ // should use the API. That will take making dumpstated call the API, which is a
+ // good thing. It also means it won't be subject to the timeout.
+ mHandler->scheduleStreamingReport(incidentArgs, NULL, fd1);
return NO_ERROR;
}