Merge "incidentd can now handle multiple callers asking it for incident reports"
This commit is contained in:
47
Android.bp
47
Android.bp
@@ -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/*",
|
||||
|
||||
@@ -29,6 +29,10 @@ cc_binary {
|
||||
"libincident",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libplatformprotos",
|
||||
],
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
440
cmds/incidentd/src/Broadcaster.cpp
Normal file
440
cmds/incidentd/src/Broadcaster.cpp
Normal 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
|
||||
|
||||
|
||||
127
cmds/incidentd/src/Broadcaster.h
Normal file
127
cmds/incidentd/src/Broadcaster.h
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
375
cmds/incidentd/src/PrivacyFilter.cpp
Normal file
375
cmds/incidentd/src/PrivacyFilter.cpp
Normal 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
|
||||
101
cmds/incidentd/src/PrivacyFilter.h
Normal file
101
cmds/incidentd/src/PrivacyFilter.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
844
cmds/incidentd/src/WorkDirectory.cpp
Normal file
844
cmds/incidentd/src/WorkDirectory.cpp
Normal 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, ×tampNs)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Make the ReportFile object, and then see if it's valid and for pkg and cls.
|
||||
sp<ReportFile> result = new ReportFile(this, timestampNs,
|
||||
make_filename(timestampNs, EXTENSION_ENVELOPE),
|
||||
make_filename(timestampNs, EXTENSION_DATA));
|
||||
|
||||
err = result->tryLoadEnvelope();
|
||||
if (err != NO_ERROR) {
|
||||
ALOGW("Can't open envelope file for report %s/%s %s", pkg.c_str(), cls.c_str(), id.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ReportFileProto& envelope = result->getEnvelope();
|
||||
const size_t reportCount = envelope.report_size();
|
||||
for (int i = 0; i < reportCount; i++) {
|
||||
const ReportFileProto_Report& report = envelope.report(i);
|
||||
if (report.pkg() == pkg && report.cls() == cls) {
|
||||
if (args != nullptr) {
|
||||
get_args_from_report(args, report);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool WorkDirectory::hasMore(int64_t after) {
|
||||
unique_lock<mutex> lock(mLock);
|
||||
|
||||
map<string,WorkDirectoryEntry> files;
|
||||
get_directory_contents_locked(&files, after);
|
||||
return files.size() > 0;
|
||||
}
|
||||
|
||||
void WorkDirectory::commit(const sp<ReportFile>& report, const string& pkg, const string& cls) {
|
||||
status_t err;
|
||||
ALOGI("Committing report %s for %s/%s", report->getId().c_str(), pkg.c_str(), cls.c_str());
|
||||
|
||||
unique_lock<mutex> lock(mLock);
|
||||
|
||||
// Load the envelope here inside the lock.
|
||||
err = report->loadEnvelope();
|
||||
|
||||
report->removeReport(pkg, cls);
|
||||
|
||||
delete_files_for_report_if_necessary(report);
|
||||
}
|
||||
|
||||
void WorkDirectory::commitAll(const string& pkg) {
|
||||
status_t err;
|
||||
ALOGI("All reports for %s", pkg.c_str());
|
||||
|
||||
unique_lock<mutex> lock(mLock);
|
||||
|
||||
map<string,WorkDirectoryEntry> files;
|
||||
get_directory_contents_locked(&files, 0);
|
||||
|
||||
for (map<string,WorkDirectoryEntry>::iterator it = files.begin();
|
||||
it != files.end(); it++) {
|
||||
sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs,
|
||||
it->second.envelope, it->second.data);
|
||||
|
||||
err = reportFile->loadEnvelope();
|
||||
if (err != NO_ERROR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
reportFile->removeReports(pkg);
|
||||
|
||||
delete_files_for_report_if_necessary(reportFile);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkDirectory::remove(const sp<ReportFile>& report) {
|
||||
unique_lock<mutex> lock(mLock);
|
||||
// Set this to false to leave files around for debugging.
|
||||
if (DO_UNLINK) {
|
||||
unlink(report->getDataFileName().c_str());
|
||||
unlink(report->getEnvelopeFileName().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t WorkDirectory::make_timestamp_ns_locked() {
|
||||
// Guarantee that we don't have duplicate timestamps.
|
||||
// This is a little bit lame, but since reports are created on the
|
||||
// same thread and are kinda slow we'll seldomly actually hit the
|
||||
// condition. The bigger risk is the clock getting reset and causing
|
||||
// a collision. In that case, we'll just make incident reporting a
|
||||
// little bit slower. Nobody will notice if we just loop until we
|
||||
// have a unique file name.
|
||||
int64_t timestampNs = 0;
|
||||
do {
|
||||
struct timespec spec;
|
||||
if (timestampNs > 0) {
|
||||
spec.tv_sec = 0;
|
||||
spec.tv_nsec = 1;
|
||||
nanosleep(&spec, nullptr);
|
||||
}
|
||||
clock_gettime(CLOCK_REALTIME, &spec);
|
||||
timestampNs = (spec.tv_sec) * 1000 + spec.tv_nsec;
|
||||
} while (file_exists_locked(timestampNs));
|
||||
return timestampNs;
|
||||
}
|
||||
|
||||
/**
|
||||
* It is required to hold the lock here so in case someone else adds it
|
||||
* our result is still correct for the caller.
|
||||
*/
|
||||
bool WorkDirectory::file_exists_locked(int64_t timestampNs) {
|
||||
const string filename = make_filename(timestampNs, EXTENSION_ENVELOPE);
|
||||
struct stat st;
|
||||
return stat(filename.c_str(), &st) == 0;
|
||||
}
|
||||
|
||||
string WorkDirectory::make_filename(int64_t timestampNs, const string& extension) {
|
||||
// Zero pad the timestamp so it can also be alpha sorted.
|
||||
stringstream result;
|
||||
result << mDirectory << '/' << setfill('0') << setw(20) << timestampNs << extension;
|
||||
return result.str();
|
||||
}
|
||||
|
||||
off_t WorkDirectory::get_directory_contents_locked(map<string,WorkDirectoryEntry>* files,
|
||||
int64_t after) {
|
||||
DIR* dir;
|
||||
struct dirent* entry;
|
||||
|
||||
if ((dir = opendir(mDirectory.c_str())) == NULL) {
|
||||
ALOGE("Couldn't open incident directory: %s", mDirectory.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
string dirbase(mDirectory);
|
||||
if (mDirectory[dirbase.size() - 1] != '/') dirbase += "/";
|
||||
|
||||
off_t totalSize = 0;
|
||||
|
||||
// Enumerate, count and add up size
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
string entryname = entry->d_name; // local to this dir
|
||||
string filename = dirbase + entryname; // fully qualified
|
||||
|
||||
bool isEnvelope = ends_with(entryname, EXTENSION_ENVELOPE);
|
||||
bool isData = ends_with(entryname, EXTENSION_DATA);
|
||||
|
||||
// If the file isn't one of our files, just ignore it. Otherwise,
|
||||
// sum up the sizes.
|
||||
if (isEnvelope || isData) {
|
||||
string timestamp = strip_extension(entryname);
|
||||
|
||||
int64_t timestampNs;
|
||||
if (!parse_timestamp_ns(timestamp, ×tampNs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (after == 0 || timestampNs > after) {
|
||||
struct stat st;
|
||||
if (stat(filename.c_str(), &st) != 0) {
|
||||
ALOGE("Unable to stat file %s", filename.c_str());
|
||||
continue;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WorkDirectoryEntry& entry = (*files)[timestamp];
|
||||
if (isEnvelope) {
|
||||
entry.envelope = filename;
|
||||
} else if (isData) {
|
||||
entry.data = filename;
|
||||
}
|
||||
entry.timestampNs = timestampNs;
|
||||
entry.size += st.st_size;
|
||||
totalSize += st.st_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
// Now check if there are any data files that don't have envelope files.
|
||||
// If there are, then just go ahead and delete them now. Don't wait for
|
||||
// a cleaning.
|
||||
|
||||
if (DO_UNLINK) {
|
||||
map<string,WorkDirectoryEntry>::iterator it = files->begin();
|
||||
while (it != files->end()) {
|
||||
if (it->second.envelope.length() == 0) {
|
||||
unlink(it->second.data.c_str());
|
||||
it = files->erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
void WorkDirectory::clean_directory_locked() {
|
||||
DIR* dir;
|
||||
struct dirent* entry;
|
||||
struct stat st;
|
||||
|
||||
// Map of filename without extension to the entries about it. Conveniently,
|
||||
// this also keeps the list sorted by filename, which is a timestamp.
|
||||
map<string,WorkDirectoryEntry> files;
|
||||
off_t totalSize = get_directory_contents_locked(&files, 0);
|
||||
if (totalSize < 0) {
|
||||
return;
|
||||
}
|
||||
int totalCount = files.size();
|
||||
|
||||
// Count or size is less than max, then we're done.
|
||||
if (totalSize < mMaxDiskUsageBytes && totalCount < mMaxFileCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove files until we're under our limits.
|
||||
if (DO_UNLINK) {
|
||||
for (map<string, WorkDirectoryEntry>::const_iterator it = files.begin();
|
||||
it != files.end() && (totalSize >= mMaxDiskUsageBytes
|
||||
|| totalCount >= mMaxFileCount);
|
||||
it++) {
|
||||
unlink(it->second.envelope.c_str());
|
||||
unlink(it->second.data.c_str());
|
||||
totalSize -= it->second.size;
|
||||
totalCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WorkDirectory::delete_files_for_report_if_necessary(const sp<ReportFile>& report) {
|
||||
if (report->getEnvelope().report_size() == 0) {
|
||||
ALOGI("Report %s is finished. Deleting from storage.", report->getId().c_str());
|
||||
if (DO_UNLINK) {
|
||||
unlink(report->getDataFileName().c_str());
|
||||
unlink(report->getEnvelopeFileName().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
void get_args_from_report(IncidentReportArgs* out, const ReportFileProto_Report& report) {
|
||||
out->setPrivacyPolicy(report.privacy_policy());
|
||||
out->setAll(report.all_sections());
|
||||
out->setReceiverPkg(report.pkg());
|
||||
out->setReceiverCls(report.cls());
|
||||
|
||||
const int sectionCount = report.section_size();
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
out->addSection(report.section(i));
|
||||
}
|
||||
|
||||
const int headerCount = report.header_size();
|
||||
for (int i = 0; i < headerCount; i++) {
|
||||
const string& header = report.header(i);
|
||||
vector<uint8_t> vec(header.begin(), header.end());
|
||||
out->addHeader(vec);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace incidentd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
276
cmds/incidentd/src/WorkDirectory.h
Normal file
276
cmds/incidentd/src/WorkDirectory.h
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
84
cmds/incidentd/src/proto_util.cpp
Normal file
84
cmds/incidentd/src/proto_util.cpp
Normal 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
|
||||
|
||||
52
cmds/incidentd/src/proto_util.h
Normal file
52
cmds/incidentd/src/proto_util.h
Normal 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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
107
cmds/incidentd/src/report_file.proto
Normal file
107
cmds/incidentd/src/report_file.proto
Normal 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
302
cmds/incidentd/tests/PrivacyFilter_test.cpp
Normal file
302
cmds/incidentd/tests/PrivacyFilter_test.cpp
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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[] = {
|
||||
§ion1,
|
||||
§ion2,
|
||||
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
|
||||
|
||||
28
cmds/incidentd/tests/test_proto.proto
Normal file
28
cmds/incidentd/tests/test_proto.proto
Normal 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;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ cc_defaults {
|
||||
|
||||
static_libs: [
|
||||
"libhealthhalutils",
|
||||
"libplatformprotos",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
12
cmds/statsd/src/external/GpuStatsPuller.cpp
vendored
12
cmds/statsd/src/external/GpuStatsPuller.cpp
vendored
@@ -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 "";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user