Merge "incidentd can now handle multiple callers asking it for incident reports"

This commit is contained in:
Joe Onorato
2019-03-27 04:06:06 +00:00
committed by Android (Google) Code Review
159 changed files with 5760 additions and 2084 deletions

View File

@@ -721,6 +721,7 @@ java_defaults {
"frameworks/av/camera/aidl",
"frameworks/av/media/libaudioclient/aidl",
"frameworks/native/aidl/gui",
"frameworks/native/libs/incidentcompanion/binder",
"system/core/storaged/binder",
"system/vold/binder",
"system/gsid/aidl",
@@ -968,7 +969,10 @@ java_library {
output_params: ["store_unknown_fields=true"],
include_dirs: ["external/protobuf/src"],
},
exclude_srcs: [
"core/proto/android/privacy.proto",
"core/proto/android/section.proto",
],
sdk_version: "current",
srcs: [
"core/proto/**/*.proto",
@@ -988,6 +992,10 @@ java_library {
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
],
exclude_srcs: [
"core/proto/android/privacy.proto",
"core/proto/android/section.proto",
],
// Protos have lots of MissingOverride and similar.
errorprone: {
javacflags: ["-XepDisableAllChecks"],
@@ -995,9 +1003,9 @@ java_library {
}
// ==== c++ proto device library ==============================
cc_library {
name: "libplatformprotos",
host_supported: true,
cc_defaults {
name: "libplatformprotos-defaults",
proto: {
export_proto_headers: true,
include_dirs: ["external/protobuf/src"],
@@ -1011,8 +1019,13 @@ cc_library {
srcs: [
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
}
cc_library {
name: "libplatformprotos",
defaults: ["libplatformprotos-defaults"],
host_supported: true,
target: {
host: {
@@ -1024,6 +1037,9 @@ cc_library {
proto: {
type: "lite",
},
shared_libs: [
"libprotobuf-cpp-lite",
],
shared: {
enabled: false,
},
@@ -1031,6 +1047,26 @@ cc_library {
},
}
// This is the full proto version of libplatformprotos. It may only
// be used by test code that is not shipped on the device.
cc_library {
name: "libplatformprotos-test",
defaults: ["libplatformprotos-defaults"],
host_supported: false,
target: {
android: {
proto: {
type: "full",
},
shared: {
enabled: false,
},
},
},
}
gensrcs {
name: "gen-platform-proto-constants",
depfile: true,
@@ -1068,6 +1104,7 @@ gensrcs {
output_extension: "proto.h",
}
subdirs = [
"cmds/*",
"core/*",

View File

@@ -29,6 +29,10 @@ cc_binary {
"libincident",
],
static_libs: [
"libplatformprotos",
],
cflags: [
"-Wall",
"-Werror",

View File

@@ -68,6 +68,7 @@ Status
StatusListener::onReportSectionStatus(int32_t section, int32_t status)
{
fprintf(stderr, "section %d status %d\n", section, status);
ALOGD("section %d status %d\n", section, status);
return Status::ok();
}
@@ -75,6 +76,7 @@ Status
StatusListener::onReportServiceStatus(const String16& service, int32_t status)
{
fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status);
ALOGD("service '%s' status %d\n", String8(service).string(), status);
return Status::ok();
}
@@ -82,6 +84,7 @@ Status
StatusListener::onReportFinished()
{
fprintf(stderr, "done\n");
ALOGD("done\n");
exit(0);
return Status::ok();
}
@@ -90,6 +93,7 @@ Status
StatusListener::onReportFailed()
{
fprintf(stderr, "failed\n");
ALOGD("failed\n");
exit(1);
return Status::ok();
}
@@ -146,24 +150,49 @@ find_section(const char* name)
// ================================================================================
static int
get_dest(const char* arg)
get_privacy_policy(const char* arg)
{
if (strcmp(arg, "L") == 0
|| strcmp(arg, "LOCAL") == 0) {
return DEST_LOCAL;
return PRIVACY_POLICY_LOCAL;
}
if (strcmp(arg, "E") == 0
|| strcmp(arg, "EXPLICIT") == 0) {
return DEST_EXPLICIT;
return PRIVACY_POLICY_EXPLICIT;
}
if (strcmp(arg, "A") == 0
|| strcmp(arg, "AUTO") == 0
|| strcmp(arg, "AUTOMATIC") == 0) {
return DEST_AUTOMATIC;
return PRIVACY_POLICY_AUTOMATIC;
}
return -1; // return the default value
}
// ================================================================================
static bool
parse_receiver_arg(const string& arg, string* pkg, string* cls)
{
if (arg.length() == 0) {
return true;
}
size_t slash = arg.find('/');
if (slash == string::npos) {
return false;
}
if (slash == 0 || slash == arg.length() - 1) {
return false;
}
if (arg.find('/', slash+1) != string::npos) {
return false;
}
pkg->assign(arg, 0, slash);
cls->assign(arg, slash+1);
if ((*cls)[0] == '.') {
*cls = (*pkg) + (*cls);
}
return true;
}
// ================================================================================
static void
usage(FILE* out)
@@ -173,10 +202,13 @@ usage(FILE* out)
fprintf(out, "Takes an incident report.\n");
fprintf(out, "\n");
fprintf(out, "OPTIONS\n");
fprintf(out, " -l list available sections\n");
fprintf(out, " -p privacy spec, LOCAL, EXPLICIT or AUTOMATIC. Default AUTOMATIC.\n");
fprintf(out, "\n");
fprintf(out, "and one of these destinations:\n");
fprintf(out, " -b (default) print the report to stdout (in proto format)\n");
fprintf(out, " -d send the report into dropbox\n");
fprintf(out, " -l list available sections\n");
fprintf(out, " -p privacy spec, LOCAL, EXPLICIT or AUTOMATIC\n");
fprintf(out, " -s PKG/CLS send broadcast to the broadcast receiver.\n");
fprintf(out, "\n");
fprintf(out, " SECTION the field numbers of the incident report fields to include\n");
fprintf(out, "\n");
@@ -187,12 +219,13 @@ main(int argc, char** argv)
{
Status status;
IncidentReportArgs args;
enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT;
int dest = -1; // default
enum { DEST_UNSET, DEST_DROPBOX, DEST_STDOUT, DEST_BROADCAST } destination = DEST_UNSET;
int privacyPolicy = PRIVACY_POLICY_AUTOMATIC;
string receiverArg;
// Parse the args
int opt;
while ((opt = getopt(argc, argv, "bhdlp:")) != -1) {
while ((opt = getopt(argc, argv, "bhdlp:s:")) != -1) {
switch (opt) {
case 'h':
usage(stdout);
@@ -201,13 +234,29 @@ main(int argc, char** argv)
section_list(stdout);
return 0;
case 'b':
if (!(destination == DEST_UNSET || destination == DEST_STDOUT)) {
usage(stderr);
return 1;
}
destination = DEST_STDOUT;
break;
case 'd':
if (!(destination == DEST_UNSET || destination == DEST_DROPBOX)) {
usage(stderr);
return 1;
}
destination = DEST_DROPBOX;
break;
case 'p':
dest = get_dest(optarg);
privacyPolicy = get_privacy_policy(optarg);
break;
case 's':
if (destination != DEST_UNSET) {
usage(stderr);
return 1;
}
destination = DEST_BROADCAST;
receiverArg = optarg;
break;
default:
usage(stderr);
@@ -215,6 +264,17 @@ main(int argc, char** argv)
}
}
string pkg;
string cls;
if (parse_receiver_arg(receiverArg, &pkg, &cls)) {
args.setReceiverPkg(pkg);
args.setReceiverCls(cls);
} else {
fprintf(stderr, "badly formatted -s package/class option: %s\n\n", receiverArg.c_str());
usage(stderr);
return 1;
}
if (optind == argc) {
args.setAll(true);
} else {
@@ -236,7 +296,7 @@ main(int argc, char** argv)
}
}
}
args.setDest(dest);
args.setPrivacyPolicy(privacyPolicy);
// Start the thread pool.
sp<ProcessState> ps(ProcessState::self());
@@ -272,12 +332,17 @@ main(int argc, char** argv)
//IPCThreadState::self()->joinThreadPool();
while (true) {
int amt = splice(fds[0], NULL, STDOUT_FILENO, NULL, 4096, 0);
fprintf(stderr, "spliced %d bytes\n", amt);
uint8_t buf[4096];
ssize_t amt = TEMP_FAILURE_RETRY(read(fds[0], buf, sizeof(buf)));
if (amt < 0) {
return errno;
break;
} else if (amt == 0) {
return 0;
break;
}
ssize_t wamt = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buf, amt));
if (wamt != amt) {
return errno;
}
}
} else {

View File

@@ -21,6 +21,7 @@ cc_binary {
srcs: [
"src/**/*.cpp",
"src/**/*.proto",
":incidentd_section_list",
],
@@ -43,6 +44,10 @@ cc_binary {
local_include_dirs: ["src"],
generated_headers: ["gen-platform-proto-constants"],
proto: {
type: "lite",
},
shared_libs: [
"libbase",
"libbinder",
@@ -56,6 +61,11 @@ cc_binary {
"libprotobuf-cpp-lite",
],
static_libs: [
"libincidentcompanion",
"libplatformprotos",
],
init_rc: ["incidentd.rc"],
}
@@ -72,6 +82,7 @@ cc_test {
"-Wall",
"-Wno-unused-variable",
"-Wunused-parameter",
"-g",
// Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed.
"-Wno-error=implicit-fallthrough",
@@ -82,21 +93,26 @@ cc_test {
srcs: [
"tests/**/*.cpp",
"src/PrivacyBuffer.cpp",
"tests/**/*.proto",
"src/FdBuffer.cpp",
"src/Privacy.cpp",
"src/PrivacyFilter.cpp",
"src/Reporter.cpp",
"src/Section.cpp",
"src/Throttler.cpp",
"src/WorkDirectory.cpp",
"src/incidentd_util.cpp",
"src/proto_util.cpp",
"src/report_directory.cpp",
"src/**/*.proto",
],
data: ["testdata/**/*"],
static_libs: [
"libgmock",
"libplatformprotos",
"libincidentcompanion",
"libplatformprotos-test",
],
shared_libs: [
"libbase",
@@ -105,11 +121,19 @@ cc_test {
"libdumputils",
"libincident",
"liblog",
"libprotobuf-cpp-lite",
"libprotobuf-cpp-full",
"libprotoutil",
"libservices",
"libutils",
],
target: {
android: {
proto: {
type: "full",
},
},
},
}
genrule {

View File

@@ -0,0 +1,440 @@
/*
* 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.
*/
#include "Log.h"
#include "Broadcaster.h"
#include "IncidentService.h"
#include <android/os/DropBoxManager.h>
#include <binder/IServiceManager.h>
namespace android {
namespace os {
namespace incidentd {
using android::os::IIncidentCompanion;
using binder::Status;
// ============================================================
Broadcaster::ConsentListener::ConsentListener(const sp<Broadcaster>& broadcaster,
const ReportId& reportId)
:mBroadcaster(broadcaster),
mId(reportId) {
}
Broadcaster::ConsentListener::~ConsentListener() {
}
Status Broadcaster::ConsentListener::onReportApproved() {
mBroadcaster->report_approved(mId);
return Status::ok();
}
Status Broadcaster::ConsentListener::onReportDenied() {
mBroadcaster->report_denied(mId);
return Status::ok();
}
// ============================================================
Broadcaster::ReportId::ReportId()
:id(),
pkg(),
cls() {
}
Broadcaster::ReportId::ReportId(const ReportId& that)
:id(that.id),
pkg(that.pkg),
cls(that.cls) {
}
Broadcaster::ReportId::ReportId(const string& i, const string& p, const string& c)
:id(i),
pkg(p),
cls(c) {
}
Broadcaster::ReportId::~ReportId() {
}
bool Broadcaster::ReportId::operator<(const ReportId& that) const {
if (id < that.id) {
return true;
}
if (id > that.id) {
return false;
}
if (pkg < that.pkg) {
return true;
}
if (pkg > that.pkg) {
return false;
}
if (cls < that.cls) {
return true;
}
return false;
}
// ============================================================
Broadcaster::ReportStatus::ReportStatus()
:approval_sent(false),
ready_sent(false),
listener(nullptr) {
}
Broadcaster::ReportStatus::ReportStatus(const ReportStatus& that)
:approval_sent(that.approval_sent),
ready_sent(that.ready_sent),
listener(that.listener) {
}
Broadcaster::ReportStatus::~ReportStatus() {
}
// ============================================================
Broadcaster::Broadcaster(const sp<WorkDirectory>& workDirectory)
:mReportHandler(),
mWorkDirectory(workDirectory) {
}
void Broadcaster::setHandler(const sp<ReportHandler>& handler) {
mReportHandler = handler;
}
void Broadcaster::reset() {
unique_lock<mutex> lock(mLock);
mLastSent = 0;
mHistory.clear();
// Could cancel the listeners, but this happens when
// the system process crashes, so don't bother.
}
void Broadcaster::clearBroadcasts(const string& pkg, const string& cls, const string& id) {
unique_lock<mutex> lock(mLock);
map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
if (found != mHistory.end()) {
if (found->second.listener != nullptr) {
sp<IIncidentCompanion> ics = get_incident_companion();
if (ics != nullptr) {
ics->cancelAuthorization(found->second.listener);
}
}
mHistory.erase(found);
}
}
void Broadcaster::clearPackageBroadcasts(const string& pkg) {
unique_lock<mutex> lock(mLock);
map<ReportId,ReportStatus>::iterator it = mHistory.begin();
while (it != mHistory.end()) {
if (it->first.pkg == pkg) {
if (it->second.listener != nullptr) {
sp<IIncidentCompanion> ics = get_incident_companion();
if (ics != nullptr) {
ics->cancelAuthorization(it->second.listener);
}
}
it = mHistory.erase(it);
} else {
it++;
}
}
}
Broadcaster::broadcast_status_t Broadcaster::sendBroadcasts() {
int err;
int64_t lastSent = get_last_sent();
vector<sp<ReportFile>> files;
mWorkDirectory->getReports(&files, 0); //lastSent);
// Don't send multiple broadcasts to the same receiver.
set<ReportId> reportReadyBroadcasts;
for (const sp<ReportFile>& file: files) {
err = file->loadEnvelope();
if (err != NO_ERROR) {
ALOGW("Error (%s) loading envelope from %s", strerror(-err),
file->getEnvelopeFileName().c_str());
continue;
}
const ReportFileProto& envelope = file->getEnvelope();
if (!envelope.completed()) {
ALOGI("Incident report not completed skipping it: %s",
file->getEnvelopeFileName().c_str());
continue;
}
// When one of the broadcast functions in this loop fails, it's almost
// certainly because the system process is crashing or has crashed. Rather
// than continuing to pound on the system process and potentially make things
// worse, we bail right away, return BROADCASTS_BACKOFF, and we will try
// again later. In the meantime, if the system process did crash, it might
// clear out mHistory, which means we'll be back here again to send the
// backlog.
size_t reportCount = envelope.report_size();
bool hasApprovalPending = false;
for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {
const ReportFileProto_Report& report = envelope.report(reportIndex);
status_t err;
if (report.privacy_policy() == PRIVACY_POLICY_AUTOMATIC || report.share_approved()) {
// It's privacy policy is AUTO, or it's been approved,
// so send the actual broadcast.
if (!was_ready_sent(file->getId(), report.pkg(), report.cls())) {
if (report.pkg() == DROPBOX_SENTINEL.getPackageName()
&& report.cls() == DROPBOX_SENTINEL.getClassName()) {
IncidentReportArgs args;
get_args_from_report(&args, report);
err = send_to_dropbox(file, args);
if (err != NO_ERROR) {
return BROADCASTS_BACKOFF;
}
} else {
reportReadyBroadcasts.insert(ReportId(file->getId(), report.pkg(),
report.cls()));
}
}
} else {
// It's not approved yet, so send the approval.
if (!was_approval_sent(file->getId(), report.pkg(), report.cls())) {
err = send_approval_broadcasts(file->getId(), report.pkg(), report.cls());
if (err != NO_ERROR) {
return BROADCASTS_BACKOFF;
}
hasApprovalPending = true;
}
}
}
lastSent = file->getTimestampNs();
if (!hasApprovalPending) {
set_last_sent(lastSent);
}
}
for (const ReportId& report: reportReadyBroadcasts) {
err = send_report_ready_broadcasts(report.id, report.pkg, report.cls);
if (err != NO_ERROR) {
return BROADCASTS_BACKOFF;
}
}
return mWorkDirectory->hasMore(lastSent) ? BROADCASTS_REPEAT : BROADCASTS_FINISHED;
}
void Broadcaster::set_last_sent(int64_t timestamp) {
unique_lock<mutex> lock(mLock);
mLastSent = timestamp;
}
int64_t Broadcaster::get_last_sent() {
unique_lock<mutex> lock(mLock);
return mLastSent;
}
/*
void Broadcaster::printReportStatuses() const {
ALOGD("mHistory {");
for (map<ReportId,ReportStatus>::const_iterator it = mHistory.begin();
it != mHistory.end(); it++) {
ALOGD(" [%s %s] --> [%d %d]", it->first.id.c_str(), it->first.pkg.c_str(),
it->second.approval_sent, it->second.ready_sent);
}
ALOGD("}");
}
*/
bool Broadcaster::was_approval_sent(const string& id, const string& pkg, const string& cls) {
unique_lock<mutex> lock(mLock);
map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
if (found != mHistory.end()) {
return found->second.approval_sent;
}
return false;
}
void Broadcaster::set_approval_sent(const string& id, const string& pkg, const string& cls,
const sp<ConsentListener>& listener) {
unique_lock<mutex> lock(mLock);
ReportStatus& reportStatus = mHistory[ReportId(id, pkg, cls)];
reportStatus.approval_sent = true;
reportStatus.listener = listener;
}
bool Broadcaster::was_ready_sent(const string& id, const string& pkg, const string& cls) {
unique_lock<mutex> lock(mLock);
map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls));
if (found != mHistory.end()) {
return found->second.ready_sent;
}
return false;
}
void Broadcaster::set_ready_sent(const string& id, const string& pkg, const string& cls) {
unique_lock<mutex> lock(mLock);
mHistory[ReportId(id, pkg, cls)].ready_sent = true;
}
status_t Broadcaster::send_approval_broadcasts(const string& id, const string& pkg,
const string& cls) {
sp<IIncidentCompanion> ics = get_incident_companion();
if (ics == nullptr) {
return NAME_NOT_FOUND;
}
sp<ConsentListener> listener = new ConsentListener(this, ReportId(id, pkg, cls));
ALOGI("send_approval_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str());
Status status = ics->authorizeReport(0, String16(pkg.c_str()),
String16(cls.c_str()), String16(id.c_str()), 0, listener);
if (!status.isOk()) {
// authorizeReport is oneway, so any error is a transaction error.
return status.transactionError();
}
set_approval_sent(id, pkg, cls, listener);
return NO_ERROR;
}
void Broadcaster::report_approved(const ReportId& reportId) {
status_t err;
// Kick off broadcaster to do send the ready broadcasts.
ALOGI("The user approved the report, so kicking off another broadcast pass. %s %s/%s",
reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id,
nullptr);
if (file != nullptr) {
err = file->loadEnvelope();
if (err != NO_ERROR) {
return;
}
err = file->markApproved(reportId.pkg, reportId.cls);
if (err != NO_ERROR) {
ALOGI("Couldn't find report that was just approved: %s %s/%s",
reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
return;
}
file->saveEnvelope();
if (err != NO_ERROR) {
return;
}
}
mReportHandler->scheduleSendBacklog();
}
void Broadcaster::report_denied(const ReportId& reportId) {
// The user didn't approve the report, so remove it from the WorkDirectory.
ALOGI("The user denied the report, so deleting it. %s %s/%s",
reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str());
sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id,
nullptr);
if (file != nullptr) {
mWorkDirectory->commit(file, reportId.pkg, reportId.cls);
}
}
status_t Broadcaster::send_report_ready_broadcasts(const string& id, const string& pkg,
const string& cls) {
sp<IIncidentCompanion> ics = get_incident_companion();
if (ics == nullptr) {
return NAME_NOT_FOUND;
}
ALOGI("send_report_ready_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str());
Status status = ics->sendReportReadyBroadcast(String16(pkg.c_str()), String16(cls.c_str()));
if (!status.isOk()) {
// sendReportReadyBroadcast is oneway, so any error is a transaction error.
return status.transactionError();
}
set_ready_sent(id, pkg, cls);
return NO_ERROR;
}
status_t Broadcaster::send_to_dropbox(const sp<ReportFile>& file,
const IncidentReportArgs& args) {
status_t err;
sp<DropBoxManager> dropbox = new DropBoxManager();
if (dropbox == nullptr) {
ALOGW("Can't reach dropbox now, so we won't be able to write the incident report to there");
return NO_ERROR;
}
// Start a thread to write the data to dropbox.
int readFd = -1;
err = file->startFilteringData(&readFd, args);
if (err != NO_ERROR) {
return err;
}
// Takes ownership of readFd.
Status status = dropbox->addFile(String16("incident"), readFd, 0);
if (!status.isOk()) {
// TODO: This may or may not leak the readFd, depending on where it failed.
// Not sure how to fix this given the dropbox API.
ALOGW("Error sending incident report to dropbox.");
return -errno;
}
// On successful write, tell the working directory that this file is done.
mWorkDirectory->commit(file, DROPBOX_SENTINEL.getPackageName(),
DROPBOX_SENTINEL.getClassName());
// Don't need to call set_ready_sent, because we just removed it from the ReportFile,
// so we'll never hear about it again.
return NO_ERROR;
}
sp<IIncidentCompanion> Broadcaster::get_incident_companion() {
sp<IBinder> binder = defaultServiceManager()->getService(String16("incidentcompanion"));
if (binder == nullptr) {
ALOGI("Can not find IIncidentCompanion service to send broadcast. Will try again later.");
return nullptr;
}
sp<IIncidentCompanion> ics = interface_cast<IIncidentCompanion>(binder);
if (ics == nullptr) {
ALOGI("The incidentcompanion service is not an IIncidentCompanion. Will try again later.");
return nullptr;
}
return ics;
}
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -0,0 +1,127 @@
/*
* 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.
*/
#pragma once
#include "WorkDirectory.h"
#include <android/os/BnIncidentAuthListener.h>
#include <android/os/IIncidentCompanion.h>
#include <frameworks/base/cmds/incidentd/src/report_file.pb.h>
namespace android {
namespace os {
namespace incidentd {
using android::binder::Status;
using android::os::BnIncidentAuthListener;
using android::os::IIncidentCompanion;
class ReportHandler;
class Broadcaster : public virtual RefBase {
public:
enum broadcast_status_t {
BROADCASTS_FINISHED = 0,
BROADCASTS_REPEAT = 1,
BROADCASTS_BACKOFF = 2
};
Broadcaster(const sp<WorkDirectory>& workDirectory);
void setHandler(const sp<ReportHandler>& handler);
/**
* Reset the beginning timestamp for broadcasts. Call this when
* the system_server restarts.
*/
void reset();
/**
* Remove the history record for the broadcasts, including pending authorizations
* if necessary.
*/
void clearBroadcasts(const string& pkg, const string& cls, const string& id);
void clearPackageBroadcasts(const string& pkg);
/**
* Send whichever broadcasts have been pending.
*/
broadcast_status_t sendBroadcasts();
private:
struct ReportId {
ReportId();
ReportId(const ReportId& that);
ReportId(const string& i, const string& p, const string& c);
~ReportId();
bool operator<(const ReportId& that) const;
string id;
string pkg;
string cls;
};
class ConsentListener : public BnIncidentAuthListener {
public:
ConsentListener(const sp<Broadcaster>& broadcaster, const ReportId& reportId);
virtual ~ConsentListener();
virtual Status onReportApproved();
virtual Status onReportDenied();
private:
sp<Broadcaster> mBroadcaster;
ReportId mId;
};
struct ReportStatus {
ReportStatus();
ReportStatus(const ReportStatus& that);
~ReportStatus();
bool approval_sent;
bool ready_sent;
sp<ConsentListener> listener;
};
sp<ReportHandler> mReportHandler;
sp<WorkDirectory> mWorkDirectory;
// protected by mLock
mutex mLock;
map<ReportId,ReportStatus> mHistory; // what we sent so we don't send it again
int64_t mLastSent;
void set_last_sent(int64_t timestamp);
int64_t get_last_sent();
void print_report_statuses() const;
status_t send_approval_broadcasts(const string& id, const string& pkg, const string& cls);
void report_approved(const ReportId& reportId);
void report_denied(const ReportId& reportId);
status_t send_report_ready_broadcasts(const string& id, const string& pkg, const string& cls);
status_t send_to_dropbox(const sp<ReportFile>& file, const IncidentReportArgs& args);
bool was_approval_sent(const string& id, const string& pkg, const string& cls);
void set_approval_sent(const string& id, const string& pkg, const string& cls,
const sp<ConsentListener>& listener);
bool was_ready_sent(const string& id, const string& pkg, const string& cls);
void set_ready_sent(const string& id, const string& pkg, const string& cls);
sp<IIncidentCompanion> get_incident_companion();
};
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -34,9 +34,15 @@ const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB
const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max
FdBuffer::FdBuffer()
: mBuffer(BUFFER_SIZE), mStartTime(-1), mFinishTime(-1), mTimedOut(false), mTruncated(false) {}
:mBuffer(new EncodedBuffer(BUFFER_SIZE)),
mStartTime(-1),
mFinishTime(-1),
mTimedOut(false),
mTruncated(false) {
}
FdBuffer::~FdBuffer() {}
FdBuffer::~FdBuffer() {
}
status_t FdBuffer::read(int fd, int64_t timeout) {
struct pollfd pfds = {.fd = fd, .events = POLLIN};
@@ -45,12 +51,12 @@ status_t FdBuffer::read(int fd, int64_t timeout) {
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
while (true) {
if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
mTruncated = true;
VLOG("Truncating data");
break;
}
if (mBuffer.writeBuffer() == NULL) {
if (mBuffer->writeBuffer() == NULL) {
VLOG("No memory");
return NO_MEMORY;
}
@@ -76,7 +82,7 @@ status_t FdBuffer::read(int fd, int64_t timeout) {
return errno != 0 ? -errno : UNKNOWN_ERROR;
} else {
ssize_t amt = TEMP_FAILURE_RETRY(
::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite()));
::read(fd, mBuffer->writeBuffer(), mBuffer->currentToWrite()));
if (amt < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
@@ -88,7 +94,7 @@ status_t FdBuffer::read(int fd, int64_t timeout) {
VLOG("Reached EOF of fd=%d", fd);
break;
}
mBuffer.wp()->move(amt);
mBuffer->wp()->move(amt);
}
}
}
@@ -100,28 +106,28 @@ status_t FdBuffer::readFully(int fd) {
mStartTime = uptimeMillis();
while (true) {
if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
// Don't let it get too big.
mTruncated = true;
VLOG("Truncating data");
break;
}
if (mBuffer.writeBuffer() == NULL) {
if (mBuffer->writeBuffer() == NULL) {
VLOG("No memory");
return NO_MEMORY;
}
ssize_t amt =
TEMP_FAILURE_RETRY(::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite()));
TEMP_FAILURE_RETRY(::read(fd, mBuffer->writeBuffer(), mBuffer->currentToWrite()));
if (amt < 0) {
VLOG("Fail to read %d: %s", fd, strerror(errno));
return -errno;
} else if (amt == 0) {
VLOG("Done reading %zu bytes", mBuffer.size());
VLOG("Done reading %zu bytes", mBuffer->size());
// We're done.
break;
}
mBuffer.wp()->move(amt);
mBuffer->wp()->move(amt);
}
mFinishTime = uptimeMillis();
@@ -150,12 +156,12 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f
// This is the buffer used to store processed data
while (true) {
if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
VLOG("Truncating data");
mTruncated = true;
break;
}
if (mBuffer.writeBuffer() == NULL) {
if (mBuffer->writeBuffer() == NULL) {
VLOG("No memory");
return NO_MEMORY;
}
@@ -248,7 +254,7 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f
// read from parsing process
ssize_t amt = TEMP_FAILURE_RETRY(
::read(fromFd.get(), mBuffer.writeBuffer(), mBuffer.currentToWrite()));
::read(fromFd.get(), mBuffer->writeBuffer(), mBuffer->currentToWrite()));
if (amt < 0) {
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
VLOG("Fail to read fromFd %d: %s", fromFd.get(), strerror(errno));
@@ -258,7 +264,7 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f
VLOG("Reached EOF of fromFd %d", fromFd.get());
break;
} else {
mBuffer.wp()->move(amt);
mBuffer->wp()->move(amt);
}
}
@@ -266,9 +272,25 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f
return NO_ERROR;
}
size_t FdBuffer::size() const { return mBuffer.size(); }
status_t FdBuffer::write(uint8_t const* buf, size_t size) {
return mBuffer->writeRaw(buf, size);
}
EncodedBuffer::iterator FdBuffer::data() const { return mBuffer.begin(); }
status_t FdBuffer::write(const sp<ProtoReader>& reader) {
return mBuffer->writeRaw(reader);
}
status_t FdBuffer::write(const sp<ProtoReader>& reader, size_t size) {
return mBuffer->writeRaw(reader, size);
}
size_t FdBuffer::size() const {
return mBuffer->size();
}
sp<EncodedBuffer> FdBuffer::data() const {
return mBuffer;
}
} // namespace incidentd
} // namespace os

View File

@@ -63,6 +63,21 @@ public:
status_t readProcessedDataInStream(int fd, unique_fd toFd, unique_fd fromFd, int64_t timeoutMs,
const bool isSysfs = false);
/**
* Write by hand into the buffer.
*/
status_t write(uint8_t const* buf, size_t size);
/**
* Write all the data from a ProtoReader into our internal buffer.
*/
status_t write(const sp<ProtoReader>& data);
/**
* Write size bytes of data from a ProtoReader into our internal buffer.
*/
status_t write(const sp<ProtoReader>& data, size_t size);
/**
* Whether we timed out.
*/
@@ -89,17 +104,12 @@ public:
int64_t durationMs() const { return mFinishTime - mStartTime; }
/**
* Reader API for data stored in FdBuffer
* Get the EncodedBuffer inside.
*/
EncodedBuffer::iterator data() const;
/**
* Return the internal buffer, don't call unless you are familiar with EncodedBuffer.
*/
EncodedBuffer* getInternalBuffer() { return &mBuffer; }
sp<EncodedBuffer> data() const;
private:
EncodedBuffer mBuffer;
sp<EncodedBuffer> mBuffer;
int64_t mStartTime;
int64_t mFinishTime;
bool mTimedOut;

View File

@@ -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 android {
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 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) {
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 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) {
}
// 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 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) {
"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 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) {
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 @@ void ReportHandler::run_report() {
// 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::reportIncident(const IncidentReportArgs& args) {
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 @@ Status IncidentService::systemRunning() {
}
// 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 @@ status_t IncidentService::cmd_help(FILE* out) {
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 @@ static void printPrivacy(const Privacy* p, FILE* out, String8 indent) {
}
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 @@ status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<Str
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 @@ status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<Str
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 @@ status_t IncidentService::dump(int fd, const Vector<String16>& args) {
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 @@ status_t IncidentService::dump(int fd, const Vector<String16>& args) {
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;
}

View File

@@ -20,13 +20,16 @@
#include "Reporter.h"
#include "Broadcaster.h"
#include "Throttler.h"
#include "WorkDirectory.h"
#include <android/os/BnIncidentManager.h>
#include <utils/Looper.h>
#include <deque>
#include <vector>
#include <mutex>
#include "Throttler.h"
namespace android {
namespace os {
@@ -37,61 +40,75 @@ using namespace android::base;
using namespace android::binder;
using namespace android::os;
// ================================================================================
class ReportRequestQueue : public virtual RefBase {
public:
ReportRequestQueue();
virtual ~ReportRequestQueue();
void addRequest(const sp<ReportRequest>& request);
sp<ReportRequest> getNextRequest();
private:
mutex mLock;
deque<sp<ReportRequest> > mQueue;
};
// ================================================================================
class ReportHandler : public MessageHandler {
public:
ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue,
const sp<Throttler>& throttler);
ReportHandler(const sp<WorkDirectory>& workDirectory,
const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
const sp<Throttler>& throttler);
virtual ~ReportHandler();
virtual void handleMessage(const Message& message);
/**
* Adds a ReportRequest to the queue.
* Schedule a report for the "main" report, where it will be delivered to
* the uploaders and/or dropbox.
*/
void scheduleRunReport(const sp<ReportRequest>& request);
void schedulePersistedReport(const IncidentReportArgs& args);
/**
* Adds a ReportRequest to the queue for one that has a listener an and fd
*/
void scheduleStreamingReport(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener,
int streamFd);
/**
* Resets mBacklogDelay to the default and schedules sending
* the messages to dropbox.
*/
void scheduleSendBacklogToDropbox();
void scheduleSendBacklog();
private:
mutex mLock;
nsecs_t mBacklogDelay;
sp<WorkDirectory> mWorkDirectory;
sp<Broadcaster> mBroadcaster;
sp<Looper> mHandlerLooper;
sp<ReportRequestQueue> mQueue;
nsecs_t mBacklogDelay;
sp<Throttler> mThrottler;
sp<ReportBatch> mBatch;
/**
* Runs all of the reports that have been queued.
*/
void run_report();
void take_report();
/**
* Schedules a dropbox task mBacklogDelay nanoseconds from now.
* Schedules permission controller approve the reports.
*/
void schedule_send_backlog_to_dropbox_locked();
void schedule_send_approvals_locked();
/**
* Sends the backlog to the dropbox service.
* Sends the approvals to the PermissionController
*/
void send_backlog_to_dropbox();
void send_approvals();
/**
* Schedules the broadcasts that reports are complete mBacklogDelay nanoseconds from now.
* The delay is because typically when an incident report is taken, the system is not
* really in a happy state. So we wait a bit before sending the report to let things
* quiet down if they can. The urgency is in taking the report, not sharing the report.
* However, we don
*/
void schedule_send_broadcasts_locked();
/**
* Sends the broadcasts to the dropbox service.
*/
void send_broadcasts();
};
// ================================================================================
@@ -108,6 +125,17 @@ public:
virtual Status systemRunning();
virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
vector<String16>* result);
virtual Status getIncidentReport(const String16& pkg, const String16& cls,
const String16& id, IncidentManager::IncidentReport* result);
virtual Status deleteIncidentReports(const String16& pkg, const String16& cls,
const String16& id);
virtual Status deleteAllIncidentReports(const String16& pkg);
// Implement commands for debugging purpose.
virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
uint32_t flags) override;
@@ -115,7 +143,8 @@ public:
virtual status_t dump(int fd, const Vector<String16>& args);
private:
sp<ReportRequestQueue> mQueue;
sp<WorkDirectory> mWorkDirectory;
sp<Broadcaster> mBroadcaster;
sp<ReportHandler> mHandler;
sp<Throttler> mThrottler;

View File

@@ -23,6 +23,8 @@ namespace android {
namespace os {
namespace incidentd {
using namespace android::os;
uint64_t encode_field_id(const Privacy* p) { return (uint64_t)p->type << 32 | p->field_id; }
const Privacy* lookup(const Privacy* p, uint32_t fieldId) {
@@ -35,39 +37,48 @@ const Privacy* lookup(const Privacy* p, uint32_t fieldId) {
return NULL;
}
static bool allowDest(const uint8_t dest, const uint8_t policy) {
switch (policy) {
case android::os::DEST_LOCAL:
return dest == android::os::DEST_LOCAL;
case android::os::DEST_EXPLICIT:
case DEST_UNSET:
return dest == android::os::DEST_LOCAL || dest == android::os::DEST_EXPLICIT ||
dest == DEST_UNSET;
case android::os::DEST_AUTOMATIC:
static bool isAllowed(const uint8_t policy, const uint8_t check) {
switch (check) {
case PRIVACY_POLICY_LOCAL:
return policy == PRIVACY_POLICY_LOCAL;
case PRIVACY_POLICY_EXPLICIT:
case PRIVACY_POLICY_UNSET:
return policy == PRIVACY_POLICY_LOCAL
|| policy == PRIVACY_POLICY_EXPLICIT
|| policy == PRIVACY_POLICY_UNSET;
case PRIVACY_POLICY_AUTOMATIC:
return true;
default:
return false;
}
}
bool PrivacySpec::operator<(const PrivacySpec& other) const { return dest < other.dest; }
bool PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) const {
uint8_t policy = privacy != NULL ? privacy->dest : defaultDest;
return allowDest(dest, policy);
PrivacySpec::PrivacySpec(uint8_t argPolicy) {
// TODO: Why on earth do we have two definitions of policy. Maybe
// it's not too late to clean this up.
switch (argPolicy) {
case android::os::PRIVACY_POLICY_AUTOMATIC:
case android::os::PRIVACY_POLICY_EXPLICIT:
case android::os::PRIVACY_POLICY_LOCAL:
mPolicy = argPolicy;
break;
default:
mPolicy = android::os::PRIVACY_POLICY_AUTOMATIC;
break;
}
}
bool PrivacySpec::RequireAll() const { return dest == android::os::DEST_LOCAL; }
bool PrivacySpec::operator<(const PrivacySpec& that) const {
return mPolicy < that.mPolicy;
}
PrivacySpec PrivacySpec::new_spec(int dest) {
switch (dest) {
case android::os::DEST_AUTOMATIC:
case android::os::DEST_EXPLICIT:
case android::os::DEST_LOCAL:
return PrivacySpec(dest);
default:
return PrivacySpec(android::os::DEST_AUTOMATIC);
}
bool PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) const {
uint8_t check = privacy != NULL ? privacy->policy : defaultDest;
return isAllowed(mPolicy, check);
}
bool PrivacySpec::RequireAll() const {
return mPolicy == android::os::PRIVACY_POLICY_LOCAL;
}
} // namespace incidentd

View File

@@ -18,15 +18,15 @@
#ifndef PRIVACY_H
#define PRIVACY_H
#include <android/os/IncidentReportArgs.h>
#include <stdint.h>
namespace android {
namespace os {
namespace incidentd {
// This is the default value of DEST enum, sync with privacy.proto
const uint8_t DEST_UNSET = 255; // DEST_UNSET is not exposed to libincident
const uint8_t DEST_DEFAULT_VALUE = DEST_UNSET;
using namespace android::os;
/*
* In order to NOT auto-generate large chuck of code by proto compiler in incidentd,
@@ -48,8 +48,8 @@ struct Privacy {
// This array is NULL-terminated.
Privacy** children;
// DESTINATION Enum in frameworks/base/libs/incident/proto/android/privacy.proto.
uint8_t dest;
// DESTINATION Enum in frameworks/base/core/proto/android/privacy.proto.
uint8_t policy;
// A list of regexp rules for stripping string fields in proto.
const char** patterns;
};
@@ -63,27 +63,28 @@ const Privacy* lookup(const Privacy* p, uint32_t fieldId);
/**
* PrivacySpec defines the request has what level of privacy authorization.
* For example, a device without user consent should only be able to upload AUTOMATIC fields.
* DEST_UNSET are treated as DEST_EXPLICIT.
* PRIVACY_POLICY_UNSET are treated as PRIVACY_POLICY_EXPLICIT.
*/
class PrivacySpec {
public:
const uint8_t dest;
explicit PrivacySpec(uint8_t argPolicy);
PrivacySpec() : dest(DEST_DEFAULT_VALUE) {}
bool operator<(const PrivacySpec& other) const;
// check permission of a policy, if returns true, don't strip the data.
bool CheckPremission(const Privacy* privacy,
const uint8_t defaultDest = DEST_DEFAULT_VALUE) const;
const uint8_t defaultPrivacyPolicy = PRIVACY_POLICY_UNSET) const;
// if returns true, no data need to be stripped.
bool RequireAll() const;
// Constructs spec using static methods below.
static PrivacySpec new_spec(int dest);
uint8_t getPolicy() const;
private:
explicit PrivacySpec(uint8_t dest) : dest(dest) {}
// unimplemented constructors
explicit PrivacySpec();
uint8_t mPolicy;
};
} // namespace incidentd

View File

@@ -1,157 +0,0 @@
/*
* 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 DEBUG false
#include "Log.h"
#include "PrivacyBuffer.h"
#include "incidentd_util.h"
#include <android-base/file.h>
#include <android/util/protobuf.h>
#include <log/log.h>
namespace android {
namespace os {
namespace incidentd {
/**
* Write the field to buf based on the wire type, iterator will point to next field.
* If skip is set to true, no data will be written to buf. Return number of bytes written.
*/
void PrivacyBuffer::writeFieldOrSkip(uint32_t fieldTag, bool skip) {
uint8_t wireType = read_wire_type(fieldTag);
size_t bytesToWrite = 0;
uint64_t varint = 0;
switch (wireType) {
case WIRE_TYPE_VARINT:
varint = mData.readRawVarint();
if (!skip) {
mProto.writeRawVarint(fieldTag);
mProto.writeRawVarint(varint);
}
return;
case WIRE_TYPE_FIXED64:
if (!skip) mProto.writeRawVarint(fieldTag);
bytesToWrite = 8;
break;
case WIRE_TYPE_LENGTH_DELIMITED:
bytesToWrite = mData.readRawVarint();
if (!skip) mProto.writeLengthDelimitedHeader(read_field_id(fieldTag), bytesToWrite);
break;
case WIRE_TYPE_FIXED32:
if (!skip) mProto.writeRawVarint(fieldTag);
bytesToWrite = 4;
break;
}
if (skip) {
mData.rp()->move(bytesToWrite);
} else {
for (size_t i = 0; i < bytesToWrite; i++) {
mProto.writeRawByte(mData.next());
}
}
}
/**
* Strip next field based on its private policy and request spec, then stores data in buf.
* Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in FdBuffer.
*
* The iterator must point to the head of a protobuf formatted field for successful operation.
* After exit with NO_ERROR, iterator points to the next protobuf field's head.
*/
status_t PrivacyBuffer::stripField(const Privacy* parentPolicy, const PrivacySpec& spec,
int depth /* use as a counter for this recusive method. */) {
if (!mData.hasNext() || parentPolicy == NULL) return BAD_VALUE;
uint32_t fieldTag = mData.readRawVarint();
uint32_t fieldId = read_field_id(fieldTag);
const Privacy* policy = lookup(parentPolicy, fieldId);
VLOG("[Depth %2d]Try to strip id %d, wiretype %d", depth, fieldId, read_wire_type(fieldTag));
if (policy == NULL || policy->children == NULL) {
bool skip = !spec.CheckPremission(policy, parentPolicy->dest);
// iterator will point to head of next field
size_t currentAt = mData.rp()->pos();
writeFieldOrSkip(fieldTag, skip);
VLOG("[Depth %2d]Field %d %ss %zu bytes", depth, fieldId, skip ? "skip" : "write",
get_varint_size(fieldTag) + mData.rp()->pos() - currentAt);
return NO_ERROR;
}
// current field is message type and its sub-fields have extra privacy policies
uint32_t msgSize = mData.readRawVarint();
size_t start = mData.rp()->pos();
uint64_t token = mProto.start(encode_field_id(policy));
while (mData.rp()->pos() - start != msgSize) {
status_t err = stripField(policy, spec, depth + 1);
if (err != NO_ERROR) {
VLOG("Bad value when stripping id %d, wiretype %d, tag %#x, depth %d, size %d, "
"relative pos %zu, ", fieldId, read_wire_type(fieldTag), fieldTag, depth,
msgSize, mData.rp()->pos() - start);
return err;
}
}
mProto.end(token);
return NO_ERROR;
}
// ================================================================================
PrivacyBuffer::PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator data)
: mPolicy(policy), mData(data), mProto(), mSize(0) {}
PrivacyBuffer::~PrivacyBuffer() {}
status_t PrivacyBuffer::strip(const PrivacySpec& spec) {
VLOG("Strip with spec %d", spec.dest);
// optimization when no strip happens
if (mPolicy == NULL || mPolicy->children == NULL || spec.RequireAll()) {
if (spec.CheckPremission(mPolicy)) mSize = mData.size();
return NO_ERROR;
}
while (mData.hasNext()) {
status_t err = stripField(mPolicy, spec, 0);
if (err != NO_ERROR) return err; // Error logged in stripField.
}
if (mData.bytesRead() != mData.size()) {
VLOG("Buffer corrupted: expect %zu bytes, read %zu bytes",
mData.size(), mData.bytesRead());
return BAD_VALUE;
}
mSize = mProto.size();
mData.rp()->rewind(); // rewind the read pointer back to beginning after the strip.
return NO_ERROR;
}
void PrivacyBuffer::clear() {
mSize = 0;
mProto.clear();
}
size_t PrivacyBuffer::size() const { return mSize; }
status_t PrivacyBuffer::flush(int fd) {
status_t err = NO_ERROR;
EncodedBuffer::iterator iter = size() == mData.size() ? mData : mProto.data();
while (iter.readBuffer() != NULL) {
err = WriteFully(fd, iter.readBuffer(), iter.currentToRead()) ? NO_ERROR : -errno;
iter.rp()->move(iter.currentToRead());
if (err != NO_ERROR) return err;
}
return NO_ERROR;
}
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -1,79 +0,0 @@
/*
* 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.
*/
#pragma once
#ifndef PRIVACY_BUFFER_H
#define PRIVACY_BUFFER_H
#include "Privacy.h"
#include <android/util/EncodedBuffer.h>
#include <android/util/ProtoOutputStream.h>
#include <stdint.h>
#include <utils/Errors.h>
namespace android {
namespace os {
namespace incidentd {
using namespace android::util;
/**
* PrivacyBuffer holds the original protobuf data and strips PII-sensitive fields
* based on the request and holds stripped data in its own buffer for output.
*/
class PrivacyBuffer {
public:
PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator data);
~PrivacyBuffer();
/**
* Strip based on the request and hold data in its own buffer. Return NO_ERROR if strip
* succeeds.
*/
status_t strip(const PrivacySpec& spec);
/**
* Clear encoded buffer so it can be reused by another request.
*/
void clear();
/**
* Return the size of the stripped data.
*/
size_t size() const;
/**
* Flush buffer to the given fd. NO_ERROR is returned if the flush succeeds.
*/
status_t flush(int fd);
private:
const Privacy* mPolicy;
EncodedBuffer::iterator mData;
ProtoOutputStream mProto;
size_t mSize;
status_t stripField(const Privacy* parentPolicy, const PrivacySpec& spec, int depth);
void writeFieldOrSkip(uint32_t fieldTag, bool skip);
};
} // namespace incidentd
} // namespace os
} // namespace android
#endif // PRIVACY_BUFFER_H

View File

@@ -0,0 +1,375 @@
/*
* 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 DEBUG false
#include "Log.h"
#include "incidentd_util.h"
#include "PrivacyFilter.h"
#include "proto_util.h"
#include <android-base/file.h>
#include <android/util/protobuf.h>
#include <android/util/ProtoFileReader.h>
#include <log/log.h>
namespace android {
namespace os {
namespace incidentd {
// ================================================================================
/**
* Write the field to buf based on the wire type, iterator will point to next field.
* If skip is set to true, no data will be written to buf. Return number of bytes written.
*/
void write_field_or_skip(ProtoOutputStream* out, const sp<ProtoReader>& in,
uint32_t fieldTag, bool skip) {
uint8_t wireType = read_wire_type(fieldTag);
size_t bytesToWrite = 0;
uint64_t varint = 0;
switch (wireType) {
case WIRE_TYPE_VARINT:
varint = in->readRawVarint();
if (!skip) {
out->writeRawVarint(fieldTag);
out->writeRawVarint(varint);
}
return;
case WIRE_TYPE_FIXED64:
if (!skip) {
out->writeRawVarint(fieldTag);
}
bytesToWrite = 8;
break;
case WIRE_TYPE_LENGTH_DELIMITED:
bytesToWrite = in->readRawVarint();
if (!skip) {
out->writeLengthDelimitedHeader(read_field_id(fieldTag), bytesToWrite);
}
break;
case WIRE_TYPE_FIXED32:
if (!skip) {
out->writeRawVarint(fieldTag);
}
bytesToWrite = 4;
break;
}
if (skip) {
in->move(bytesToWrite);
} else {
for (size_t i = 0; i < bytesToWrite; i++) {
out->writeRawByte(in->next());
}
}
}
/**
* Strip next field based on its private policy and request spec, then stores data in buf.
* Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in
* FdBuffer.
*
* The iterator must point to the head of a protobuf formatted field for successful operation.
* After exit with NO_ERROR, iterator points to the next protobuf field's head.
*
* depth is the depth of recursion, for debugging.
*/
status_t strip_field(ProtoOutputStream* out, const sp<ProtoReader>& in,
const Privacy* parentPolicy, const PrivacySpec& spec, int depth) {
if (!in->hasNext() || parentPolicy == NULL) {
return BAD_VALUE;
}
uint32_t fieldTag = in->readRawVarint();
uint32_t fieldId = read_field_id(fieldTag);
const Privacy* policy = lookup(parentPolicy, fieldId);
if (policy == NULL || policy->children == NULL) {
bool skip = !spec.CheckPremission(policy, parentPolicy->policy);
// iterator will point to head of next field
size_t currentAt = in->bytesRead();
write_field_or_skip(out, in, fieldTag, skip);
return NO_ERROR;
}
// current field is message type and its sub-fields have extra privacy policies
uint32_t msgSize = in->readRawVarint();
size_t start = in->bytesRead();
uint64_t token = out->start(encode_field_id(policy));
while (in->bytesRead() - start != msgSize) {
status_t err = strip_field(out, in, policy, spec, depth + 1);
if (err != NO_ERROR) {
ALOGW("Bad value when stripping id %d, wiretype %d, tag %#x, depth %d, size %d, "
"relative pos %zu, ", fieldId, read_wire_type(fieldTag), fieldTag, depth,
msgSize, in->bytesRead() - start);
return err;
}
}
out->end(token);
return NO_ERROR;
}
// ================================================================================
class FieldStripper {
public:
FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data,
uint8_t bufferLevel);
/**
* Take the data that we have, and filter it down so that no fields
* are more sensitive than the given privacy policy.
*/
status_t strip(uint8_t privacyPolicy);
/**
* At the current filter level, how many bytes of data there is.
*/
ssize_t dataSize() const { return mSize; }
/**
* Write the data from the current filter level to the file descriptor.
*/
status_t writeData(int fd);
private:
/**
* The global set of field --> required privacy level mapping.
*/
const Privacy* mRestrictions;
/**
* The current buffer.
*/
sp<ProtoReader> mData;
/**
* The current size of the buffer inside mData.
*/
ssize_t mSize;
/**
* The current privacy policy that the data is filtered to, as an optimization
* so we don't always re-filter data that has already been filtered.
*/
uint8_t mCurrentLevel;
};
FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data,
uint8_t bufferLevel)
:mRestrictions(restrictions),
mData(data),
mSize(data->size()),
mCurrentLevel(bufferLevel) {
if (mSize < 0) {
ALOGW("FieldStripper constructed with a ProtoReader that doesn't support size."
" Data will be missing.");
}
}
status_t FieldStripper::strip(const uint8_t privacyPolicy) {
// If the current strip level is less (fewer fields retained) than what's already in the
// buffer, then we can skip it.
if (mCurrentLevel < privacyPolicy) {
PrivacySpec spec(privacyPolicy);
ProtoOutputStream proto;
// Optimization when no strip happens.
if (mRestrictions == NULL || mRestrictions->children == NULL || spec.RequireAll()) {
if (spec.CheckPremission(mRestrictions)) {
mSize = mData->size();
}
return NO_ERROR;
}
while (mData->hasNext()) {
status_t err = strip_field(&proto, mData, mRestrictions, spec, 0);
if (err != NO_ERROR) {
return err; // Error logged in strip_field.
}
}
if (mData->bytesRead() != mData->size()) {
ALOGW("Buffer corrupted: expect %zu bytes, read %zu bytes", mData->size(),
mData->bytesRead());
return BAD_VALUE;
}
mData = proto.data();
mSize = proto.size();
mCurrentLevel = privacyPolicy;
}
return NO_ERROR;
}
status_t FieldStripper::writeData(int fd) {
status_t err = NO_ERROR;
sp<ProtoReader> reader = mData;
while (reader->readBuffer() != NULL) {
err = WriteFully(fd, reader->readBuffer(), reader->currentToRead()) ? NO_ERROR : -errno;
reader->move(reader->currentToRead());
if (err != NO_ERROR) return err;
}
return NO_ERROR;
}
// ================================================================================
FilterFd::FilterFd(uint8_t privacyPolicy, int fd)
:mPrivacyPolicy(privacyPolicy),
mFd(fd) {
}
FilterFd::~FilterFd() {
}
// ================================================================================
PrivacyFilter::PrivacyFilter(int sectionId, const Privacy* restrictions)
:mSectionId(sectionId),
mRestrictions(restrictions),
mOutputs() {
}
PrivacyFilter::~PrivacyFilter() {
}
void PrivacyFilter::addFd(const sp<FilterFd>& output) {
mOutputs.push_back(output);
}
status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel,
size_t* maxSize) {
status_t err;
if (maxSize != NULL) {
*maxSize = 0;
}
// Order the writes by privacy filter, with increasing levels of filtration,k
// so we can do the filter once, and then write many times.
sort(mOutputs.begin(), mOutputs.end(),
[](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool {
return a->getPrivacyPolicy() < b->getPrivacyPolicy();
});
uint8_t privacyPolicy = PRIVACY_POLICY_LOCAL; // a.k.a. no filtering
FieldStripper fieldStripper(mRestrictions, buffer.data()->read(), bufferLevel);
for (const sp<FilterFd>& output: mOutputs) {
// Do another level of filtering if necessary
if (privacyPolicy != output->getPrivacyPolicy()) {
privacyPolicy = output->getPrivacyPolicy();
err = fieldStripper.strip(privacyPolicy);
if (err != NO_ERROR) {
// We can't successfully strip this data. We will skip
// the rest of this section.
return err;
}
}
// Write the resultant buffer to the fd, along with the header.
ssize_t dataSize = fieldStripper.dataSize();
if (dataSize > 0) {
err = write_section_header(output->getFd(), mSectionId, dataSize);
if (err != NO_ERROR) {
output->onWriteError(err);
continue;
}
err = fieldStripper.writeData(output->getFd());
if (err != NO_ERROR) {
output->onWriteError(err);
continue;
}
}
if (maxSize != NULL) {
if (dataSize > *maxSize) {
*maxSize = dataSize;
}
}
}
return NO_ERROR;
}
// ================================================================================
class ReadbackFilterFd : public FilterFd {
public:
ReadbackFilterFd(uint8_t privacyPolicy, int fd);
virtual void onWriteError(status_t err);
status_t getError() { return mError; }
private:
status_t mError;
};
ReadbackFilterFd::ReadbackFilterFd(uint8_t privacyPolicy, int fd)
:FilterFd(privacyPolicy, fd),
mError(NO_ERROR) {
}
void ReadbackFilterFd::onWriteError(status_t err) {
mError = err;
}
// ================================================================================
status_t filter_and_write_report(int to, int from, uint8_t bufferLevel,
const IncidentReportArgs& args) {
status_t err;
sp<ProtoFileReader> reader = new ProtoFileReader(from);
while (reader->hasNext()) {
uint64_t fieldTag = reader->readRawVarint();
uint32_t fieldId = read_field_id(fieldTag);
uint8_t wireType = read_wire_type(fieldTag);
if (wireType == WIRE_TYPE_LENGTH_DELIMITED && args.containsSection(fieldId)) {
// We need this field, but we need to strip it to the level provided in args.
PrivacyFilter filter(fieldId, get_privacy_of_section(fieldId));
filter.addFd(new ReadbackFilterFd(args.getPrivacyPolicy(), to));
// Read this section from the reader into an FdBuffer
size_t sectionSize = reader->readRawVarint();
FdBuffer sectionData;
err = sectionData.write(reader, sectionSize);
if (err != NO_ERROR) {
ALOGW("filter_and_write_report FdBuffer.write failed (this shouldn't happen): %s",
strerror(-err));
return err;
}
// Do the filter and write.
err = filter.writeData(sectionData, bufferLevel, nullptr);
if (err != NO_ERROR) {
ALOGW("filter_and_write_report filter.writeData had an error: %s", strerror(-err));
return err;
}
} else {
// We don't need this field. Incident does not have any direct children
// other than sections. So just skip them.
write_field_or_skip(NULL, reader, fieldTag, true);
}
}
err = reader->getError();
if (err != NO_ERROR) {
ALOGW("filter_and_write_report reader had an error: %s", strerror(-err));
return err;
}
return NO_ERROR;
}
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -0,0 +1,101 @@
/*
* 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.
*/
#pragma once
#ifndef PRIVACY_BUFFER_H
#define PRIVACY_BUFFER_H
#include "Privacy.h"
#include "FdBuffer.h"
#include <android/os/IncidentReportArgs.h>
#include <android/util/ProtoOutputStream.h>
#include <stdint.h>
#include <utils/Errors.h>
namespace android {
namespace os {
namespace incidentd {
using namespace android::util;
/**
* Class to wrap a file descriptor, so callers of PrivacyFilter
* can associate additional data with each fd for their own
* purposes.
*/
class FilterFd : public RefBase {
public:
FilterFd(uint8_t privacyPolicy, int fd);
virtual ~FilterFd();
uint8_t getPrivacyPolicy() const { return mPrivacyPolicy; }
int getFd() { return mFd;}
virtual void onWriteError(status_t err) = 0;
private:
uint8_t mPrivacyPolicy;
int mFd;
};
/**
* PrivacyFilter holds the original protobuf data and strips PII-sensitive fields
* for several requests, streaming them to a set of corresponding file descriptors.
*/
class PrivacyFilter {
public:
/**
* Constructor, with the field --> privacy restrictions mapping.
*/
PrivacyFilter(int sectionId, const Privacy* restrictions);
~PrivacyFilter();
/**
* Add a target file descriptor, and the privacy policy to which
* it should be filtered.
*/
void addFd(const sp<FilterFd>& output);
/**
* Write the data, filtered according to the privacy specs, to each of the
* file descriptors. Any non-NO_ERROR return codes are fatal to the whole
* report. Individual write errors to streams are reported via the callbacks
* on the FilterFds.
*
* If maxSize is not NULL, it will be set to the maximum size buffer that
* was written (i.e. after filtering).
*
* The buffer is assumed to have already been filtered to bufferLevel.
*/
status_t writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize);
private:
int mSectionId;
const Privacy* mRestrictions;
vector<sp<FilterFd>> mOutputs;
};
status_t filter_and_write_report(int to, int from, uint8_t bufferLevel,
const IncidentReportArgs& args);
} // namespace incidentd
} // namespace os
} // namespace android
#endif // PRIVACY_BUFFER_H

View File

@@ -18,12 +18,18 @@
#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-base/properties.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>
@@ -33,308 +39,673 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
/**
* The directory where the incident reports are stored.
*/
static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/";
#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;
}
}
void poo_make_metadata(IncidentMetadata* result, const IncidentMetadata& full,
int64_t reportId, int32_t privacyPolicy, const IncidentReportArgs& 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 (args.containsSection(sectionStats.id())) {
*result->add_sections() = sectionStats;
}
}
}
// 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 (args->containsSection(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>& l, int f)
: args(a), listener(l), fd(f), err(NO_ERROR) {}
const sp<IIncidentReportStatusListener>& listener, int fd)
:args(a),
mListener(listener),
mFd(fd),
mIsStreaming(fd >= 0),
mStatus(NO_ERROR) {
}
ReportRequest::~ReportRequest() {
if (fd >= 0) {
if (mIsStreaming && mFd >= 0) {
// clean up the opened file descriptor
close(fd);
close(mFd);
}
}
bool ReportRequest::ok() { return fd >= 0 && err == NO_ERROR; }
// ================================================================================
ReportRequestSet::ReportRequestSet()
: mRequests(), mSections(), mMainFd(-1), mMainDest(-1), mMetadata(), mSectionStats() {}
ReportRequestSet::~ReportRequestSet() {}
// TODO: dedup on exact same args and fd, report the status back to listener!
void ReportRequestSet::add(const sp<ReportRequest>& request) {
mRequests.push_back(request);
mSections.merge(request->args);
mMetadata.set_request_size(mMetadata.request_size() + 1);
bool ReportRequest::ok() {
return mFd >= 0 && mStatus == NO_ERROR;
}
void ReportRequestSet::setMainFd(int fd) {
mMainFd = fd;
mMetadata.set_use_dropbox(fd > 0);
}
void ReportRequestSet::setMainDest(int dest) {
mMainDest = dest;
PrivacySpec spec = PrivacySpec::new_spec(dest);
switch (spec.dest) {
case android::os::DEST_AUTOMATIC:
mMetadata.set_dest(IncidentMetadata_Destination_AUTOMATIC);
break;
case android::os::DEST_EXPLICIT:
mMetadata.set_dest(IncidentMetadata_Destination_EXPLICIT);
break;
case android::os::DEST_LOCAL:
mMetadata.set_dest(IncidentMetadata_Destination_LOCAL);
break;
void ReportRequest::closeFd() {
if (mIsStreaming && mFd >= 0) {
close(mFd);
mFd = -1;
}
}
bool ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); }
IncidentMetadata::SectionStats* ReportRequestSet::sectionStats(int id) {
if (mSectionStats.find(id) == mSectionStats.end()) {
IncidentMetadata::SectionStats stats;
stats.set_id(id);
mSectionStats[id] = stats;
}
return &mSectionStats[id];
}
// ================================================================================
Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; };
ReportBatch::ReportBatch() {}
Reporter::Reporter(const char* directory) : batch() {
char buf[100];
ReportBatch::~ReportBatch() {}
mMaxSize = 30 * 1024 * 1024; // incident reports can take up to 30MB on disk
mMaxCount = 100;
// string ends up with '/' is a directory
String8 dir = String8(directory);
if (directory[dir.size() - 1] != '/') dir += "/";
mIncidentDirectory = dir.string();
// There can't be two at the same time because it's on one thread.
mStartTime = time(NULL);
strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
mFilename = mIncidentDirectory + buf;
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);
}
}
Reporter::~Reporter() {}
void ReportBatch::addStreamingReport(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener, int streamFd) {
mStreamingRequests.push_back(new ReportRequest(args, listener, streamFd));
}
Reporter::run_report_status_t Reporter::runReport(size_t* reportByteSize) {
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::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)) {
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;
bool needMainFd = false;
int mainFd = -1;
int mainDest = -1;
int sectionCount = 0;
HeaderSection headers;
MetadataSection metadataSection;
IncidentMetadata metadata;
int persistedPrivacyPolicy = PRIVACY_POLICY_UNSET;
std::string buildType = android::base::GetProperty("ro.build.type", "");
const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng";
// See if we need the main file
for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) {
if ((*it)->fd < 0 && mainFd < 0) {
needMainFd = true;
mainDest = (*it)->args.dest();
break;
}
}
if (needMainFd) {
// Create the directory
if (!isTest) err = create_directory(mIncidentDirectory);
if (err != NO_ERROR) {
goto DONE;
}
// If there are too many files in the directory (for whatever reason),
// delete the oldest ones until it's under the limit. Doing this first
// does mean that we can go over, so the max size is not a hard limit.
if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount);
// Open the file.
err = create_file(&mainFd);
if (err != NO_ERROR) {
goto DONE;
}
// Add to the set
batch.setMainFd(mainFd);
batch.setMainDest(mainDest);
}
(*reportByteSize) = 0;
// Tell everyone that we're starting.
for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) {
if ((*it)->listener != NULL) {
(*it)->listener->onReportStarted();
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();
}
}
// Write the incident headers
headers.Execute(&batch);
// 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);
}
});
// 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 id = (*section)->id;
const int sectionId = (*section)->id;
// If this section is too private for user builds, skip it.
if ((*section)->userdebugAndEngOnly && !isUserdebugOrEng) {
VLOG("Skipping incident report section %d '%s' because it's limited to userdebug/eng",
id, (*section)->name.string());
sectionId, (*section)->name.string());
continue;
}
if (this->batch.containsSection(id)) {
VLOG("Taking incident report section %d '%s'", id, (*section)->name.string());
for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) {
if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
(*it)->listener->onReportSectionStatus(
id, IIncidentReportStatusListener::STATUS_STARTING);
}
}
// Execute - go get the data and write it into the file descriptors.
IncidentMetadata::SectionStats* stats = batch.sectionStats(id);
int64_t startTime = uptimeMillis();
err = (*section)->Execute(&batch);
int64_t endTime = uptimeMillis();
stats->set_exec_duration_ms(endTime - startTime);
if (err != NO_ERROR) {
ALOGW("Incident section %s (%d) failed: %s. Stopping report.",
(*section)->name.string(), id, strerror(-err));
// Execute() has already recorded this status. Only update if there's new failure.
stats->set_success(false);
goto DONE;
}
(*reportByteSize) += stats->report_size_bytes();
// Notify listener of starting
for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) {
if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
(*it)->listener->onReportSectionStatus(
id, IIncidentReportStatusListener::STATUS_FINISHED);
}
}
VLOG("Finish incident report section %d '%s'", id, (*section)->name.string());
sectionCount++;
// 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:
ALOGD("Incident reporting took %d sections.", sectionCount);
// Reports the metdadata when taking the incident report.
if (!isTest) metadataSection.Execute(&batch);
// Finish up the persisted file.
if (mPersistedFile != nullptr) {
mPersistedFile->closeDataFile();
// Close the file.
if (mainFd >= 0) {
close(mainFd);
}
// Set the stored metadata
IncidentReportArgs combinedArgs;
mBatch->getCombinedPersistedArgs(&combinedArgs);
IncidentMetadata persistedMetadata;
make_metadata(&persistedMetadata, metadata, mPersistedFile->getTimestampNs(),
persistedPrivacyPolicy, &combinedArgs);
mPersistedFile->setMetadata(persistedMetadata);
// Tell everyone that we're done.
for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) {
if ((*it)->listener != NULL) {
if (err == NO_ERROR) {
(*it)->listener->onReportFinished();
} else {
(*it)->listener->onReportFailed();
}
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);
}
}
// Put the report into dropbox.
if (needMainFd && err == NO_ERROR) {
sp<DropBoxManager> dropbox = new DropBoxManager();
Status status = dropbox->addFile(String16("incident"), mFilename, 0);
ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string());
if (!status.isOk()) {
return REPORT_NEEDS_DROPBOX;
// 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));
}
});
// If the status was ok, delete the file. If not, leave it around until the next
// boot or the next checkin. If the directory gets too big older files will
// be rotated out.
if (!isTest) unlink(mFilename.c_str());
// 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();
});
}
return REPORT_FINISHED;
ALOGI("Done taking incident report err=%s", strerror(-err));
}
/**
* Create our output file and set the access permissions to -rw-rw----
*/
status_t Reporter::create_file(int* fd) {
const char* filename = mFilename.c_str();
*fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660);
if (*fd < 0) {
ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno));
return -errno;
}
// Override umask. Not super critical. If it fails go on with life.
chmod(filename, 0660);
if (chown(filename, AID_INCIDENTD, AID_INCIDENTD)) {
ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno));
status_t err = -errno;
unlink(mFilename.c_str());
return err;
}
return NO_ERROR;
}
Reporter::run_report_status_t Reporter::upload_backlog() {
DIR* dir;
struct dirent* entry;
struct stat st;
status_t err;
ALOGD("Start uploading backlogs in %s", INCIDENT_DIRECTORY);
if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) {
ALOGE("directory doesn't exist: %s", strerror(-err));
return REPORT_FINISHED;
}
if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) {
ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY);
return REPORT_NEEDS_DROPBOX;
}
sp<DropBoxManager> dropbox = new DropBoxManager();
// Enumerate, count and add up size
int count = 0;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
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;
}
String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name;
if (stat(filename.string(), &st) != 0) {
ALOGE("Unable to stat file %s", filename.string());
continue;
}
if (!S_ISREG(st.st_mode)) {
continue;
}
Status status = dropbox->addFile(String16("incident"), filename.string(), 0);
ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string());
if (!status.isOk()) {
return REPORT_NEEDS_DROPBOX;
}
// If the status was ok, delete the file. If not, leave it around until the next
// boot or the next checkin. If the directory gets too big older files will
// be rotated out.
unlink(filename.string());
count++;
}
ALOGD("Successfully uploaded %d files to Dropbox.", count);
closedir(dir);
return REPORT_FINISHED;
// 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

View File

@@ -15,103 +15,247 @@
*/
#pragma once
#ifndef REPORTER_H
#define REPORTER_H
#include "FdBuffer.h"
#include "Throttler.h"
#include "WorkDirectory.h"
#include "frameworks/base/core/proto/android/os/metadata.pb.h"
#include <android/content/ComponentName.h>
#include <android/os/IIncidentReportStatusListener.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
#include <map>
#include <string>
#include <vector>
#include <time.h>
#include "Throttler.h"
#include "frameworks/base/libs/incident/proto/android/os/metadata.pb.h"
#include <stdarg.h>
namespace android {
namespace os {
namespace incidentd {
using namespace std;
using namespace android::content;
using namespace android::os;
class Section;
// ================================================================================
struct ReportRequest : public virtual RefBase {
class ReportRequest : public virtual RefBase {
public:
IncidentReportArgs args;
sp<IIncidentReportStatusListener> listener;
int fd;
status_t err;
ReportRequest(const IncidentReportArgs& args, const sp<IIncidentReportStatusListener>& listener,
int fd);
virtual ~ReportRequest();
bool isStreaming() { return mIsStreaming; }
void setStatus(status_t err) { mStatus = err; }
status_t getStatus() const { return mStatus; }
bool ok(); // returns true if the request is ok for write.
bool containsSection(int sectionId) const { return args.containsSection(sectionId); }
sp<IIncidentReportStatusListener> getListener() { return mListener; }
int getFd() { return mFd; }
int setPersistedFd(int fd);
void closeFd();
private:
sp<IIncidentReportStatusListener> mListener;
int mFd;
bool mIsStreaming;
status_t mStatus;
};
// ================================================================================
class ReportRequestSet {
class ReportBatch : public virtual RefBase {
public:
ReportRequestSet();
~ReportRequestSet();
ReportBatch();
virtual ~ReportBatch();
void add(const sp<ReportRequest>& request);
void setMainFd(int fd);
void setMainDest(int dest);
// TODO: Should there be some kind of listener associated with the
// component? Could be good for getting status updates e.g. in the ui,
// as it progresses. But that's out of scope for now.
typedef vector<sp<ReportRequest>>::iterator iterator;
/**
* Schedule a report for the "main" report, where it will be delivered to
* the uploaders and/or dropbox.
*/
void addPersistedReport(const IncidentReportArgs& args);
iterator begin() { return mRequests.begin(); }
iterator end() { return mRequests.end(); }
/**
* Adds a ReportRequest to the queue for one that has a listener an and fd
*/
void addStreamingReport(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener, int streamFd);
int mainFd() { return mMainFd; }
int mainDest() { return mMainDest; }
IncidentMetadata& metadata() { return mMetadata; }
map<int, IncidentMetadata::SectionStats>& allSectionStats() { return mSectionStats; }
/**
* Returns whether both queues are empty.
*/
bool empty() const;
/**
* Returns whether there are any persisted records.
*/
bool hasPersistedReports() const { return mPersistedRequests.size() > 0; }
/**
* Return the persisted request for the given component, or nullptr.
*/
sp<ReportRequest> getPersistedRequest(const ComponentName& component);
/**
* Call func(request) for each Request.
*/
void forEachPersistedRequest(const function<void (const sp<ReportRequest>&)>& func);
/**
* Call func(request) for each Request.
*/
void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
/**
* Call func(request) for each file descriptor that has
*/
void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
/**
* Call func(listener) for every listener in this batch.
*/
void forEachListener(const function<void (const sp<IIncidentReportStatusListener>&)>& func);
/**
* Call func(listener) for every listener in this batch that requests
* sectionId.
*/
void forEachListener(int sectionId,
const function<void (const sp<IIncidentReportStatusListener>&)>& func);
/**
* Get an IncidentReportArgs that represents the combined args for the
* persisted requests.
*/
void getCombinedPersistedArgs(IncidentReportArgs* results);
/**
* Return whether any of the requests contain the section.
*/
bool containsSection(int id);
IncidentMetadata::SectionStats* sectionStats(int id);
/**
* Remove all of the broadcast (persisted) requests.
*/
void clearPersistedRequests();
/**
* Get the requests that have encountered errors.
*/
void getFailedRequests(vector<sp<ReportRequest>>* requests);
/**
* Remove the request from whichever list it's in.
*/
void removeRequest(const sp<ReportRequest>& request);
private:
vector<sp<ReportRequest>> mRequests;
IncidentReportArgs mSections;
int mMainFd;
int mMainDest;
map<ComponentName, sp<ReportRequest>> mPersistedRequests;
vector<sp<ReportRequest>> mStreamingRequests;
};
IncidentMetadata mMetadata;
map<int, IncidentMetadata::SectionStats> mSectionStats;
// ================================================================================
class ReportWriter {
public:
ReportWriter(const sp<ReportBatch>& batch);
~ReportWriter();
void setPersistedFile(sp<ReportFile> file);
void setMaxPersistedPrivacyPolicy(uint8_t privacyPolicy);
void startSection(int sectionId);
void endSection(IncidentMetadata::SectionStats* sectionStats);
void setSectionStats(const FdBuffer& buffer);
void warning(const Section* section, status_t err, const char* format, ...);
void error(const Section* section, status_t err, const char* format, ...);
status_t writeSection(const FdBuffer& buffer);
private:
// Data about all requests
sp<ReportBatch> mBatch;
/**
* The file on disk where we will store the persisted file.
*/
sp<ReportFile> mPersistedFile;
/**
* The least restricted privacy policy of all of the perstited
* requests. We pre-filter to that to save disk space.
*/
uint8_t mMaxPersistedPrivacyPolicy;
/**
* The current section that is being written.
*/
int mCurrentSectionId;
/**
* The time that that the current section was started.
*/
int64_t mSectionStartTimeMs;
/**
* The last section that setSectionStats was called for, so if someone misses
* it we can log that.
*/
int mSectionStatsCalledForSectionId;
/*
* Fields for IncidentMetadata.SectionStats. Set by setSectionStats. Accessed by
* getSectionStats.
*/
int32_t mDumpSizeBytes;
int64_t mDumpDurationMs;
bool mSectionTimedOut;
bool mSectionTruncated;
bool mSectionBufferSuccess;
bool mHadError;
string mSectionErrors;
size_t mMaxSectionDataFilteredSize;
void vflog(const Section* section, status_t err, int level, const char* levelText,
const char* format, va_list args);
};
// ================================================================================
class Reporter : public virtual RefBase {
public:
enum run_report_status_t { REPORT_FINISHED = 0, REPORT_NEEDS_DROPBOX = 1 };
Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
ReportRequestSet batch;
Reporter(); // PROD must use this constructor.
explicit Reporter(const char* directory); // For testing purpose only.
virtual ~Reporter();
// Run the report as described in the batch and args parameters.
run_report_status_t runReport(size_t* reportByteSize);
static run_report_status_t upload_backlog();
void runReport(size_t* reportByteSize);
private:
String8 mIncidentDirectory;
sp<WorkDirectory> mWorkDirectory;
ReportWriter mWriter;
sp<ReportBatch> mBatch;
sp<ReportFile> mPersistedFile;
string mFilename;
off_t mMaxSize;
size_t mMaxCount;
time_t mStartTime;
status_t create_file(int* fd);
bool isTest = true; // default to true for testing
void cancel_and_remove_failed_requests();
};
} // namespace incidentd
} // namespace os
} // namespace android
#endif // REPORTER_H

View File

@@ -27,6 +27,7 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android/util/protobuf.h>
#include <android/util/ProtoOutputStream.h>
#include <binder/IServiceManager.h>
#include <debuggerd/client.h>
#include <dumputils/dump_utils.h>
@@ -37,7 +38,6 @@
#include "FdBuffer.h"
#include "Privacy.h"
#include "PrivacyBuffer.h"
#include "frameworks/base/core/proto/android/os/backtrace.proto.h"
#include "frameworks/base/core/proto/android/os/data.proto.h"
#include "frameworks/base/core/proto/android/util/log.proto.h"
@@ -51,7 +51,6 @@ using namespace android::base;
using namespace android::util;
// special section ids
const int FIELD_ID_INCIDENT_HEADER = 1;
const int FIELD_ID_INCIDENT_METADATA = 2;
// incident section parameters
@@ -63,114 +62,6 @@ static pid_t fork_execute_incident_helper(const int id, Fpipe* p2cPipe, Fpipe* c
return fork_execute_cmd(const_cast<char**>(ihArgs), p2cPipe, c2pPipe);
}
// ================================================================================
static status_t write_section_header(int fd, int sectionId, size_t size) {
uint8_t buf[20];
uint8_t* p = write_length_delimited_tag_header(buf, sectionId, size);
return WriteFully(fd, buf, p - buf) ? NO_ERROR : -errno;
}
static void write_section_stats(IncidentMetadata::SectionStats* stats, const FdBuffer& buffer) {
stats->set_dump_size_bytes(buffer.data().size());
stats->set_dump_duration_ms(buffer.durationMs());
stats->set_timed_out(buffer.timedOut());
stats->set_is_truncated(buffer.truncated());
stats->set_success(!buffer.timedOut() && !buffer.truncated());
}
// Reads data from FdBuffer and writes it to the requests file descriptor.
static status_t write_report_requests(const int id, const FdBuffer& buffer,
ReportRequestSet* requests) {
status_t err = -EBADF;
EncodedBuffer::iterator data = buffer.data();
PrivacyBuffer privacyBuffer(get_privacy_of_section(id), data);
IncidentMetadata::SectionStats* stats = requests->sectionStats(id);
int nPassed = 0;
// The streaming ones, group requests by spec in order to save unnecessary strip operations
map<PrivacySpec, vector<sp<ReportRequest>>> requestsBySpec;
for (auto it = requests->begin(); it != requests->end(); it++) {
sp<ReportRequest> request = *it;
if (!request->ok() || !request->args.containsSection(id)) {
continue; // skip invalid request
}
PrivacySpec spec = PrivacySpec::new_spec(request->args.dest());
requestsBySpec[spec].push_back(request);
}
for (auto mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) {
PrivacySpec spec = mit->first;
err = privacyBuffer.strip(spec);
if (err != NO_ERROR) {
// Privacy Buffer is corrupted, probably due to an ill-formatted proto. This is a
// non-fatal error. The whole report is still valid. So just log the failure.
ALOGW("Failed to strip section %d with spec %d: %s",
id, spec.dest, strerror(-err));
stats->set_success(false);
stats->set_error_msg("Failed to strip section: privacy buffer corrupted, probably "
"due to an ill-formatted proto");
nPassed++;
continue;
}
if (privacyBuffer.size() == 0) continue;
for (auto it = mit->second.begin(); it != mit->second.end(); it++) {
sp<ReportRequest> request = *it;
err = write_section_header(request->fd, id, privacyBuffer.size());
if (err != NO_ERROR) {
request->err = err;
continue;
}
err = privacyBuffer.flush(request->fd);
if (err != NO_ERROR) {
request->err = err;
continue;
}
nPassed++;
VLOG("Section %d flushed %zu bytes to fd %d with spec %d", id, privacyBuffer.size(),
request->fd, spec.dest);
}
privacyBuffer.clear();
}
// The dropbox file
if (requests->mainFd() >= 0) {
PrivacySpec spec = PrivacySpec::new_spec(requests->mainDest());
err = privacyBuffer.strip(spec);
if (err != NO_ERROR) {
ALOGW("Failed to strip section %d with spec %d for dropbox: %s",
id, spec.dest, strerror(-err));
stats->set_success(false);
stats->set_error_msg("Failed to strip section: privacy buffer corrupted, probably "
"due to an ill-formatted proto");
nPassed++;
goto DONE;
}
if (privacyBuffer.size() == 0) goto DONE;
err = write_section_header(requests->mainFd(), id, privacyBuffer.size());
if (err != NO_ERROR) {
requests->setMainFd(-1);
goto DONE;
}
err = privacyBuffer.flush(requests->mainFd());
if (err != NO_ERROR) {
requests->setMainFd(-1);
goto DONE;
}
nPassed++;
VLOG("Section %d flushed %zu bytes to dropbox %d with spec %d", id, privacyBuffer.size(),
requests->mainFd(), spec.dest);
// Reports bytes of the section uploaded via dropbox after filtering.
requests->sectionStats(id)->set_report_size_bytes(privacyBuffer.size());
}
DONE:
// only returns error if there is no fd to write to.
return nPassed > 0 ? NO_ERROR : err;
}
// ================================================================================
Section::Section(int i, int64_t timeoutMs, bool userdebugAndEngOnly)
: id(i),
@@ -179,86 +70,6 @@ Section::Section(int i, int64_t timeoutMs, bool userdebugAndEngOnly)
Section::~Section() {}
// ================================================================================
HeaderSection::HeaderSection() : Section(FIELD_ID_INCIDENT_HEADER, 0) {}
HeaderSection::~HeaderSection() {}
status_t HeaderSection::Execute(ReportRequestSet* requests) const {
for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) {
const sp<ReportRequest> request = *it;
const vector<vector<uint8_t>>& headers = request->args.headers();
for (vector<vector<uint8_t>>::const_iterator buf = headers.begin(); buf != headers.end();
buf++) {
if (buf->empty()) continue;
// So the idea is only requests with negative fd are written to dropbox file.
int fd = request->fd >= 0 ? request->fd : requests->mainFd();
write_section_header(fd, id, buf->size());
WriteFully(fd, (uint8_t const*)buf->data(), buf->size());
// If there was an error now, there will be an error later and we will remove
// it from the list then.
}
}
return NO_ERROR;
}
// ================================================================================
MetadataSection::MetadataSection() : Section(FIELD_ID_INCIDENT_METADATA, 0) {}
MetadataSection::~MetadataSection() {}
status_t MetadataSection::Execute(ReportRequestSet* requests) const {
ProtoOutputStream proto;
IncidentMetadata metadata = requests->metadata();
proto.write(FIELD_TYPE_ENUM | IncidentMetadata::kDestFieldNumber, metadata.dest());
proto.write(FIELD_TYPE_INT32 | IncidentMetadata::kRequestSizeFieldNumber,
metadata.request_size());
proto.write(FIELD_TYPE_BOOL | IncidentMetadata::kUseDropboxFieldNumber, metadata.use_dropbox());
for (auto iter = requests->allSectionStats().begin(); iter != requests->allSectionStats().end();
iter++) {
IncidentMetadata::SectionStats stats = iter->second;
uint64_t token = proto.start(FIELD_TYPE_MESSAGE | IncidentMetadata::kSectionsFieldNumber);
proto.write(FIELD_TYPE_INT32 | IncidentMetadata::SectionStats::kIdFieldNumber, stats.id());
proto.write(FIELD_TYPE_BOOL | IncidentMetadata::SectionStats::kSuccessFieldNumber,
stats.success());
proto.write(FIELD_TYPE_INT32 | IncidentMetadata::SectionStats::kReportSizeBytesFieldNumber,
stats.report_size_bytes());
proto.write(FIELD_TYPE_INT64 | IncidentMetadata::SectionStats::kExecDurationMsFieldNumber,
stats.exec_duration_ms());
proto.write(FIELD_TYPE_INT32 | IncidentMetadata::SectionStats::kDumpSizeBytesFieldNumber,
stats.dump_size_bytes());
proto.write(FIELD_TYPE_INT64 | IncidentMetadata::SectionStats::kDumpDurationMsFieldNumber,
stats.dump_duration_ms());
proto.write(FIELD_TYPE_BOOL | IncidentMetadata::SectionStats::kTimedOutFieldNumber,
stats.timed_out());
proto.write(FIELD_TYPE_BOOL | IncidentMetadata::SectionStats::kIsTruncatedFieldNumber,
stats.is_truncated());
proto.write(FIELD_TYPE_STRING | IncidentMetadata::SectionStats::kErrorMsgFieldNumber,
stats.error_msg());
proto.end(token);
}
for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) {
const sp<ReportRequest> request = *it;
if (request->fd < 0 || request->err != NO_ERROR) {
continue;
}
write_section_header(request->fd, id, proto.size());
if (!proto.flush(request->fd)) {
ALOGW("Failed to write metadata to fd %d", request->fd);
// we don't fail if we can't write to a single request's fd.
}
}
if (requests->mainFd() >= 0) {
write_section_header(requests->mainFd(), id, proto.size());
if (!proto.flush(requests->mainFd())) {
ALOGW("Failed to write metadata to dropbox fd %d", requests->mainFd());
return -1;
}
}
return NO_ERROR;
}
// ================================================================================
static inline bool isSysfs(const char* filename) { return strncmp(filename, "/sys/", 5) == 0; }
@@ -271,7 +82,7 @@ FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
FileSection::~FileSection() {}
status_t FileSection::Execute(ReportRequestSet* requests) const {
status_t FileSection::Execute(ReportWriter* writer) const {
// read from mFilename first, make sure the file is available
// add O_CLOEXEC to make sure it is closed when exec incident helper
unique_fd fd(open(mFilename, O_RDONLY | O_CLOEXEC));
@@ -301,7 +112,7 @@ status_t FileSection::Execute(ReportRequestSet* requests) const {
status_t readStatus = buffer.readProcessedDataInStream(fd.get(), std::move(p2cPipe.writeFd()),
std::move(c2pPipe.readFd()),
this->timeoutMs, mIsSysfs);
write_section_stats(requests->sectionStats(this->id), buffer);
writer->setSectionStats(buffer);
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s",
this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
@@ -315,7 +126,7 @@ status_t FileSection::Execute(ReportRequestSet* requests) const {
return ihStatus;
}
return write_report_requests(this->id, buffer, requests);
return writer->writeSection(buffer);
}
// ================================================================================
GZipSection::GZipSection(int id, const char* filename, ...) : Section(id) {
@@ -332,7 +143,7 @@ GZipSection::GZipSection(int id, const char* filename, ...) : Section(id) {
GZipSection::~GZipSection() { free(mFilenames); }
status_t GZipSection::Execute(ReportRequestSet* requests) const {
status_t GZipSection::Execute(ReportWriter* writer) const {
// Reads the files in order, use the first available one.
int index = 0;
unique_fd fd;
@@ -366,7 +177,7 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const {
// construct Fdbuffer to output GZippedfileProto, the reason to do this instead of using
// ProtoOutputStream is to avoid allocation of another buffer inside ProtoOutputStream.
EncodedBuffer* internalBuffer = buffer.getInternalBuffer();
sp<EncodedBuffer> internalBuffer = buffer.data();
internalBuffer->writeHeader((uint32_t)GZippedFileProto::FILENAME, WIRE_TYPE_LENGTH_DELIMITED);
size_t fileLen = strlen(mFilenames[index]);
internalBuffer->writeRawVarint32(fileLen);
@@ -383,7 +194,7 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const {
status_t readStatus = buffer.readProcessedDataInStream(
fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs,
isSysfs(mFilenames[index]));
write_section_stats(requests->sectionStats(this->id), buffer);
writer->setSectionStats(buffer);
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("[%s] failed to read data from gzip: %s, timedout: %s", this->name.string(),
strerror(-readStatus), buffer.timedOut() ? "true" : "false");
@@ -402,7 +213,7 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const {
internalBuffer->writeRawVarint32(dataSize);
internalBuffer->copy(dataBeginAt, dataSize);
return write_report_requests(this->id, buffer, requests);
return writer->writeSection(buffer);
}
// ================================================================================
@@ -458,13 +269,12 @@ static void* worker_thread_func(void* cookie) {
return NULL;
}
status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const {
status_t WorkerThreadSection::Execute(ReportWriter* writer) const {
status_t err = NO_ERROR;
pthread_t thread;
pthread_attr_t attr;
bool workerDone = false;
FdBuffer buffer;
IncidentMetadata::SectionStats* stats = requests->sectionStats(this->id);
// Data shared between this thread and the worker thread.
sp<WorkerThreadData> data = new WorkerThreadData(this);
@@ -474,10 +284,6 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const {
return -errno;
}
// The worker thread needs a reference and we can't let the count go to zero
// if that thread is slow to start.
data->incStrong(this);
// Create the thread
err = pthread_attr_init(&attr);
if (err != 0) {
@@ -489,12 +295,17 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const {
pthread_attr_destroy(&attr);
return -err;
}
// The worker thread needs a reference and we can't let the count go to zero
// if that thread is slow to start.
data->incStrong(this);
err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get());
pthread_attr_destroy(&attr);
if (err != 0) {
pthread_attr_destroy(&attr);
data->decStrong(this);
return -err;
}
pthread_attr_destroy(&attr);
// Loop reading until either the timeout or the worker side is done (i.e. eof).
err = buffer.read(data->pipe.readFd().get(), this->timeoutMs);
@@ -517,18 +328,17 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const {
workerDone = data->workerDone;
}
write_section_stats(stats, buffer);
writer->setSectionStats(buffer);
if (err != NO_ERROR) {
char errMsg[128];
snprintf(errMsg, 128, "[%s] failed with error '%s'",
this->name.string(), strerror(-err));
stats->set_success(false);
stats->set_error_msg(errMsg);
writer->error(this, err, "WorkerThreadSection failed.");
return NO_ERROR;
}
if (buffer.truncated()) {
ALOGW("[%s] too large, truncating", this->name.string());
// Do not write a truncated section. It won't pass through the PrivacyBuffer.
// Do not write a truncated section. It won't pass through the PrivacyFilter.
return NO_ERROR;
}
if (!workerDone || buffer.timedOut()) {
@@ -537,7 +347,7 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const {
}
// Write the data that was collected
return write_report_requests(this->id, buffer, requests);
return writer->writeSection(buffer);
}
// ================================================================================
@@ -568,7 +378,7 @@ CommandSection::CommandSection(int id, const char* command, ...) : Section(id) {
CommandSection::~CommandSection() { free(mCommand); }
status_t CommandSection::Execute(ReportRequestSet* requests) const {
status_t CommandSection::Execute(ReportWriter* writer) const {
FdBuffer buffer;
Fpipe cmdPipe;
Fpipe ihPipe;
@@ -591,7 +401,7 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const {
cmdPipe.writeFd().reset();
status_t readStatus = buffer.read(ihPipe.readFd().get(), this->timeoutMs);
write_section_stats(requests->sectionStats(this->id), buffer);
writer->setSectionStats(buffer);
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s",
this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
@@ -605,13 +415,13 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const {
status_t cmdStatus = wait_child(cmdPid);
status_t ihStatus = wait_child(ihPid);
if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
ALOGW("[%s] abnormal child processes, return status: command: %s, incident "
"helper: %s",
ALOGW("[%s] abnormal child processes, return status: command: %s, incident helper: %s",
this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
return cmdStatus != NO_ERROR ? cmdStatus : ihStatus;
// Not a fatal error.
return NO_ERROR;
}
return write_report_requests(this->id, buffer, requests);
return writer->writeSection(buffer);
}
// ================================================================================
@@ -842,7 +652,8 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid);
std::string exe;
if (!android::base::Readlink(link_name, &exe)) {
ALOGE("Can't read '%s': %s\n", link_name.c_str(), strerror(errno));
ALOGE("Section %s: Can't read '%s': %s\n", name.string(),
link_name.c_str(), strerror(errno));
continue;
}
@@ -913,10 +724,10 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
}
auto dump = std::make_unique<char[]>(buffer.size());
auto iterator = buffer.data();
sp<ProtoReader> reader = buffer.data()->read();
int i = 0;
while (iterator.hasNext()) {
dump[i] = iterator.next();
while (reader->hasNext()) {
dump[i] = reader->next();
i++;
}
uint64_t token = proto.start(android::os::BackTraceProto::TRACES);

View File

@@ -46,29 +46,7 @@ public:
Section(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS, bool userdebugAndEngOnly = false);
virtual ~Section();
virtual status_t Execute(ReportRequestSet* requests) const = 0;
};
/**
* Section that generates incident headers.
*/
class HeaderSection : public Section {
public:
HeaderSection();
virtual ~HeaderSection();
virtual status_t Execute(ReportRequestSet* requests) const;
};
/**
* Section that generates incident metadata.
*/
class MetadataSection : public Section {
public:
MetadataSection();
virtual ~MetadataSection();
virtual status_t Execute(ReportRequestSet* requests) const;
virtual status_t Execute(ReportWriter* writer) const = 0;
};
/**
@@ -80,7 +58,7 @@ public:
int64_t timeoutMs = 5000 /* 5 seconds */);
virtual ~FileSection();
virtual status_t Execute(ReportRequestSet* requests) const;
virtual status_t Execute(ReportWriter* writer) const;
private:
const char* mFilename;
@@ -95,7 +73,7 @@ public:
GZipSection(int id, const char* filename, ...);
virtual ~GZipSection();
virtual status_t Execute(ReportRequestSet* requests) const;
virtual status_t Execute(ReportWriter* writer) const;
private:
// It looks up the content from multiple files and stops when the first one is available.
@@ -111,7 +89,7 @@ public:
bool userdebugAndEngOnly = false);
virtual ~WorkerThreadSection();
virtual status_t Execute(ReportRequestSet* requests) const;
virtual status_t Execute(ReportWriter* writer) const;
virtual status_t BlockingCall(int pipeWriteFd) const = 0;
};
@@ -127,7 +105,7 @@ public:
virtual ~CommandSection();
virtual status_t Execute(ReportRequestSet* requests) const;
virtual status_t Execute(ReportWriter* writer) const;
private:
const char** mCommand;

View File

@@ -0,0 +1,844 @@
/*
* 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 "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");
/**
* 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* fd, const IncidentReportArgs& args) {
// Open data file.
int dataFd = open(mDataFileName.c_str(), O_RDONLY | O_CLOEXEC);
if (dataFd < 0) {
return -errno;
}
// Check that the size on disk is what we thought we wrote.
struct stat st;
if (fstat(dataFd, &st) != 0) {
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);
return BAD_VALUE;
}
// Create pipe
int fds[2];
if (pipe(fds) != 0) {
ALOGW("Error opening pipe to filter incident report: %s", getDataFileName().c_str());
return -errno;
}
*fd = fds[0];
int writeFd = fds[1];
// Spawn off a thread to do the filtering and writing
thread th([this, dataFd, writeFd, args]() {
ALOGD("worker thread started dataFd=%d writeFd=%d", dataFd, writeFd);
status_t err;
err = filter_and_write_report(writeFd, dataFd, mEnvelope.privacy_policy(), args);
close(writeFd);
if (err != NO_ERROR) {
ALOGW("Error writing incident report '%s' to dropbox: %s", getDataFileName().c_str(),
strerror(-err));
// If there's an error here, there will also be an error returned from
// addFile, so we'll use that error to reschedule the send_to_dropbox.
// If the file is corrupted, we will put some logs in logcat, but won't
// actually return an error.
return;
}
});
// Better would be to join this thread after write is back, but there is no
// timeout parameter for that, which means we can't clean up if system server
// is stuck. Better is to leak the thread, which will eventually clean itself
// up after system server eventually dies, which it probably will.
th.detach();
// If the thread fails to start, we should return an error, but the thread
// class doesn't give us a good way to determine that. Just pretend everything
// is ok.
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, &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 = (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, &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());
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

View File

@@ -0,0 +1,276 @@
/*
* 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.
*/
#pragma once
#include <android/content/ComponentName.h>
#include <android/os/IncidentReportArgs.h>
#include <frameworks/base/core/proto/android/os/metadata.pb.h>
#include <frameworks/base/cmds/incidentd/src/report_file.pb.h>
#include <utils/RefBase.h>
#include <mutex>
#include <string>
namespace android {
namespace os {
namespace incidentd {
using android::content::ComponentName;
using android::os::IncidentReportArgs;
using namespace std;
extern const ComponentName DROPBOX_SENTINEL;
class WorkDirectory;
struct WorkDirectoryEntry;
void get_args_from_report(IncidentReportArgs* out, const ReportFileProto_Report& report);
/**
* A ReportFile object is backed by two files.
* - A metadata file, which contains a
*/
class ReportFile : public virtual RefBase {
public:
ReportFile(const sp<WorkDirectory>& workDirectory, int64_t timestampNs,
const string& envelopeFileName, const string& dataFileName);
virtual ~ReportFile();
/**
* Get the timestamp from when this file was added.
*/
int64_t getTimestampNs() const;
/**
* Add an additional report to this ReportFile.
*/
void addReport(const IncidentReportArgs& args);
/**
* Remove the reports for pkg/cls from this file.
*/
void removeReport(const string& pkg, const string& cls);
/**
* Remove all reports for pkg from this file.
*/
void removeReports(const string& pkg);
/**
* Set the metadata for this incident report.
*/
void setMetadata(const IncidentMetadata& metadata);
/*
* Mark this incident report as finished and ready for broadcast.
*/
void markCompleted();
/*
* Mark this incident report as finished and ready for broadcast.
*/
status_t markApproved(const string& pkg, const string& cls);
/**
* Set the privacy policy that is being used to pre-filter the data
* going to disk.
*/
void setMaxPersistedPrivacyPolicy(int persistedPrivacyPolicy);
/**
* Save the metadata (envelope) information about the incident
* report. Must be called after addReport, setMetadata markCompleted
* markApproved to save those changes to disk.
*/
status_t saveEnvelope();
/**
* Like saveEnvelope() but will not clean up if there is an error.
*/
status_t trySaveEnvelope();
/**
* Read the envelope information from disk. If there was an error, the envelope and
* data file will be removed. If the proto can't be loaded, the whole file is deleted.
*/
status_t loadEnvelope();
/**
* Like loadEnvelope() but will not clean up if there is an error.
*/
status_t tryLoadEnvelope();
/**
* Get the envelope information.
*/
const ReportFileProto& getEnvelope();
/**
* Open the file that will contain the contents of the incident report. Call
* close() or closeDataFile() on the result of getDataFileFd() when you're done.
* This is not done automatically in the desctructor. If there is an error, returns
* it and you will not get an fd.
*/
status_t startWritingDataFile();
/**
* Close the data file.
*/
void closeDataFile();
/**
* Spawn a thread to start writing and filtering data to a pipe, the read end of which
* will be returned in fd. This thread will be detached and run until somebody finishes
* reading from the fd or closes it. If there is an error, returns it and you will not
* get an fd.
*
* Use the privacy and section configuraiton from the args parameter.
*/
status_t startFilteringData(int* fd, const IncidentReportArgs& args);
/**
* Get the name of the data file on disk.
*/
string getDataFileName() const;
/**
* Get the name of the envelope file on disk.
*/
string getEnvelopeFileName() const;
/**
* Return the file descriptor for the data file, or -1 if it is not
* currently open.
*/
int getDataFileFd();
/**
* Record that there was an error writing to the data file.
*/
void setWriteError(status_t err);
/**
* Get whether there was previously an error writing to the data file.
*/
status_t getWriteError();
/**
* Get the unique identifier for this file.
*/
string getId();
private:
sp<WorkDirectory> mWorkDirectory;
int64_t mTimestampNs;
string mEnvelopeFileName;
string mDataFileName;
ReportFileProto mEnvelope;
int mDataFd;
status_t mError;
status_t save_envelope_impl(bool cleanup);
status_t load_envelope_impl(bool cleanup);
};
/**
* For directory cleanup to work, WorkDirectory must be kept
* alive for the duration of all of the ReportFiles. In the real
* incidentd, WorkDirectory is a singleton. In tests, it may
* have a shorter duration.
*/
class WorkDirectory : public virtual RefBase {
public:
/**
* Save files to the default location.
*/
WorkDirectory();
/**
* Save files to a specific location (primarily for testing).
*/
WorkDirectory(const string& dir, int maxFileCount, long maxDiskUsageBytes);
/**
* Return a new report file. Creating this object won't fail, but
* subsequent actions on the file could, if the disk is full, permissions
* aren't set correctly, etc.
*/
sp<ReportFile> createReportFile();
/**
* Get the reports that are saved on-disk, with the time after (>) than the
* given timestamp. Pass 0 to start at the beginning. These files
* will be sorted by timestamp. The envelope will not have been loaded.
*/
status_t getReports(vector<sp<ReportFile>>* files, int64_t after);
/**
* Get the report with the given package, class and id. Returns nullptr if
* that can't be found. The envelope will have been loaded. Returns the
* original IncidentReportArgs in *args if args != nullptr.
*/
sp<ReportFile> getReport(const string& pkg, const string& cls, const string& id,
IncidentReportArgs* args);
/**
* Returns whether there are more reports after the given timestamp.
*/
bool hasMore(int64_t after);
/**
* Confirm that a particular broadcast receiver has received the data. When all
* broadcast receivers for a particular report file have finished, the envelope
* and data files will be deleted.
*/
void commit(const sp<ReportFile>& report, const string& pkg, const string& cls);
/**
* Commit all reports the given package.
*/
void commitAll(const string& pkg);
/**
* Remove the envelope and data file from disk, regardless of whether there are
* more pending readers or broadcasts, for example in response to an error.
*/
void remove(const sp<ReportFile>& report);
private:
string mDirectory;
int mMaxFileCount;
long mMaxDiskUsageBytes;
// Held while creating or removing envelope files, which are the file that keeps
// the directory consistent.
mutex mLock;
int64_t make_timestamp_ns_locked();
bool file_exists_locked(int64_t timestampNs);
off_t get_directory_contents_locked(map<string,WorkDirectoryEntry>* files, int64_t after);
void clean_directory_locked();
void delete_files_for_report_if_necessary(const sp<ReportFile>& report);
string make_filename(int64_t timestampNs, const string& extension);
};
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -68,7 +68,6 @@ pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output) {
// fork used in multithreaded environment, avoid adding unnecessary code in child process
pid_t pid = fork();
if (pid == 0) {
VLOG("[In child]cmd %s", argv[0]);
if (input != NULL && (TEMP_FAILURE_RETRY(dup2(input->readFd().get(), STDIN_FILENO)) < 0 ||
!input->close())) {
ALOGW("Failed to dup2 stdin.");
@@ -158,4 +157,4 @@ status_t wait_child(pid_t pid) {
} // namespace incidentd
} // namespace os
} // namespace android
} // namespace android

View File

@@ -78,6 +78,8 @@ uint64_t Nanotime();
status_t kill_child(pid_t pid);
status_t wait_child(pid_t pid);
status_t start_detached_thread(const function<void ()>& func);
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -0,0 +1,84 @@
/*
* 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.
*/
#include "proto_util.h"
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <android/util/protobuf.h>
#include <android/util/ProtoOutputStream.h>
#include <android-base/file.h>
namespace android {
namespace os {
namespace incidentd {
using namespace android::base;
using namespace android::util;
using google::protobuf::io::FileOutputStream;
// special section ids
const int FIELD_ID_INCIDENT_HEADER = 1;
status_t write_header_section(int fd, const vector<uint8_t>& buf) {
status_t err;
const size_t bufSize = buf.size();
if (buf.empty()) {
return NO_ERROR;
}
err = write_section_header(fd, FIELD_ID_INCIDENT_HEADER, bufSize);
if (err != NO_ERROR) {
return err;
}
err = WriteFully(fd, (uint8_t const*)buf.data(), bufSize);
if (err != NO_ERROR) {
return err;
}
return NO_ERROR;
}
status_t write_section_header(int fd, int sectionId, size_t size) {
uint8_t buf[20];
uint8_t* p = write_length_delimited_tag_header(buf, sectionId, size);
return WriteFully(fd, buf, p - buf) ? NO_ERROR : -errno;
}
status_t write_section(int fd, int sectionId, const MessageLite& message) {
status_t err;
err = write_section_header(fd, sectionId, message.ByteSize());
if (err != NO_ERROR) {
return err;
}
FileOutputStream stream(fd);
if (!message.SerializeToZeroCopyStream(&stream)) {
return stream.GetErrno();
} else {
return NO_ERROR;
}
}
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -0,0 +1,52 @@
/*
* 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.
*/
#pragma once
#include <utils/Errors.h>
#include <google/protobuf/message_lite.h>
#include <vector>
namespace android {
namespace os {
namespace incidentd {
using std::vector;
using google::protobuf::MessageLite;
/**
* Write the IncidentHeaderProto section
*/
status_t write_header_section(int fd, const vector<uint8_t>& buf);
/**
* Write the prologue for a section in the incident report
* (This is the proto length-prefixed field format).
*/
status_t write_section_header(int fd, int sectionId, size_t size);
/**
* Write the given protobuf object as a section.
*/
status_t write_section(int fd, int sectionId, const MessageLite& message);
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -33,63 +33,6 @@ namespace android {
namespace os {
namespace incidentd {
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;
}
static bool stat_mtime_cmp(const std::pair<String8, struct stat>& a,
const std::pair<String8, struct stat>& b) {
return a.second.st_mtime < b.second.st_mtime;
@@ -153,4 +96,4 @@ void clean_directory(const char* directory, off_t maxSize, size_t maxCount) {
} // namespace incidentd
} // namespace os
} // namespace android
} // namespace android

View File

@@ -0,0 +1,107 @@
/*
* 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.
*/
syntax = "proto2";
package android.os.incidentd;
import "frameworks/base/core/proto/android/os/metadata.proto";
message ReportFileProto {
/**
* Metadata about each of the calls to reportIncident that
* initiated the incident report.
*/
message Report {
/**
* Package name for broadcast receiver to be told when
* the report is complete.
*/
optional string pkg = 1;
/**
* Class name for broadcast receiver to be told when
* the report is complete.
*/
optional string cls = 2;
/**
* Privacy policy at which this report should be shared.
*/
optional uint32 privacy_policy = 4;
/**
* Whether all available sections should be returned.
*/
optional bool all_sections = 5;
/**
* If all_sections is not true, then this is the
* list of sections that were requested.
*/
repeated int32 section = 6;
/**
* Flattened IncidentHeaderProto that was passed with this
* request.
*/
repeated bytes header = 7;
/**
* Whether the user has approved this report to be shared with
* the given client.
*/
optional bool share_approved = 8;
}
/**
* Metadata section recorded while the incident report
* was taken.
*/
optional android.os.IncidentMetadata metadata = 1;
/**
* Report data structures for the incident reports.
*/
repeated Report report = 2;
/**
* The file name, relative to the work directory where
* the data file is stored. The content of the data file
* is an android.os.IncidentProto, without the metadata
* or header sections.
*/
optional string data_file = 3;
/**
* The privacy policy to which the file is already filtered.
*/
optional uint32 privacy_policy = 4;
/**
* How big the data file is expected to be. If the size
* recorded here and the size on disk mismatch, then we
* know there was an error.
*/
optional int64 data_file_size = 5;
/**
* Whether this report has been finished, and is now
* ready for broadcast / dropbox / etc.
*/
optional bool completed = 6;
}

View File

@@ -50,9 +50,9 @@ public:
void AssertBufferContent(const char* expected) {
int i = 0;
EncodedBuffer::iterator it = buffer.data();
while (it.hasNext()) {
ASSERT_EQ(it.next(), expected[i++]);
sp<ProtoReader> reader = buffer.data()->read();
while (reader->hasNext()) {
ASSERT_EQ(reader->next(), expected[i++]);
}
EXPECT_EQ(expected[i], '\0');
}
@@ -92,8 +92,8 @@ TEST_F(FdBufferTest, ReadAndWrite) {
}
TEST_F(FdBufferTest, IterateEmpty) {
EncodedBuffer::iterator it = buffer.data();
EXPECT_FALSE(it.hasNext());
sp<ProtoReader> reader = buffer.data()->read();
EXPECT_FALSE(reader->hasNext());
}
TEST_F(FdBufferTest, ReadAndIterate) {
@@ -102,15 +102,23 @@ TEST_F(FdBufferTest, ReadAndIterate) {
ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
int i = 0;
EncodedBuffer::iterator it = buffer.data();
while (it.hasNext()) {
EXPECT_EQ(it.next(), (uint8_t)testdata[i++]);
}
sp<ProtoReader> reader = buffer.data()->read();
it.rp()->rewind();
it.rp()->move(buffer.size());
EXPECT_EQ(it.bytesRead(), testdata.size());
EXPECT_FALSE(it.hasNext());
while (reader->hasNext()) {
EXPECT_EQ(reader->next(), (uint8_t)testdata[i++]);
}
}
TEST_F(FdBufferTest, Move) {
std::string testdata = "FdBuffer test string";
ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
sp<ProtoReader> reader = buffer.data()->read();
reader->move(buffer.size());
EXPECT_EQ(reader->bytesRead(), testdata.size());
EXPECT_FALSE(reader->hasNext());
}
TEST_F(FdBufferTest, ReadTimeout) {
@@ -224,7 +232,7 @@ TEST_F(FdBufferTest, ReadInStreamEmpty) {
}
}
TEST_F(FdBufferTest, ReadInStreamMoreThan4MB) {
TEST_F(FdBufferTest, ReadInStreamMoreThan4MBWithMove) {
const std::string testFile = kTestDataPath + "morethan4MB.txt";
size_t fourMB = (size_t)4 * 1024 * 1024;
unique_fd fd(open(testFile.c_str(), O_RDONLY | O_CLOEXEC));
@@ -250,15 +258,45 @@ TEST_F(FdBufferTest, ReadInStreamMoreThan4MB) {
EXPECT_FALSE(buffer.timedOut());
EXPECT_TRUE(buffer.truncated());
wait(&pid);
EncodedBuffer::iterator it = buffer.data();
it.rp()->move(fourMB);
EXPECT_EQ(it.bytesRead(), fourMB);
EXPECT_FALSE(it.hasNext());
sp<ProtoReader> reader = buffer.data()->read();
reader->move(fourMB);
it.rp()->rewind();
while (it.hasNext()) {
char c = 'A' + (it.bytesRead() % 64 / 8);
ASSERT_TRUE(it.next() == c);
EXPECT_EQ(reader->bytesRead(), fourMB);
EXPECT_FALSE(reader->hasNext());
}
}
TEST_F(FdBufferTest, ReadInStreamMoreThan4MBWithNext) {
const std::string testFile = kTestDataPath + "morethan4MB.txt";
size_t fourMB = (size_t)4 * 1024 * 1024;
unique_fd fd(open(testFile.c_str(), O_RDONLY | O_CLOEXEC));
ASSERT_NE(fd.get(), -1);
int pid = fork();
ASSERT_TRUE(pid != -1);
if (pid == 0) {
p2cPipe.writeFd().reset();
c2pPipe.readFd().reset();
ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd()));
p2cPipe.readFd().reset();
c2pPipe.writeFd().reset();
_exit(EXIT_SUCCESS);
} else {
p2cPipe.readFd().reset();
c2pPipe.writeFd().reset();
ASSERT_EQ(NO_ERROR,
buffer.readProcessedDataInStream(fd, std::move(p2cPipe.writeFd()),
std::move(c2pPipe.readFd()), READ_TIMEOUT));
EXPECT_EQ(buffer.size(), fourMB);
EXPECT_FALSE(buffer.timedOut());
EXPECT_TRUE(buffer.truncated());
wait(&pid);
sp<ProtoReader> reader = buffer.data()->read();
while (reader->hasNext()) {
char c = 'A' + (reader->bytesRead() % 64 / 8);
ASSERT_TRUE(reader->next() == c);
}
}
}

View File

@@ -1,288 +0,0 @@
// 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 DEBUG false
#include "Log.h"
#include "FdBuffer.h"
#include "PrivacyBuffer.h"
#include <android-base/file.h>
#include <android-base/test_utils.h>
#include <android/os/IncidentReportArgs.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string.h>
using namespace android;
using namespace android::base;
using namespace android::os;
using namespace android::os::incidentd;
using ::testing::StrEq;
using ::testing::Test;
using ::testing::internal::CaptureStdout;
using ::testing::internal::GetCapturedStdout;
const uint8_t OTHER_TYPE = 1;
const uint8_t STRING_TYPE = 9;
const uint8_t MESSAGE_TYPE = 11;
const std::string STRING_FIELD_0 = "\x02\viamtestdata";
const std::string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
const std::string STRING_FIELD_2 = "\x12\vandroidwins";
const std::string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
const std::string FIX32_FIELD_4 = "\x25\xff\xff\xff\xff"; // -1
const std::string MESSAGE_FIELD_5 = "\x2a\x10" + VARINT_FIELD_1 + STRING_FIELD_2;
const std::string NEGATIVE_VARINT_FIELD_6 = "\x30\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"; // -1
class PrivacyBufferTest : public Test {
public:
virtual ~PrivacyBufferTest() {
// Delete in reverse order of construction, to be consistent with
// regular allocation/deallocation.
while (!privacies.empty()) {
delete privacies.back();
privacies.pop_back();
}
}
virtual void SetUp() override { ASSERT_NE(tf.fd, -1); }
void writeToFdBuffer(std::string str) {
ASSERT_TRUE(WriteStringToFile(str, tf.path));
ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
ASSERT_EQ(str.size(), buffer.size());
}
void assertBuffer(PrivacyBuffer& buf, std::string expected) {
ASSERT_EQ(buf.size(), expected.size());
CaptureStdout();
ASSERT_EQ(buf.flush(STDOUT_FILENO), NO_ERROR);
ASSERT_THAT(GetCapturedStdout(), StrEq(expected));
}
void assertStrip(uint8_t dest, std::string expected, Privacy* policy) {
PrivacySpec spec = PrivacySpec::new_spec(dest);
EncodedBuffer::iterator bufData = buffer.data();
PrivacyBuffer privacyBuf(policy, bufData);
ASSERT_EQ(privacyBuf.strip(spec), NO_ERROR);
assertBuffer(privacyBuf, expected);
}
void assertStripByFields(uint8_t dest, std::string expected, int size, Privacy* privacy, ...) {
Privacy* list[size + 1];
list[0] = privacy;
va_list args;
va_start(args, privacy);
for (int i = 1; i < size; i++) {
Privacy* p = va_arg(args, Privacy*);
list[i] = p;
}
va_end(args);
list[size] = NULL;
assertStrip(dest, expected, create_message_privacy(300, list));
}
Privacy* create_privacy(uint32_t field_id, uint8_t type, uint8_t dest) {
Privacy* p = new_uninit_privacy();
p->field_id = field_id;
p->type = type;
p->children = NULL;
p->dest = dest;
p->patterns = NULL;
return p;
}
Privacy* create_message_privacy(uint32_t field_id, Privacy** children) {
Privacy* p = new_uninit_privacy();
p->field_id = field_id;
p->type = MESSAGE_TYPE;
p->children = children;
p->dest = DEST_DEFAULT_VALUE;
p->patterns = NULL;
return p;
}
FdBuffer buffer;
private:
TemporaryFile tf;
// Littering this code with unique_ptr (or similar) is ugly, so we just
// mass-free everything after the test completes.
std::vector<Privacy*> privacies;
Privacy* new_uninit_privacy() {
Privacy* p = new Privacy;
privacies.push_back(p);
return p;
}
};
TEST_F(PrivacyBufferTest, NullPolicy) {
writeToFdBuffer(STRING_FIELD_0);
assertStrip(DEST_EXPLICIT, STRING_FIELD_0, NULL);
}
TEST_F(PrivacyBufferTest, StripUnsetField) {
writeToFdBuffer(STRING_FIELD_0);
assertStripByFields(DEST_AUTOMATIC, "", 1, create_privacy(0, STRING_TYPE, DEST_UNSET));
}
TEST_F(PrivacyBufferTest, StripVarintField) {
writeToFdBuffer(VARINT_FIELD_1);
assertStripByFields(DEST_EXPLICIT, "", 1, create_privacy(1, OTHER_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, StripLengthDelimitedField_String) {
writeToFdBuffer(STRING_FIELD_2);
assertStripByFields(DEST_EXPLICIT, "", 1, create_privacy(2, STRING_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, StripFixed64Field) {
writeToFdBuffer(FIX64_FIELD_3);
assertStripByFields(DEST_EXPLICIT, "", 1, create_privacy(3, OTHER_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, StripFixed32Field) {
writeToFdBuffer(FIX32_FIELD_4);
assertStripByFields(DEST_EXPLICIT, "", 1, create_privacy(4, OTHER_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, StripLengthDelimitedField_Message) {
writeToFdBuffer(MESSAGE_FIELD_5);
assertStripByFields(DEST_EXPLICIT, "", 1, create_privacy(5, MESSAGE_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, StripNegativeVarint) {
writeToFdBuffer(NEGATIVE_VARINT_FIELD_6);
assertStripByFields(DEST_EXPLICIT, "", 1, create_privacy(6, OTHER_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, NoStripVarintField) {
writeToFdBuffer(VARINT_FIELD_1);
assertStripByFields(DEST_EXPLICIT, VARINT_FIELD_1, 1,
create_privacy(1, OTHER_TYPE, DEST_AUTOMATIC));
}
TEST_F(PrivacyBufferTest, NoStripLengthDelimitedField_String) {
writeToFdBuffer(STRING_FIELD_2);
assertStripByFields(DEST_EXPLICIT, STRING_FIELD_2, 1,
create_privacy(2, STRING_TYPE, DEST_AUTOMATIC));
}
TEST_F(PrivacyBufferTest, NoStripFixed64Field) {
writeToFdBuffer(FIX64_FIELD_3);
assertStripByFields(DEST_EXPLICIT, FIX64_FIELD_3, 1,
create_privacy(3, OTHER_TYPE, DEST_AUTOMATIC));
}
TEST_F(PrivacyBufferTest, NoStripFixed32Field) {
writeToFdBuffer(FIX32_FIELD_4);
assertStripByFields(DEST_EXPLICIT, FIX32_FIELD_4, 1,
create_privacy(4, OTHER_TYPE, DEST_AUTOMATIC));
}
TEST_F(PrivacyBufferTest, NoStripLengthDelimitedField_Message) {
writeToFdBuffer(MESSAGE_FIELD_5);
assertStripByFields(DEST_EXPLICIT, MESSAGE_FIELD_5, 1,
create_privacy(5, MESSAGE_TYPE, DEST_AUTOMATIC));
}
TEST_F(PrivacyBufferTest, NoStripNegativeVarintField) {
writeToFdBuffer(NEGATIVE_VARINT_FIELD_6);
assertStripByFields(DEST_EXPLICIT, NEGATIVE_VARINT_FIELD_6, 1,
create_privacy(6, OTHER_TYPE, DEST_AUTOMATIC));
}
TEST_F(PrivacyBufferTest, StripVarintAndString) {
writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3 +
FIX32_FIELD_4);
std::string expected = STRING_FIELD_0 + FIX64_FIELD_3 + FIX32_FIELD_4;
assertStripByFields(DEST_EXPLICIT, expected, 2, create_privacy(1, OTHER_TYPE, DEST_LOCAL),
create_privacy(2, STRING_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, StripVarintAndFixed64) {
writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3 +
FIX32_FIELD_4);
std::string expected = STRING_FIELD_0 + STRING_FIELD_2 + FIX32_FIELD_4;
assertStripByFields(DEST_EXPLICIT, expected, 2, create_privacy(1, OTHER_TYPE, DEST_LOCAL),
create_privacy(3, OTHER_TYPE, DEST_LOCAL));
}
TEST_F(PrivacyBufferTest, StripVarintInNestedMessage) {
writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, DEST_LOCAL), NULL};
std::string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
assertStripByFields(DEST_EXPLICIT, expected, 1, create_message_privacy(5, list));
}
TEST_F(PrivacyBufferTest, StripFix64AndVarintInNestedMessage) {
writeToFdBuffer(STRING_FIELD_0 + FIX64_FIELD_3 + MESSAGE_FIELD_5);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, DEST_LOCAL), NULL};
std::string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
assertStripByFields(DEST_EXPLICIT, expected, 2, create_privacy(3, OTHER_TYPE, DEST_LOCAL),
create_message_privacy(5, list));
}
TEST_F(PrivacyBufferTest, ClearAndStrip) {
string data = STRING_FIELD_0 + VARINT_FIELD_1;
writeToFdBuffer(data);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, DEST_LOCAL), NULL};
EncodedBuffer::iterator bufData = buffer.data();
PrivacyBuffer privacyBuf(create_message_privacy(300, list), bufData);
PrivacySpec spec1 = PrivacySpec::new_spec(DEST_EXPLICIT);
PrivacySpec spec2 = PrivacySpec::new_spec(DEST_LOCAL);
ASSERT_EQ(privacyBuf.strip(spec1), NO_ERROR);
assertBuffer(privacyBuf, STRING_FIELD_0);
ASSERT_EQ(privacyBuf.strip(spec2), NO_ERROR);
assertBuffer(privacyBuf, data);
}
TEST_F(PrivacyBufferTest, BadDataInFdBuffer) {
writeToFdBuffer("iambaddata");
Privacy* list[] = {create_privacy(4, OTHER_TYPE, DEST_AUTOMATIC), NULL};
EncodedBuffer::iterator bufData = buffer.data();
PrivacyBuffer privacyBuf(create_message_privacy(300, list), bufData);
PrivacySpec spec;
ASSERT_EQ(privacyBuf.strip(spec), BAD_VALUE);
}
TEST_F(PrivacyBufferTest, BadDataInNestedMessage) {
writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5 + "aoeoe");
Privacy* list[] = {create_privacy(1, OTHER_TYPE, DEST_LOCAL), NULL};
Privacy* field5[] = {create_message_privacy(5, list), NULL};
EncodedBuffer::iterator bufData = buffer.data();
PrivacyBuffer privacyBuf(create_message_privacy(300, field5), bufData);
PrivacySpec spec;
ASSERT_EQ(privacyBuf.strip(spec), BAD_VALUE);
}
TEST_F(PrivacyBufferTest, SelfRecursionMessage) {
string input = "\x2a\"" + VARINT_FIELD_1 + STRING_FIELD_2 + MESSAGE_FIELD_5;
writeToFdBuffer(input);
Privacy* field5 = create_message_privacy(5, NULL);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, DEST_LOCAL), field5, NULL};
field5->children = list;
std::string expected = "\x2a\x1c" + STRING_FIELD_2 + "\x2a\xd" + STRING_FIELD_2;
assertStrip(DEST_EXPLICIT, expected, field5);
}
TEST_F(PrivacyBufferTest, AutoMessage) {
writeToFdBuffer(STRING_FIELD_2 + MESSAGE_FIELD_5);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, DEST_LOCAL), NULL};
Privacy* autoMsg = create_privacy(5, MESSAGE_TYPE, DEST_AUTOMATIC);
autoMsg->children = list;
std::string expected = "\x2a\xd" + STRING_FIELD_2;
assertStripByFields(DEST_AUTOMATIC, expected, 1, autoMsg);
}

View File

@@ -0,0 +1,302 @@
// 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 DEBUG false
#include "Log.h"
#include "FdBuffer.h"
#include "PrivacyFilter.h"
#include <android-base/file.h>
#include <android-base/test_utils.h>
#include <android/os/IncidentReportArgs.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string.h>
using namespace android;
using namespace android::base;
using namespace android::os;
using namespace android::os::incidentd;
using ::testing::StrEq;
using ::testing::Test;
using ::testing::internal::CaptureStdout;
using ::testing::internal::GetCapturedStdout;
const uint8_t OTHER_TYPE = 1;
const uint8_t STRING_TYPE = 9;
const uint8_t MESSAGE_TYPE = 11;
const std::string STRING_FIELD_0 = "\x02\viamtestdata";
const std::string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
const std::string STRING_FIELD_2 = "\x12\vandroidwins";
const std::string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
const std::string FIX32_FIELD_4 = "\x25\xff\xff\xff\xff"; // -1
const std::string MESSAGE_FIELD_5 = "\x2a\x10" + VARINT_FIELD_1 + STRING_FIELD_2;
const std::string NEGATIVE_VARINT_FIELD_6 = "\x30\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"; // -1
#if 0
class PrivacyFilterTest : public Test {
public:
virtual ~PrivacyFilterTest() {
// Delete in reverse order of construction, to be consistent with
// regular allocation/deallocation.
while (!privacies.empty()) {
delete privacies.back();
privacies.pop_back();
}
}
virtual void SetUp() override { ASSERT_NE(tf.fd, -1); }
void writeToFdBuffer(std::string str) {
ASSERT_TRUE(WriteStringToFile(str, tf.path));
ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
ASSERT_EQ(str.size(), buffer.size());
}
void assertBuffer(PrivacyFilter& buf, std::string expected) {
ASSERT_EQ(buf.size(), expected.size());
CaptureStdout();
ASSERT_EQ(buf.flush(STDOUT_FILENO), NO_ERROR);
ASSERT_THAT(GetCapturedStdout(), StrEq(expected));
}
void assertStrip(uint8_t privacyPolicy, std::string expected, Privacy* policy) {
PrivacySpec spec = PrivacySpec::new_spec(privacyPolicy);
EncodedBuffer::iterator bufData = buffer.data();
PrivacyFilter filter(policy, bufData);
ASSERT_EQ(filter.strip(spec), NO_ERROR);
assertBuffer(filter, expected);
}
void assertStripByFields(uint8_t privacyPolicy, std::string expected, int size,
Privacy* privacy, ...) {
Privacy* list[size + 1];
list[0] = privacy;
va_list args;
va_start(args, privacy);
for (int i = 1; i < size; i++) {
Privacy* p = va_arg(args, Privacy*);
list[i] = pPrivacyFilter
}
va_end(args);
list[size] = NULL;
assertStrip(privacyPolicy, expected, create_message_privacy(300, list));
}
Privacy* create_privacy(uint32_t field_id, uint8_t type, uint8_t privacyPolicy) {
Privacy* p = new_uninit_privacy();
p->field_id = field_id;
p->type = type;
p->children = NULL;
p->policy = privacyPolicy;
p->patterns = NULL;
return p;
}
Privacy* create_message_privacy(uint32_t field_id, Privacy** children) {
Privacy* p = new_uninit_privacy();
p->field_id = field_id;
p->type = MESSAGE_TYPE;
p->children = children;
p->policy = PRIVACY_POLICY_UNSET;
p->patterns = NULL;
return p;
}
FdBuffer buffer;
private:
TemporaryFile tf;
// Littering this code with unique_ptr (or similar) is ugly, so we just
// mass-free everything after the test completes.
std::vector<Privacy*> privacies;
Privacy* new_uninit_privacy() {
Privacy* p = new Privacy;
privacies.push_back(p);
return p;
}
};
TEST_F(PrivacyFilterTest, NullPolicy) {
writeToFdBuffer(STRING_FIELD_0);
assertStrip(PRIVACY_POLICY_EXPLICIT, STRING_FIELD_0, NULL);
}
TEST_F(PrivacyFilterTest, StripUnsetField) {
writeToFdBuffer(STRING_FIELD_0);
assertStripByFields(PRIVACY_POLICY_AUTOMATIC, "", 1,
create_privacy(0, STRING_TYPE, PRIVACY_POLICY_UNSET));
}
TEST_F(PrivacyFilterTest, StripVarintField) {
writeToFdBuffer(VARINT_FIELD_1);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, "", 1,
create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, StripLengthDelimitedField_String) {
writeToFdBuffer(STRING_FIELD_2);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, "", 1,
create_privacy(2, STRING_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, StripFixed64Field) {
writeToFdBuffer(FIX64_FIELD_3);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, "", 1,
create_privacy(3, OTHER_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, StripFixed32Field) {
writeToFdBuffer(FIX32_FIELD_4);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, "", 1,
create_privacy(4, OTHER_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, StripLengthDelimitedField_Message) {
writeToFdBuffer(MESSAGE_FIELD_5);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, "", 1,
create_privacy(5, MESSAGE_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, StripNegativeVarint) {
writeToFdBuffer(NEGATIVE_VARINT_FIELD_6);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, "", 1,
create_privacy(6, OTHER_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, NoStripVarintField) {
writeToFdBuffer(VARINT_FIELD_1);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, VARINT_FIELD_1, 1,
create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_AUTOMATIC));
}
TEST_F(PrivacyFilterTest, NoStripLengthDelimitedField_String) {
writeToFdBuffer(STRING_FIELD_2);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, STRING_FIELD_2, 1,
create_privacy(2, STRING_TYPE, PRIVACY_POLICY_AUTOMATIC));
}
TEST_F(PrivacyFilterTest, NoStripFixed64Field) {
writeToFdBuffer(FIX64_FIELD_3);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, FIX64_FIELD_3, 1,
create_privacy(3, OTHER_TYPE, PRIVACY_POLICY_AUTOMATIC));
}
TEST_F(PrivacyFilterTest, NoStripFixed32Field) {
writeToFdBuffer(FIX32_FIELD_4);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, FIX32_FIELD_4, 1,
create_privacy(4, OTHER_TYPE, PRIVACY_POLICY_AUTOMATIC));
}
TEST_F(PrivacyFilterTest, NoStripLengthDelimitedField_Message) {
writeToFdBuffer(MESSAGE_FIELD_5);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, MESSAGE_FIELD_5, 1,
create_privacy(5, MESSAGE_TYPE, PRIVACY_POLICY_AUTOMATIC));
}
TEST_F(PrivacyFilterTest, NoStripNegativeVarintField) {
writeToFdBuffer(NEGATIVE_VARINT_FIELD_6);
assertStripByFields(PRIVACY_POLICY_EXPLICIT, NEGATIVE_VARINT_FIELD_6, 1,
create_privacy(6, OTHER_TYPE, PRIVACY_POLICY_AUTOMATIC));
}
TEST_F(PrivacyFilterTest, StripVarintAndString) {
writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3 +
FIX32_FIELD_4);
std::string expected = STRING_FIELD_0 + FIX64_FIELD_3 + FIX32_FIELD_4;
assertStripByFields(PRIVACY_POLICY_EXPLICIT, expected, 2,
create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL),
create_privacy(2, STRING_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, StripVarintAndFixed64) {
writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3 +
FIX32_FIELD_4);
std::string expected = STRING_FIELD_0 + STRING_FIELD_2 + FIX32_FIELD_4;
assertStripByFields(PRIVACY_POLICY_EXPLICIT, expected, 2,
create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL),
create_privacy(3, OTHER_TYPE, PRIVACY_POLICY_LOCAL));
}
TEST_F(PrivacyFilterTest, StripVarintInNestedMessage) {
writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL), NULL};
std::string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
assertStripByFields(PRIVACY_POLICY_EXPLICIT, expected, 1, create_message_privacy(5, list));
}
TEST_F(PrivacyFilterTest, StripFix64AndVarintInNestedMessage) {
writeToFdBuffer(STRING_FIELD_0 + FIX64_FIELD_3 + MESSAGE_FIELD_5);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL), NULL};
std::string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
assertStripByFields(PRIVACY_POLICY_EXPLICIT, expected, 2,
create_privacy(3, OTHER_TYPE, PRIVACY_POLICY_LOCAL),
create_message_privacy(5, list));
}
TEST_F(PrivacyFilterTest, ClearAndStrip) {
string data = STRING_FIELD_0 + VARINT_FIELD_1;
writeToFdBuffer(data);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL), NULL};
EncodedBuffer::iterator bufData = buffer.data();
PrivacyFilter filter(create_message_privacy(300, list), bufData);
PrivacySpec spec1 = PrivacySpec::new_spec(PRIVACY_POLICY_EXPLICIT);
PrivacySpec spec2 = PrivacySpec::new_spec(PRIVACY_POLICY_LOCAL);
ASSERT_EQ(filter.strip(spec1), NO_ERROR);
assertBuffer(filter, STRING_FIELD_0);
ASSERT_EQ(filter.strip(spec2), NO_ERROR);
assertBuffer(filter, data);
}
TEST_F(PrivacyFilterTest, BadDataInFdBuffer) {
writeToFdBuffer("iambaddata");
Privacy* list[] = {create_privacy(4, OTHER_TYPE, PRIVACY_POLICY_AUTOMATIC), NULL};
EncodedBuffer::iterator bufData = buffer.data();
PrivacyFilter filter(create_message_privacy(300, list), bufData);
PrivacySpec spec;
ASSERT_EQ(filter.strip(spec), BAD_VALUE);
}
TEST_F(PrivacyFilterTest, BadDataInNestedMessage) {
writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5 + "aoeoe");
Privacy* list[] = {create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL), NULL};
Privacy* field5[] = {create_message_privacy(5, list), NULL};
EncodedBuffer::iterator bufData = buffer.data();
PrivacyFilter filter(create_message_privacy(300, field5), bufData);
PrivacySpec spec;
ASSERT_EQ(filter.strip(spec), BAD_VALUE);
}
TEST_F(PrivacyFilterTest, SelfRecursionMessage) {
string input = "\x2a\"" + VARINT_FIELD_1 + STRING_FIELD_2 + MESSAGE_FIELD_5;
writeToFdBuffer(input);
Privacy* field5 = create_message_privacy(5, NULL);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL), field5, NULL};
field5->children = list;
std::string expected = "\x2a\x1c" + STRING_FIELD_2 + "\x2a\xd" + STRING_FIELD_2;
assertStrip(PRIVACY_POLICY_EXPLICIT, expected, field5);
}
TEST_F(PrivacyFilterTest, AutoMessage) {
writeToFdBuffer(STRING_FIELD_2 + MESSAGE_FIELD_5);
Privacy* list[] = {create_privacy(1, OTHER_TYPE, PRIVACY_POLICY_LOCAL), NULL};
Privacy* autoMsg = create_privacy(5, MESSAGE_TYPE, PRIVACY_POLICY_AUTOMATIC);
autoMsg->children = list;
std::string expected = "\x2a\xd" + STRING_FIELD_2;
assertStripByFields(PRIVACY_POLICY_AUTOMATIC, expected, 1, autoMsg);
}
#endif

View File

@@ -17,7 +17,7 @@
#include "Reporter.h"
#include <android/os/BnIncidentReportStatusListener.h>
#include <frameworks/base/libs/incident/proto/android/os/header.pb.h>
#include <frameworks/base/core/proto/android/os/header.pb.h>
#include <dirent.h>
#include <string.h>
@@ -36,6 +36,7 @@ using ::testing::StrEq;
using ::testing::Test;
namespace {
/*
void getHeaderData(const IncidentHeaderProto& headerProto, vector<uint8_t>* out) {
out->clear();
auto serialized = headerProto.SerializeAsString();
@@ -43,6 +44,7 @@ void getHeaderData(const IncidentHeaderProto& headerProto, vector<uint8_t>* out)
out->resize(serialized.length());
std::copy(serialized.begin(), serialized.end(), out->begin());
}
*/
}
class TestListener : public IIncidentReportStatusListener {
@@ -82,6 +84,24 @@ public:
return Status::ok();
};
int sectionStarted(int sectionId) const {
map<int, int>::const_iterator found = startSections.find(sectionId);
if (found != startSections.end()) {
return found->second;
} else {
return 0;
}
};
int sectionFinished(int sectionId) const {
map<int, int>::const_iterator found = finishSections.find(sectionId);
if (found != finishSections.end()) {
return found->second;
} else {
return 0;
}
};
protected:
virtual IBinder* onAsBinder() override { return nullptr; };
};
@@ -89,8 +109,7 @@ protected:
class ReporterTest : public Test {
public:
virtual void SetUp() {
reporter = new Reporter(td.path);
l = new TestListener();
listener = new TestListener();
}
vector<string> InspectFiles() {
@@ -115,9 +134,7 @@ public:
protected:
TemporaryDir td;
ReportRequestSet requests;
sp<Reporter> reporter;
sp<TestListener> l;
sp<TestListener> listener;
size_t size;
};
@@ -132,18 +149,17 @@ TEST_F(ReporterTest, IncidentReportArgs) {
ASSERT_TRUE(args1.containsSection(3));
}
TEST_F(ReporterTest, ReportRequestSetEmpty) {
requests.setMainFd(STDOUT_FILENO);
ASSERT_EQ(requests.mainFd(), STDOUT_FILENO);
}
/*
TEST_F(ReporterTest, RunReportEmpty) {
vector<sp<ReportRequest>> requests;
sp<Reporter> reporter = new Reporter(requests, td.path);
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
EXPECT_EQ(l->startInvoked, 0);
EXPECT_EQ(l->finishInvoked, 0);
EXPECT_TRUE(l->startSections.empty());
EXPECT_TRUE(l->finishSections.empty());
EXPECT_EQ(l->failedInvoked, 0);
EXPECT_EQ(0, listener->startInvoked);
EXPECT_EQ(0, listener->finishInvoked);
EXPECT_TRUE(listener->startSections.empty());
EXPECT_TRUE(listener->finishSections.empty());
EXPECT_EQ(0, listener->failedInvoked);
}
TEST_F(ReporterTest, RunReportWithHeaders) {
@@ -157,11 +173,11 @@ TEST_F(ReporterTest, RunReportWithHeaders) {
vector<uint8_t> out;
getHeaderData(header, &out);
args2.addHeader(out);
sp<ReportRequest> r1 = new ReportRequest(args1, l, tf.fd);
sp<ReportRequest> r2 = new ReportRequest(args2, l, tf.fd);
reporter->batch.add(r1);
reporter->batch.add(r2);
sp<WorkDirectory> workDirectory = new WorkDirectory(td.path);
sp<ReportBatch> batch = new ReportBatch();
batch->addStreamingReport(args1, listener, tf.fd);
sp<Reporter> reporter = new Reporter(workDirectory, batch);
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
@@ -170,11 +186,11 @@ TEST_F(ReporterTest, RunReportWithHeaders) {
EXPECT_THAT(result, StrEq("\n\x2"
"\b\f"));
EXPECT_EQ(l->startInvoked, 2);
EXPECT_EQ(l->finishInvoked, 2);
EXPECT_TRUE(l->startSections.empty());
EXPECT_TRUE(l->finishSections.empty());
EXPECT_EQ(l->failedInvoked, 0);
EXPECT_EQ(listener->startInvoked, 1);
EXPECT_EQ(listener->finishInvoked, 1);
EXPECT_FALSE(listener->startSections.empty());
EXPECT_FALSE(listener->finishSections.empty());
EXPECT_EQ(listener->failedInvoked, 0);
}
TEST_F(ReporterTest, RunReportToGivenDirectory) {
@@ -188,8 +204,10 @@ TEST_F(ReporterTest, RunReportToGivenDirectory) {
args.addHeader(out);
getHeaderData(header2, &out);
args.addHeader(out);
sp<ReportRequest> r = new ReportRequest(args, l, -1);
reporter->batch.add(r);
vector<sp<ReportRequest>> requests;
requests.push_back(new ReportRequest(args, listener, -1));
sp<Reporter> reporter = new Reporter(requests, td.path);
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
vector<string> results = InspectFiles();
@@ -204,14 +222,36 @@ TEST_F(ReporterTest, RunReportToGivenDirectory) {
TEST_F(ReporterTest, ReportMetadata) {
IncidentReportArgs args;
args.addSection(1);
args.setDest(android::os::DEST_EXPLICIT);
sp<ReportRequest> r = new ReportRequest(args, l, -1);
reporter->batch.add(r);
args.setPrivacyPolicy(android::os::PRIVACY_POLICY_EXPLICIT);
vector<sp<ReportRequest>> requests;
requests.push_back(new ReportRequest(args, listener, -1));
sp<Reporter> reporter = new Reporter(requests, td.path);
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
IncidentMetadata metadata = reporter->batch.metadata();
IncidentMetadata metadata = reporter->metadata();
EXPECT_EQ(IncidentMetadata_Destination_EXPLICIT, metadata.dest());
EXPECT_EQ(1, metadata.request_size());
EXPECT_TRUE(metadata.use_dropbox());
EXPECT_EQ(0, metadata.sections_size());
}
TEST_F(ReporterTest, RunReportLocal_1_2) {
IncidentReportArgs args;
args.addSection(1);
args.addSection(2);
args.setPrivacyPolicy(android::os::PRIVACY_POLICY_LOCAL);
vector<sp<ReportRequest>> requests;
requests.push_back(new ReportRequest(args, listener, -1));
sp<Reporter> reporter = new Reporter(requests, td.path);
ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport(&size));
EXPECT_EQ(1, listener->sectionStarted(1));
EXPECT_EQ(1, listener->sectionFinished(1));
EXPECT_EQ(1, listener->sectionStarted(2));
EXPECT_EQ(1, listener->sectionFinished(2));
// TODO: validate that a file was created in the directory
}
*/

View File

@@ -20,7 +20,8 @@
#include <android-base/test_utils.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
#include <frameworks/base/libs/incident/proto/android/os/header.pb.h>
#include <frameworks/base/core/proto/android/os/incident.pb.h>
#include <frameworks/base/core/proto/android/os/header.pb.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string.h>
@@ -62,7 +63,6 @@ public:
protected:
TemporaryFile tf;
ReportRequestSet requests;
const std::string kTestPath = GetExecutableDirectory();
const std::string kTestDataPath = kTestPath + "/testdata/";
@@ -74,7 +74,8 @@ public:
virtual ~SimpleListener(){};
virtual Status onReportStarted() { return Status::ok(); };
virtual Status onReportSectionStatus(int /*section*/, int /*status*/) { return Status::ok(); };
virtual Status onReportSectionStatus(int /*section*/, int /*status*/)
{ return Status::ok(); };
virtual Status onReportFinished() { return Status::ok(); };
virtual Status onReportFailed() { return Status::ok(); };
@@ -82,67 +83,30 @@ protected:
virtual IBinder* onAsBinder() override { return nullptr; };
};
namespace {
void getHeaderData(const IncidentHeaderProto& headerProto, vector<uint8_t>* out) {
out->clear();
auto serialized = headerProto.SerializeAsString();
if (serialized.empty()) return;
out->resize(serialized.length());
std::copy(serialized.begin(), serialized.end(), out->begin());
}
}
TEST_F(SectionTest, HeaderSection) {
HeaderSection hs;
IncidentReportArgs args1, args2;
args1.addSection(1);
args1.addSection(2);
args2.setAll(true);
IncidentHeaderProto head1, head2;
head1.set_reason("axe");
head2.set_reason("pup");
vector<uint8_t> out;
getHeaderData(head1, &out);
args1.addHeader(out);
getHeaderData(head2, &out);
args1.addHeader(out);
getHeaderData(head2, &out);
args2.addHeader(out);
requests.add(new ReportRequest(args1, new SimpleListener(), -1));
requests.add(new ReportRequest(args2, new SimpleListener(), tf.fd));
requests.setMainFd(STDOUT_FILENO);
std::string content;
CaptureStdout();
ASSERT_EQ(NO_ERROR, hs.Execute(&requests));
EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5"
"\x12\x3"
"axe\n\x05\x12\x03pup"));
EXPECT_TRUE(ReadFileToString(tf.path, &content));
EXPECT_THAT(content, StrEq("\n\x05\x12\x03pup"));
}
/*
TEST_F(SectionTest, MetadataSection) {
MetadataSection ms;
const std::string testFile = kTestDataPath + "metadata.txt";
std::string expect;
ASSERT_TRUE(ReadFileToString(testFile, &expect));
requests.setMainFd(STDOUT_FILENO);
requests.setMainDest(android::os::DEST_LOCAL);
requests.sectionStats(1)->set_success(true);
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
requestSet.setMainPrivacyPolicy(android::os::PRIVACY_POLICY_LOCAL);
requestSet.editSectionStats(1)->set_success(true);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ms.Execute(&requests));
// Notice message_lite.h ParseFromString doesn't work so we just match the bytes directly.
EXPECT_THAT(GetCapturedStdout(), StrEq(expect));
ASSERT_EQ(NO_ERROR, ms.Execute(&requestSet));
string out = GetCapturedStdout();
IncidentProto expectedIncident;
expectedIncident.ParseFromArray(out.data(), out.size());
ASSERT_TRUE(expectedIncident.has_metadata());
const IncidentMetadata& expectedMetadata = expectedIncident.metadata();
ASSERT_EQ(IncidentMetadata::LOCAL, expectedMetadata.dest());
ASSERT_EQ(1, expectedMetadata.sections_size());
ASSERT_EQ(1, expectedMetadata.sections(0).id());
ASSERT_TRUE(expectedMetadata.sections(0).has_success());
ASSERT_TRUE(expectedMetadata.sections(0).success());
}
TEST_F(SectionTest, FileSection) {
@@ -150,27 +114,35 @@ TEST_F(SectionTest, FileSection) {
ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path));
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
ASSERT_EQ(NO_ERROR, fs.Execute(&requestSet));
// The input string is reversed in incident helper
// The length is 11, in 128Varint it is "0000 1011" -> \v
EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\vatadtsetmai"));
}
TEST_F(SectionTest, FileSectionNotExist) {
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
FileSection fs1(NOOP_PARSER, "notexist", QUICK_TIMEOUT_MS);
ASSERT_EQ(NO_ERROR, fs1.Execute(&requests));
ASSERT_EQ(NO_ERROR, fs1.Execute(&requestSet));
FileSection fs2(NOOP_PARSER, "notexist", QUICK_TIMEOUT_MS);
ASSERT_EQ(NO_ERROR, fs2.Execute(&requests));
ASSERT_EQ(NO_ERROR, fs2.Execute(&requestSet));
}
TEST_F(SectionTest, FileSectionTimeout) {
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
FileSection fs(TIMEOUT_PARSER, tf.path, QUICK_TIMEOUT_MS);
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
ASSERT_TRUE(requests.sectionStats(TIMEOUT_PARSER)->timed_out());
ASSERT_EQ(NO_ERROR, fs.Execute(&requestSet));
ASSERT_TRUE(requestSet.getSectionStats(TIMEOUT_PARSER)->timed_out());
}
TEST_F(SectionTest, GZipSection) {
@@ -178,10 +150,12 @@ TEST_F(SectionTest, GZipSection) {
const std::string testGzFile = testFile + ".gz";
GZipSection gs(NOOP_PARSER, "/tmp/nonexist", testFile.c_str(), NULL);
requests.setMainFd(tf.fd);
requests.setMainDest(android::os::DEST_LOCAL);
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(tf.fd);
requestSet.setMainPrivacyPolicy(android::os::PRIVACY_POLICY_LOCAL);
ASSERT_EQ(NO_ERROR, gs.Execute(&requests));
ASSERT_EQ(NO_ERROR, gs.Execute(&requestSet));
std::string expected, gzFile, actual;
ASSERT_TRUE(ReadFileToString(testGzFile, &gzFile));
ASSERT_TRUE(ReadFileToString(tf.path, &actual));
@@ -200,8 +174,10 @@ TEST_F(SectionTest, GZipSection) {
TEST_F(SectionTest, GZipSectionNoFileFound) {
GZipSection gs(NOOP_PARSER, "/tmp/nonexist1", "/tmp/nonexist2", NULL);
requests.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, gs.Execute(&requests));
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, gs.Execute(&requestSet));
}
TEST_F(SectionTest, CommandSectionConstructor) {
@@ -220,51 +196,65 @@ TEST_F(SectionTest, CommandSectionConstructor) {
TEST_F(SectionTest, CommandSectionEcho) {
CommandSection cs(REVERSE_PARSER, "/system/bin/echo", "about", NULL);
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
ASSERT_EQ(NO_ERROR, cs.Execute(&requestSet));
EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\x06\ntuoba"));
}
TEST_F(SectionTest, CommandSectionCommandTimeout) {
CommandSection cs(NOOP_PARSER, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
ASSERT_TRUE(requests.sectionStats(NOOP_PARSER)->timed_out());
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
ASSERT_EQ(NO_ERROR, cs.Execute(&requestSet));
ASSERT_TRUE(requestSet.getSectionStats(NOOP_PARSER)->timed_out());
}
TEST_F(SectionTest, CommandSectionIncidentHelperTimeout) {
CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
requests.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
ASSERT_TRUE(requests.sectionStats(TIMEOUT_PARSER)->timed_out());
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, cs.Execute(&requestSet));
ASSERT_TRUE(requestSet.getSectionStats(TIMEOUT_PARSER)->timed_out());
}
TEST_F(SectionTest, CommandSectionBadCommand) {
CommandSection cs(NOOP_PARSER, "echoo", "about", NULL);
ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requestSet));
}
TEST_F(SectionTest, CommandSectionBadCommandAndTimeout) {
CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
// timeout will return first
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
ASSERT_TRUE(requests.sectionStats(TIMEOUT_PARSER)->timed_out());
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
ASSERT_EQ(NO_ERROR, cs.Execute(&requestSet));
ASSERT_TRUE(requestSet.getSectionStats(TIMEOUT_PARSER)->timed_out());
}
TEST_F(SectionTest, LogSectionBinary) {
LogSection ls(1, LOG_ID_EVENTS);
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
ASSERT_EQ(NO_ERROR, ls.Execute(&requestSet));
std::string results = GetCapturedStdout();
EXPECT_FALSE(results.empty());
}
TEST_F(SectionTest, LogSectionSystem) {
LogSection ls(1, LOG_ID_SYSTEM);
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
ASSERT_EQ(NO_ERROR, ls.Execute(&requestSet));
std::string results = GetCapturedStdout();
EXPECT_FALSE(results.empty());
}
@@ -274,10 +264,12 @@ TEST_F(SectionTest, TestFilterPiiTaggedFields) {
ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
ASSERT_EQ(NO_ERROR, fs.Execute(&requestSet));
EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
}
@@ -287,13 +279,16 @@ TEST_F(SectionTest, TestBadFdRequest) {
IncidentReportArgs args;
args.setAll(true);
args.setDest(0);
args.setPrivacyPolicy(0);
sp<ReportRequest> badFdRequest = new ReportRequest(args, new SimpleListener(), 1234567);
requests.add(badFdRequest);
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
requests.push_back(badFdRequest);
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
ASSERT_EQ(NO_ERROR, fs.Execute(&requestSet));
EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
EXPECT_EQ(badFdRequest->err, -EBADF);
}
@@ -304,9 +299,13 @@ TEST_F(SectionTest, TestBadRequests) {
IncidentReportArgs args;
args.setAll(true);
args.setDest(0);
requests.add(new ReportRequest(args, new SimpleListener(), -1));
EXPECT_EQ(fs.Execute(&requests), -EBADF);
args.setPrivacyPolicy(0);
vector<sp<ReportRequest>> requests;
requests.push_back(new ReportRequest(args, new SimpleListener(), -1));
ReportRequestSet requestSet(requests);
EXPECT_EQ(fs.Execute(&requestSet), -EBADF);
}
TEST_F(SectionTest, TestMultipleRequests) {
@@ -320,17 +319,20 @@ TEST_F(SectionTest, TestMultipleRequests) {
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
args1.setDest(android::os::DEST_LOCAL);
args1.setPrivacyPolicy(android::os::PRIVACY_POLICY_LOCAL);
args2.setAll(true);
args2.setDest(android::os::DEST_EXPLICIT);
args2.setPrivacyPolicy(android::os::PRIVACY_POLICY_EXPLICIT);
sp<SimpleListener> l = new SimpleListener();
requests.add(new ReportRequest(args1, l, output1.fd));
requests.add(new ReportRequest(args2, l, output2.fd));
requests.add(new ReportRequest(args3, l, output3.fd));
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
requests.push_back(new ReportRequest(args1, l, output1.fd));
requests.push_back(new ReportRequest(args2, l, output2.fd));
requests.push_back(new ReportRequest(args3, l, output3.fd));
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
ASSERT_EQ(NO_ERROR, fs.Execute(&requestSet));
EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
std::string content, expect;
@@ -361,18 +363,21 @@ TEST_F(SectionTest, TestMultipleRequestsBySpec) {
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
args1.setDest(android::os::DEST_EXPLICIT);
args1.setPrivacyPolicy(android::os::PRIVACY_POLICY_EXPLICIT);
args2.setAll(true);
args2.setDest(android::os::DEST_EXPLICIT);
args2.setPrivacyPolicy(android::os::PRIVACY_POLICY_EXPLICIT);
args3.setAll(true);
sp<SimpleListener> l = new SimpleListener();
requests.add(new ReportRequest(args1, l, output1.fd));
requests.add(new ReportRequest(args2, l, output2.fd));
requests.add(new ReportRequest(args3, l, output3.fd));
requests.setMainFd(STDOUT_FILENO);
vector<sp<ReportRequest>> requests;
requests.push_back(new ReportRequest(args1, l, output1.fd));
requests.push_back(new ReportRequest(args2, l, output2.fd));
requests.push_back(new ReportRequest(args3, l, output3.fd));
ReportRequestSet requestSet(requests);
requestSet.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
ASSERT_EQ(NO_ERROR, fs.Execute(&requestSet));
EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
std::string content, expect;
@@ -390,3 +395,4 @@ TEST_F(SectionTest, TestMultipleRequestsBySpec) {
EXPECT_TRUE(ReadFileToString(output3.path, &content));
EXPECT_THAT(content, StrEq(string("\x02") + c + STRING_FIELD_2));
}
*/

View File

@@ -1,19 +1,65 @@
// This file is a dummy section_list.cpp used for test only.
#include "section_list.h"
#include "frameworks/base/cmds/incidentd/tests/test_proto.pb.h"
namespace android {
namespace os {
namespace incidentd {
const Section* SECTION_LIST[] = {NULL};
class TestSection: public Section {
public:
TestSection(int id);
~TestSection();
virtual status_t Execute(ReportWriter* writer) const;
};
Privacy sub_field_1{1, 1, NULL, DEST_LOCAL, NULL};
Privacy sub_field_2{2, 9, NULL, DEST_AUTOMATIC, NULL};
TestSection::TestSection(int id)
:Section(id, 5000 /* ms timeout */) {
}
TestSection::~TestSection() {
}
status_t TestSection::Execute(ReportWriter* writer) const {
uint8_t buf[1024];
status_t err;
TestSectionProto proto;
proto.set_field_1(this->id);
proto.set_field_2(this->id * 10);
// Not infinitely scalable, but we know that our TestSectionProto will always
// fit in this many bytes.
if (!proto.SerializeToArray(buf, sizeof(buf))) {
return -1;
}
FdBuffer buffer;
err = buffer.write(buf, proto.ByteSize());
if (err != NO_ERROR) {
return err;
}
return writer->writeSection(buffer);
}
TestSection section1(1);
TestSection section2(2);
const Section* SECTION_LIST[] = {
&section1,
&section2,
NULL
};
Privacy sub_field_1{1, 1, NULL, PRIVACY_POLICY_LOCAL, NULL};
Privacy sub_field_2{2, 9, NULL, PRIVACY_POLICY_AUTOMATIC, NULL};
Privacy* list[] = {&sub_field_1, &sub_field_2, NULL};
Privacy field_0{0, 11, list, DEST_EXPLICIT, NULL};
Privacy field_1{1, 9, NULL, DEST_AUTOMATIC, NULL};
Privacy field_0{0, 11, list, PRIVACY_POLICY_EXPLICIT, NULL};
Privacy field_1{1, 9, NULL, PRIVACY_POLICY_AUTOMATIC, NULL};
Privacy* final_list[] = {&field_0, &field_1};
@@ -23,4 +69,4 @@ const int PRIVACY_POLICY_COUNT = 2;
} // namespace incidentd
} // namespace os
} // namespace android
} // namespace android

View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
syntax = "proto2";
package android.os.incidentd;
message TestSectionProto {
// The id of the section, written by TestSection.
optional int32 field_1 = 1;
// The id of the section, times 10, written by TestSection.
optional int32 field_2 = 2;
}

View File

@@ -118,6 +118,7 @@ cc_defaults {
static_libs: [
"libhealthhalutils",
"libplatformprotos",
],
shared_libs: [

View File

@@ -407,12 +407,12 @@ void StatsLogProcessor::onDumpReport(const ConfigKey& key, const int64_t dumpTim
outData->clear();
outData->resize(proto.size());
size_t pos = 0;
auto iter = proto.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&((*outData)[pos]), iter.readBuffer(), toRead);
sp<android::util::ProtoReader> reader = proto.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&((*outData)[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
}

View File

@@ -50,6 +50,7 @@ using android::base::StringPrintf;
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_INT64;
using android::util::FIELD_TYPE_MESSAGE;
using android::util::ProtoReader;
namespace android {
namespace os {
@@ -1220,12 +1221,12 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra
experimentIdsProtoBuffer.resize(proto.size());
size_t pos = 0;
auto iter = proto.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&(experimentIdsProtoBuffer[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = proto.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&(experimentIdsProtoBuffer[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
}

View File

@@ -29,6 +29,8 @@ namespace android {
namespace os {
namespace statsd {
using android::util::ProtoReader;
GpuStatsPuller::GpuStatsPuller(const int tagId) : StatsPuller(tagId) {
}
@@ -116,11 +118,11 @@ static std::string protoOutputStreamToByteString(ProtoOutputStream& proto) {
if (!proto.size()) return "";
std::string byteString;
auto iter = proto.data();
while (iter.readBuffer() != nullptr) {
const size_t toRead = iter.currentToRead();
byteString.append((char*)iter.readBuffer(), toRead);
iter.rp()->move(toRead);
sp<ProtoReader> reader = proto.data();
while (reader->readBuffer() != nullptr) {
const size_t toRead = reader->currentToRead();
byteString.append((char*)reader->readBuffer(), toRead);
reader->move(toRead);
}
if (byteString.size() != proto.size()) return "";

View File

@@ -904,12 +904,12 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
output->resize(bufferSize);
size_t pos = 0;
auto it = proto.data();
while (it.readBuffer() != NULL) {
size_t toRead = it.currentToRead();
std::memcpy(&((*output)[pos]), it.readBuffer(), toRead);
sp<android::util::ProtoReader> reader = proto.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&((*output)[pos]), reader->readBuffer(), toRead);
pos += toRead;
it.rp()->move(toRead);
reader->move(toRead);
}
if (reset) {

View File

@@ -90,12 +90,12 @@ std::unique_ptr<std::vector<uint8_t>> serializeProtoLocked(ProtoOutputStream& pr
std::unique_ptr<std::vector<uint8_t>> buffer(new std::vector<uint8_t>(bufferSize));
size_t pos = 0;
auto it = protoOutput.data();
while (it.readBuffer() != NULL) {
size_t toRead = it.currentToRead();
std::memcpy(&((*buffer)[pos]), it.readBuffer(), toRead);
sp<android::util::ProtoReader> reader = protoOutput.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&((*buffer)[pos]), reader->readBuffer(), toRead);
pos += toRead;
it.rp()->move(toRead);
reader->move(toRead);
}
return buffer;

View File

@@ -80,11 +80,11 @@ void writeAtomMetricStatsToStream(const std::pair<int64_t, StatsdStats::AtomMetr
template<class T>
bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) {
std::string pbBytes;
auto iter = protoOutput.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
pbBytes.append(reinterpret_cast<const char*>(iter.readBuffer()), toRead);
iter.rp()->move(toRead);
sp<android::util::ProtoReader> reader = protoOutput.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
pbBytes.append(reinterpret_cast<const char*>(reader->readBuffer()), toRead);
reader->move(toRead);
}
return message->ParseFromArray(pbBytes.c_str(), pbBytes.size());
}

View File

@@ -120,12 +120,12 @@ void getProtoData(const int64_t& rule_id, int64_t metricId, const MetricDimensio
protoData->resize(headerProto.size());
size_t pos = 0;
auto iter = headerProto.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&((*protoData)[pos]), iter.readBuffer(), toRead);
sp<android::util::ProtoReader> reader = headerProto.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&((*protoData)[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
}
} // namespace
@@ -152,15 +152,15 @@ bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int
uint8_t dest;
switch (config.dest()) {
case IncidentdDetails_Destination_AUTOMATIC:
dest = android::os::DEST_AUTOMATIC;
dest = android::os::PRIVACY_POLICY_AUTOMATIC;
break;
case IncidentdDetails_Destination_EXPLICIT:
dest = android::os::DEST_EXPLICIT;
dest = android::os::PRIVACY_POLICY_EXPLICIT;
break;
default:
dest = android::os::DEST_AUTOMATIC;
dest = android::os::PRIVACY_POLICY_AUTOMATIC;
}
incidentReport.setDest(dest);
incidentReport.setPrivacyPolicy(dest);
incidentReport.setReceiverPkg(config.receiver_pkg());

View File

@@ -24,6 +24,8 @@
#ifdef __ANDROID__
using android::util::ProtoReader;
namespace android {
namespace os {
namespace statsd {
@@ -252,12 +254,12 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) {
vector<uint8_t> outData;
outData.resize(protoOut.size());
size_t pos = 0;
auto iter = protoOut.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = protoOut.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
DimensionsValue result;
@@ -343,12 +345,12 @@ TEST(AtomMatcherTest, TestWriteDimensionToProto) {
vector<uint8_t> outData;
outData.resize(protoOut.size());
size_t pos = 0;
auto iter = protoOut.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = protoOut.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
DimensionsValue result;
@@ -405,12 +407,12 @@ TEST(AtomMatcherTest, TestWriteDimensionLeafNodesToProto) {
vector<uint8_t> outData;
outData.resize(protoOut.size());
size_t pos = 0;
auto iter = protoOut.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = protoOut.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
DimensionsValueTuple result;
@@ -458,12 +460,12 @@ TEST(AtomMatcherTest, TestWriteAtomToProto) {
vector<uint8_t> outData;
outData.resize(protoOutput.size());
size_t pos = 0;
auto iter = protoOutput.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = protoOutput.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
Atom result;

View File

@@ -26,6 +26,7 @@ namespace statsd {
using std::string;
using util::ProtoOutputStream;
using util::ProtoReader;
TEST(LogEventTest, TestLogParsing) {
LogEvent event1(1, 2000);
@@ -590,12 +591,12 @@ TEST(LogEventTest, TestBinaryFieldAtom) {
std::vector<uint8_t> outData;
outData.resize(proto.size());
size_t pos = 0;
auto iter = proto.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = proto.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
std::string result_str(outData.begin(), outData.end());
@@ -629,12 +630,12 @@ TEST(LogEventTest, TestBinaryFieldAtom_empty) {
std::vector<uint8_t> outData;
outData.resize(proto.size());
size_t pos = 0;
auto iter = proto.data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = proto.data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
std::string result_str(outData.begin(), outData.end());

View File

@@ -33,6 +33,7 @@ namespace os {
namespace statsd {
using android::util::ProtoOutputStream;
using android::util::ProtoReader;
#ifdef __ANDROID__
const string kApp1 = "app1.sharing.1";
@@ -179,12 +180,12 @@ static void protoOutputStreamToUidMapping(ProtoOutputStream* proto, UidMapping*
vector<uint8_t> bytes;
bytes.resize(proto->size());
size_t pos = 0;
auto iter = proto->data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&((bytes)[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = proto->data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
results->ParseFromArray(bytes.data(), bytes.size());
}

View File

@@ -36,7 +36,7 @@ TEST(IncidentReportArgsTest, testSerialization) {
args.addHeader(header1);
args.addHeader(header2);
args.setDest(1);
args.setPrivacyPolicy(1);
args.setReceiverPkg("com.android.os");
args.setReceiverCls("com.android.os.Receiver");
@@ -56,10 +56,10 @@ TEST(IncidentReportArgsTest, testSerialization) {
sections.insert(1000);
sections.insert(1001);
EXPECT_EQ(sections, args2.sections());
EXPECT_EQ(1, args2.dest());
EXPECT_EQ(1, args2.getPrivacyPolicy());
EXPECT_EQ(String16("com.android.os"), args2.receiverPkg());
EXPECT_EQ(String16("com.android.os.Receiver"), args2.receiverCls());
EXPECT_EQ(string("com.android.os"), args2.receiverPkg());
EXPECT_EQ(string("com.android.os.Receiver"), args2.receiverCls());
vector<vector<uint8_t>> headers;
headers.push_back(header1);
@@ -69,4 +69,4 @@ TEST(IncidentReportArgsTest, testSerialization) {
} // namespace statsd
} // namespace os
} // namespace android
} // namespace android

View File

@@ -26,6 +26,7 @@
using namespace testing;
using android::sp;
using android::util::ProtoReader;
using std::make_shared;
using std::set;
using std::shared_ptr;
@@ -2730,12 +2731,12 @@ static StatsLogReport outputStreamToProto(ProtoOutputStream* proto) {
vector<uint8_t> bytes;
bytes.resize(proto->size());
size_t pos = 0;
auto iter = proto->data();
while (iter.readBuffer() != NULL) {
size_t toRead = iter.currentToRead();
std::memcpy(&((bytes)[pos]), iter.readBuffer(), toRead);
sp<ProtoReader> reader = proto->data();
while (reader->readBuffer() != NULL) {
size_t toRead = reader->currentToRead();
std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead);
pos += toRead;
iter.rp()->move(toRead);
reader->move(toRead);
}
StatsLogReport report;

View File

@@ -339,6 +339,8 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
}
public void writeToParcel(Parcel out, int flags) {
// WARNING: If you modify this function, also update
// frameworks/base/libs/services/src/content/ComponentName.cpp.
out.writeString(mPackage);
out.writeString(mClass);
}

View File

@@ -17,32 +17,57 @@
package android.os;
import android.os.IIncidentReportStatusListener;
import android.os.IncidentManager;
import android.os.IncidentReportArgs;
/**
* Binder interface to report system health incidents.
* {@hide}
*/
oneway interface IIncidentManager {
interface IIncidentManager {
/**
* Takes a report with the given args, reporting status to the optional listener.
*
* When the report is completed, the system report listener will be notified.
*/
void reportIncident(in IncidentReportArgs args);
oneway void reportIncident(in IncidentReportArgs args);
/**
* Takes a report with the given args, reporting status to the optional listener.
*
* When the report is completed, the system report listener will be notified.
*/
void reportIncidentToStream(in IncidentReportArgs args,
oneway void reportIncidentToStream(in IncidentReportArgs args,
@nullable IIncidentReportStatusListener listener,
FileDescriptor stream);
/**
* Tell the incident daemon that the android system server is up and running.
*/
void systemRunning();
oneway void systemRunning();
/**
* List the incident reports for the given ComponentName. This is called
* via IncidentCompanion, which validates that the package name matches
* the caller.
*/
List<String> getIncidentReportList(String pkg, String cls);
/**
* Get the IncidentReport object.
*/
IncidentManager.IncidentReport getIncidentReport(String pkg, String cls, String id);
/**
* Reduce the refcount on this receiver. This is called
* via IncidentCompanion, which validates that the package name matches
* the caller.
*/
void deleteIncidentReports(String pkg, String cls, String id);
/**
* Delete all incident reports for this package.
*/
void deleteAllIncidentReports(String pkg);
}

View File

@@ -74,6 +74,13 @@ public class IncidentManager {
*/
public static final String URI_PARAM_ID = "id";
/**
* Query parameter for the uris for the incident report id.
*
* @hide
*/
public static final String URI_PARAM_REPORT_ID = "r";
/**
* Query parameter for the uris for the pending report id.
*
@@ -96,6 +103,13 @@ public class IncidentManager {
*/
public static final String URI_PARAM_FLAGS = "flags";
/**
* Query parameter for the uris for the pending report id.
*
* @hide
*/
public static final String URI_PARAM_RECEIVER_CLASS = "receiver";
/**
* Do the confirmation with a dialog instead of the default, which is a notification.
* It is possible for the dialog to be downgraded to a notification in some cases.
@@ -243,12 +257,12 @@ public class IncidentManager {
@SystemApi
@TestApi
public static class IncidentReport implements Parcelable, Closeable {
private final long mTimestampMs;
private final long mTimestampNs;
private final int mPrivacyPolicy;
private ParcelFileDescriptor mFileDescriptor;
public IncidentReport(Parcel in) {
mTimestampMs = in.readLong();
mTimestampNs = in.readLong();
mPrivacyPolicy = in.readInt();
if (in.readInt() != 0) {
mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
@@ -272,10 +286,10 @@ public class IncidentManager {
/**
* Get the time at which this incident report was taken, in wall clock time
* ({@link System#uptimeMillis System.uptimeMillis()} time base).
* ({@link System#currenttimeMillis System.currenttimeMillis()} time base).
*/
public long getTimestamp() {
return mTimestampMs;
return mTimestampNs / 1000000;
}
/**
@@ -310,7 +324,7 @@ public class IncidentManager {
* @inheritDoc
*/
public void writeToParcel(Parcel out, int flags) {
out.writeLong(mTimestampMs);
out.writeLong(mTimestampNs);
out.writeInt(mPrivacyPolicy);
if (mFileDescriptor != null) {
out.writeInt(1);
@@ -397,8 +411,8 @@ public class IncidentManager {
public void requestAuthorization(int callingUid, String callingPackage, int flags,
AuthListener listener) {
try {
getCompanionServiceLocked().authorizeReport(callingUid, callingPackage, flags,
listener.mBinder);
getCompanionServiceLocked().authorizeReport(callingUid, callingPackage, null, null,
flags, listener.mBinder);
} catch (RemoteException ex) {
// System process going down
throw new RuntimeException(ex);
@@ -477,7 +491,19 @@ public class IncidentManager {
android.Manifest.permission.PACKAGE_USAGE_STATS
})
public @NonNull List<Uri> getIncidentReportList(String receiverClass) {
throw new RuntimeException("implement me");
List<String> strings;
try {
strings = getCompanionServiceLocked().getIncidentReportList(
mContext.getPackageName(), receiverClass);
} catch (RemoteException ex) {
throw new RuntimeException("System server or incidentd going down", ex);
}
final int size = strings.size();
ArrayList<Uri> result = new ArrayList(size);
for (int i = 0; i < size; i++) {
result.add(Uri.parse(strings.get(i)));
}
return result;
}
/**
@@ -493,20 +519,74 @@ public class IncidentManager {
android.Manifest.permission.PACKAGE_USAGE_STATS
})
public @Nullable IncidentReport getIncidentReport(Uri uri) {
throw new RuntimeException("implement me");
final String pkg = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
if (pkg == null) {
throw new RuntimeException("Invalid URI: No "
+ URI_PARAM_CALLING_PACKAGE + " parameter. " + uri);
}
final String cls = uri.getQueryParameter(URI_PARAM_RECEIVER_CLASS);
if (cls == null) {
throw new RuntimeException("Invalid URI: No "
+ URI_PARAM_RECEIVER_CLASS + " parameter. " + uri);
}
final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID);
if (cls == null) {
// If there's no report id, it's a bug report, so we can't return the incident
// report.
return null;
}
try {
return getCompanionServiceLocked().getIncidentReport(pkg, cls, id);
} catch (RemoteException ex) {
throw new RuntimeException("System server or incidentd going down", ex);
}
}
/**
* Delete the incident report with the given URI id.
*
* @param uri Identifier of the incident report.
* @param uri Identifier of the incident report. Pass null to delete all
* incident reports owned by this application.
*/
@RequiresPermission(allOf = {
android.Manifest.permission.DUMP,
android.Manifest.permission.PACKAGE_USAGE_STATS
})
public void deleteIncidentReports(Uri uri) {
throw new RuntimeException("implement me");
if (uri == null) {
try {
getCompanionServiceLocked().deleteAllIncidentReports(mContext.getPackageName());
} catch (RemoteException ex) {
throw new RuntimeException("System server or incidentd going down", ex);
}
} else {
final String pkg = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
if (pkg == null) {
throw new RuntimeException("Invalid URI: No "
+ URI_PARAM_CALLING_PACKAGE + " parameter. " + uri);
}
final String cls = uri.getQueryParameter(URI_PARAM_RECEIVER_CLASS);
if (cls == null) {
throw new RuntimeException("Invalid URI: No "
+ URI_PARAM_RECEIVER_CLASS + " parameter. " + uri);
}
final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID);
if (cls == null) {
throw new RuntimeException("Invalid URI: No "
+ URI_PARAM_REPORT_ID + " parameter. " + uri);
}
try {
getCompanionServiceLocked().deleteIncidentReports(pkg, cls, id);
} catch (RemoteException ex) {
throw new RuntimeException("System server or incidentd going down", ex);
}
}
}
private void reportIncidentInternal(IncidentReportArgs args) {

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
import "frameworks/base/core/proto/android/app/pendingintent.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;

View File

@@ -19,7 +19,7 @@ option java_multiple_files = true;
package android.app;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.app.Notification object.

View File

@@ -20,7 +20,7 @@ option java_multiple_files = true;
package android.app;
import "frameworks/base/core/proto/android/media/audioattributes.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.app.NotificationChannel object.

View File

@@ -20,7 +20,7 @@ option java_multiple_files = true;
package android.app;
import "frameworks/base/core/proto/android/app/notification_channel.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.app.NotificationChannelGroup object.

View File

@@ -19,7 +19,7 @@ option java_multiple_files = true;
package android.app;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.app.NotificationManager.Policy object.

View File

@@ -20,7 +20,7 @@ option java_multiple_files = true;
package android.app;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.app.PendingIntent object.

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.app;

View File

@@ -20,7 +20,7 @@ option java_multiple_files = true;
package android.app;
import "frameworks/base/core/proto/android/graphics/rect.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/** Proto representation for WindowConfiguration.java class. */
message WindowConfigurationProto {

View File

@@ -21,7 +21,7 @@ option java_multiple_files = true;
import "frameworks/base/core/proto/android/content/clipdescription.proto";
import "frameworks/base/core/proto/android/content/intent.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// An android.content.ClipData object.
message ClipDataProto {

View File

@@ -20,7 +20,7 @@ package android.content;
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/persistablebundle.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// An android.content.ClipDescription object.
message ClipDescriptionProto {

View File

@@ -19,7 +19,7 @@ option java_multiple_files = true;
package android.content;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.content.ComponentName object.

View File

@@ -21,7 +21,7 @@ package android.content;
import "frameworks/base/core/proto/android/app/window_configuration.proto";
import "frameworks/base/core/proto/android/content/locale.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android Configuration object.

View File

@@ -16,7 +16,7 @@
syntax = "proto2";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;

View File

@@ -21,7 +21,7 @@ option java_multiple_files = true;
import "frameworks/base/core/proto/android/content/component_name.proto";
import "frameworks/base/core/proto/android/os/patternmatcher.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// Next Tag: 13
message IntentProto {

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.content;

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.content.pm;

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
package android.graphics;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
package android.graphics;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;

View File

@@ -19,7 +19,7 @@ package com.android.internal.util;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message LocalLogProto {
option (.android.msg_privacy).dest = DEST_EXPLICIT;

View File

@@ -19,7 +19,7 @@ option java_multiple_files = true;
package android.media;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.media.AudioAttributes object.

View File

@@ -19,7 +19,7 @@ option java_multiple_files = true;
package android.net;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.net.Network object.

View File

@@ -20,7 +20,7 @@ package android.net;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.net.NetworkCapabilities object.

View File

@@ -21,7 +21,7 @@ package android.net;
option java_multiple_files = true;
import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
/**
* An android.net.NetworkRequest object.

View File

@@ -19,7 +19,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message BackTraceProto {
option (android.msg_privacy).dest = DEST_EXPLICIT;

View File

@@ -22,7 +22,7 @@ package android.os;
import "frameworks/base/core/proto/android/app/job/enums.proto";
import "frameworks/base/core/proto/android/os/powermanager.proto";
import "frameworks/base/core/proto/android/telephony/enums.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message BatteryStatsProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;

View File

@@ -20,7 +20,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message BatteryTypeProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;

View File

@@ -19,7 +19,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// An android.os.Bundle object.
message BundleProto {

View File

@@ -17,7 +17,7 @@ syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.os;

View File

@@ -17,7 +17,7 @@ syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.os;

View File

@@ -22,7 +22,9 @@ import "frameworks/base/core/proto/android/os/batterytype.proto";
import "frameworks/base/core/proto/android/os/cpufreq.proto";
import "frameworks/base/core/proto/android/os/cpuinfo.proto";
import "frameworks/base/core/proto/android/os/data.proto";
import "frameworks/base/core/proto/android/os/header.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/metadata.proto";
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
import "frameworks/base/core/proto/android/os/procrank.proto";
import "frameworks/base/core/proto/android/os/ps.proto";
@@ -49,10 +51,8 @@ import "frameworks/base/core/proto/android/service/procstats.proto";
import "frameworks/base/core/proto/android/service/usb.proto";
import "frameworks/base/core/proto/android/util/event_log_tags.proto";
import "frameworks/base/core/proto/android/util/log.proto";
import "frameworks/base/libs/incident/proto/android/os/header.proto";
import "frameworks/base/libs/incident/proto/android/os/metadata.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/libs/incident/proto/android/section.proto";
import "frameworks/base/core/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/section.proto";
package android.os;

View File

@@ -17,7 +17,7 @@ syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.os;

View File

@@ -20,7 +20,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/messagequeue.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message LooperProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
package android.os;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;
message MessageProto {

View File

@@ -20,7 +20,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/message.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message MessageQueueProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;

View File

@@ -27,7 +27,7 @@ message IncidentMetadata {
// The id of the incident report.
optional int64 report_id = 1;
// The sequence number of the report.
// No longer filled in as of Qt.
optional int32 sequence_number = 2;
// privacy level of the incident report.
@@ -38,8 +38,10 @@ message IncidentMetadata {
}
optional Destination dest = 3;
// No longer filled in as of Qt.
optional int32 request_size = 4;
// No longer filled in as of Qt.
optional bool use_dropbox = 5;
// stats of each section taken in this incident report.

View File

@@ -17,7 +17,7 @@ syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.os;

View File

@@ -16,7 +16,7 @@
syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.os;

View File

@@ -19,7 +19,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// An android.os.PersistableBundle object.
message PersistableBundleProto {

View File

@@ -20,7 +20,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/worksource.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message PowerManagerProto {
/* User activity events in PowerManager.java. */

View File

@@ -19,7 +19,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// Memory usage of running processes
message ProcrankProto {

View File

@@ -20,7 +20,7 @@ package android.os;
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message PsProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;

View File

@@ -19,7 +19,7 @@ option java_multiple_files = true;
package android.os;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// Dump of statsd report data (dumpsys stats --proto).
message StatsDataDumpProto {

View File

@@ -18,7 +18,7 @@ syntax = "proto2";
option java_multiple_files = true;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
package android.os;

View File

@@ -17,7 +17,7 @@
syntax = "proto2";
package android.os;
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;

View File

@@ -23,7 +23,7 @@ option java_outer_classname = "SettingsServiceProto";
import "frameworks/base/core/proto/android/providers/settings/global.proto";
import "frameworks/base/core/proto/android/providers/settings/secure.proto";
import "frameworks/base/core/proto/android/providers/settings/system.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
message SettingsServiceDumpProto {
option (android.msg_privacy).dest = DEST_EXPLICIT;

View File

@@ -20,7 +20,7 @@ package android.providers.settings;
option java_multiple_files = true;
import "frameworks/base/core/proto/android/providers/settings/common.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// Note: it's a conscious decision to add each setting as a separate field. This
// allows annotating each setting with its own privacy tag.

View File

@@ -20,7 +20,7 @@ package android.providers.settings;
option java_multiple_files = true;
import "frameworks/base/core/proto/android/providers/settings/common.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/privacy.proto";
// Note: it's a conscious decision to add each setting as a separate field. This
// allows annotating each setting with its own privacy tag.

Some files were not shown because too many files have changed in this diff Show More