diff options
159 files changed, 5736 insertions, 2060 deletions
diff --git a/Android.bp b/Android.bp index 8a3f76c40e86..b9b1bd87e80d 100644 --- a/Android.bp +++ b/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/*", diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp index 2a5ec5bfacaf..f56f101465eb 100644 --- a/cmds/incident/Android.bp +++ b/cmds/incident/Android.bp @@ -29,6 +29,10 @@ cc_binary { "libincident", ], + static_libs: [ + "libplatformprotos", + ], + cflags: [ "-Wall", "-Werror", diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp index cdec6a01d086..93e592c9c01b 100644 --- a/cmds/incident/main.cpp +++ b/cmds/incident/main.cpp @@ -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,25 +150,50 @@ 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 { diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp index 3dc10939fed7..8ac11df5e5ad 100644 --- a/cmds/incidentd/Android.bp +++ b/cmds/incidentd/Android.bp @@ -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 { diff --git a/cmds/incidentd/src/Broadcaster.cpp b/cmds/incidentd/src/Broadcaster.cpp new file mode 100644 index 000000000000..39e5393e1f81 --- /dev/null +++ b/cmds/incidentd/src/Broadcaster.cpp @@ -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 + + diff --git a/cmds/incidentd/src/Broadcaster.h b/cmds/incidentd/src/Broadcaster.h new file mode 100644 index 000000000000..933029709fb6 --- /dev/null +++ b/cmds/incidentd/src/Broadcaster.h @@ -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 + diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index 04819ec75a09..685c0677c8fa 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -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); +} + +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); +} -EncodedBuffer::iterator FdBuffer::data() const { return mBuffer.begin(); } +size_t FdBuffer::size() const { + return mBuffer->size(); +} + +sp<EncodedBuffer> FdBuffer::data() const { + return mBuffer; +} } // namespace incidentd } // namespace os diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h index 20deefd02558..a3493604f425 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -64,6 +64,21 @@ public: 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. */ bool timedOut() const { return mTimedOut; } @@ -89,17 +104,12 @@ public: int64_t durationMs() const { return mFinishTime - mStartTime; } /** - * Reader API for data stored in FdBuffer - */ - EncodedBuffer::iterator data() const; - - /** - * Return the internal buffer, don't call unless you are familiar with EncodedBuffer. + * Get the EncodedBuffer inside. */ - EncodedBuffer* getInternalBuffer() { return &mBuffer; } + sp<EncodedBuffer> data() const; private: - EncodedBuffer mBuffer; + sp<EncodedBuffer> mBuffer; int64_t mStartTime; int64_t mFinishTime; bool mTimedOut; diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index f8fb4a676ba0..4ba31b45e81c 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -19,7 +19,7 @@ #include "IncidentService.h" #include "FdBuffer.h" -#include "PrivacyBuffer.h" +#include "PrivacyFilter.h" #include "Reporter.h" #include "incidentd_util.h" #include "section_list.h" @@ -35,9 +35,12 @@ #include <unistd.h> -enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 }; +enum { + WHAT_TAKE_REPORT = 1, + WHAT_SEND_BROADCASTS = 2 +}; -#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL) +#define DEFAULT_DELAY_NS (1000000000LL) #define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024) // 20MB #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day @@ -53,6 +56,7 @@ namespace 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::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::scheduleSendBacklogToDropbox() { +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(); +void ReportHandler::take_report() { + // Cycle the batch + sp<ReportBatch> batch; + { + unique_lock<mutex> lock(mLock); + batch = mBatch; + mBatch = new ReportBatch(); + } - // 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); + 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; } diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h index c63a183b70b9..6481321bbd69 100644 --- a/cmds/incidentd/src/IncidentService.h +++ b/cmds/incidentd/src/IncidentService.h @@ -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 { @@ -38,60 +41,74 @@ 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 permission controller approve the reports. + */ + void schedule_send_approvals_locked(); /** - * Schedules a dropbox task mBacklogDelay nanoseconds from now. + * Sends the approvals to the PermissionController */ - void schedule_send_backlog_to_dropbox_locked(); + void send_approvals(); /** - * Sends the backlog to the dropbox service. + * 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 send_backlog_to_dropbox(); + 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; diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp index 6e55f906087c..386303b038e7 100644 --- a/cmds/incidentd/src/Privacy.cpp +++ b/cmds/incidentd/src/Privacy.cpp @@ -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; } +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::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) const { - uint8_t policy = privacy != NULL ? privacy->dest : defaultDest; - return allowDest(dest, policy); +bool PrivacySpec::operator<(const PrivacySpec& that) const { + return mPolicy < that.mPolicy; } -bool PrivacySpec::RequireAll() const { return dest == android::os::DEST_LOCAL; } +bool PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) const { + uint8_t check = privacy != NULL ? privacy->policy : defaultDest; + return isAllowed(mPolicy, check); +} -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::RequireAll() const { + return mPolicy == android::os::PRIVACY_POLICY_LOCAL; } } // namespace incidentd diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h index a0159d9a8649..fc8caae7fc60 100644 --- a/cmds/incidentd/src/Privacy.h +++ b/cmds/incidentd/src/Privacy.h @@ -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 diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp deleted file mode 100644 index 08f535db01fa..000000000000 --- a/cmds/incidentd/src/PrivacyBuffer.cpp +++ /dev/null @@ -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 diff --git a/cmds/incidentd/src/PrivacyBuffer.h b/cmds/incidentd/src/PrivacyBuffer.h deleted file mode 100644 index eac3862b2cab..000000000000 --- a/cmds/incidentd/src/PrivacyBuffer.h +++ /dev/null @@ -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
\ No newline at end of file diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp new file mode 100644 index 000000000000..7126322575d5 --- /dev/null +++ b/cmds/incidentd/src/PrivacyFilter.cpp @@ -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 diff --git a/cmds/incidentd/src/PrivacyFilter.h b/cmds/incidentd/src/PrivacyFilter.h new file mode 100644 index 000000000000..76b28498a0ac --- /dev/null +++ b/cmds/incidentd/src/PrivacyFilter.h @@ -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 diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 8f62da202606..7a08dd654d9e 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -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 { -// ================================================================================ -ReportRequest::ReportRequest(const IncidentReportArgs& a, - const sp<IIncidentReportStatusListener>& l, int f) - : args(a), listener(l), fd(f), err(NO_ERROR) {} +using namespace android::util; -ReportRequest::~ReportRequest() { - if (fd >= 0) { - // clean up the opened file descriptor - close(fd); +/** + * 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; + } } } -bool ReportRequest::ok() { return fd >= 0 && err == NO_ERROR; } +// 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); +} + // ================================================================================ -ReportRequestSet::ReportRequestSet() - : mRequests(), mSections(), mMainFd(-1), mMainDest(-1), mMetadata(), mSectionStats() {} +class PersistedFilterFd : public FilterFd { +public: + PersistedFilterFd(uint8_t privacyPolicy, int fd, const sp<ReportFile>& reportFile); + + virtual void onWriteError(status_t err); -ReportRequestSet::~ReportRequestSet() {} +private: + sp<ReportFile> mReportFile; +}; -// 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); +PersistedFilterFd::PersistedFilterFd(uint8_t privacyPolicy, int fd, + const sp<ReportFile>& reportFile) + :FilterFd(privacyPolicy, fd), + mReportFile(reportFile) { } -void ReportRequestSet::setMainFd(int fd) { - mMainFd = fd; - mMetadata.set_use_dropbox(fd > 0); +void PersistedFilterFd::onWriteError(status_t err) { + mReportFile->setWriteError(err); } -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; + +// ================================================================================ +ReportRequest::ReportRequest(const IncidentReportArgs& a, + const sp<IIncidentReportStatusListener>& listener, int fd) + :args(a), + mListener(listener), + mFd(fd), + mIsStreaming(fd >= 0), + mStatus(NO_ERROR) { +} + +ReportRequest::~ReportRequest() { + if (mIsStreaming && mFd >= 0) { + // clean up the opened file descriptor + close(mFd); } } -bool ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); } +bool ReportRequest::ok() { + return mFd >= 0 && mStatus == NO_ERROR; +} -IncidentMetadata::SectionStats* ReportRequestSet::sectionStats(int id) { - if (mSectionStats.find(id) == mSectionStats.end()) { - IncidentMetadata::SectionStats stats; - stats.set_id(id); - mSectionStats[id] = stats; +void ReportRequest::closeFd() { + if (mIsStreaming && mFd >= 0) { + close(mFd); + mFd = -1; } - return &mSectionStats[id]; } // ================================================================================ -Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; }; - -Reporter::Reporter(const char* directory) : batch() { - char buf[100]; +ReportBatch::ReportBatch() {} + +ReportBatch::~ReportBatch() {} + +void ReportBatch::addPersistedReport(const IncidentReportArgs& args) { + ComponentName component(args.receiverPkg(), args.receiverCls()); + map<ComponentName, sp<ReportRequest>>::iterator found = mPersistedRequests.find(component); + if (found == mPersistedRequests.end()) { + // not found + mPersistedRequests[component] = new ReportRequest(args, nullptr, -1); + } else { + // found + sp<ReportRequest> request = found->second; + request->args.merge(args); + } +} - mMaxSize = 30 * 1024 * 1024; // incident reports can take up to 30MB on disk - mMaxCount = 100; +void ReportBatch::addStreamingReport(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, int streamFd) { + mStreamingRequests.push_back(new ReportRequest(args, listener, streamFd)); +} - // string ends up with '/' is a directory - String8 dir = String8(directory); - if (directory[dir.size() - 1] != '/') dir += "/"; - mIncidentDirectory = dir.string(); +bool ReportBatch::empty() const { + return mPersistedRequests.size() == 0 && mStreamingRequests.size() == 0; +} - // 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; +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; + } } -Reporter::~Reporter() {} +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); + } +} -Reporter::run_report_status_t 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; - std::string buildType = android::base::GetProperty("ro.build.type", ""); - const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng"; +void ReportBatch::forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func) { + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + func(*request); + } +} - // 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; +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); } } - if (needMainFd) { - // Create the directory - if (!isTest) err = create_directory(mIncidentDirectory); - if (err != NO_ERROR) { - goto DONE; + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + sp<IIncidentReportStatusListener> listener = (*request)->getListener(); + if (listener != nullptr) { + func(listener); } + } +} - // 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; +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); + } } - - // Add to the set - batch.setMainFd(mainFd); - batch.setMainDest(mainDest); } - - // Tell everyone that we're starting. - for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { - if ((*it)->listener != NULL) { - (*it)->listener->onReportStarted(); + 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); + } } } +} - // Write the incident headers - headers.Execute(&batch); +void ReportBatch::getCombinedPersistedArgs(IncidentReportArgs* result) { + for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + result->merge(it->second->args); + } +} - // 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; - if ((*section)->userdebugAndEngOnly && !isUserdebugOrEng) { - VLOG("Skipping incident report section %d '%s' because it's limited to userdebug/eng", - id, (*section)->name.string()); - continue; +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; } - 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); - } - } + } + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + if ((*request)->containsSection(sectionId)) { + return true; + } + } + return false; +} - // 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(); +void ReportBatch::clearPersistedRequests() { + mPersistedRequests.clear(); +} - // 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++; +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); } } - -DONE: - ALOGD("Incident reporting took %d sections.", sectionCount); - // Reports the metdadata when taking the incident report. - if (!isTest) metadataSection.Execute(&batch); - - // Close the file. - if (mainFd >= 0) { - close(mainFd); - } - - // 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(); - } + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + if ((*request)->getStatus() != NO_ERROR) { + requests->push_back(*request); } } +} - // 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; +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(); +} - // 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()); +void ReportWriter::endSection(IncidentMetadata::SectionStats* sectionMetadata) { + long endTime = uptimeMillis(); + + if (mSectionStatsCalledForSectionId != mCurrentSectionId) { + ALOGW("setSectionStats not called for section %d", mCurrentSectionId); } - return REPORT_FINISHED; + 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); } -/** - * 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(); +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); +} - *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; +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; } - // Override umask. Not super critical. If it fails go on with life. - chmod(filename, 0660); + sprintf(line, prefixFormat, levelText, section->id, err, strerror(-err)); + + vsprintf(line + prefixLen, format, args); + + __android_log_write(level, LOG_TAG, line); - 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; + if (mSectionErrors.length() == 0) { + mSectionErrors = line; + } else { + mSectionErrors += '\n'; + mSectionErrors += line; } - return NO_ERROR; + free(line); + + if (level >= ANDROID_LOG_ERROR) { + mHadError = true; + } } -Reporter::run_report_status_t Reporter::upload_backlog() { - DIR* dir; - struct dirent* entry; - struct stat st; - status_t err; +// 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)); - 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; + // Add the fd for the persisted requests + if (mPersistedFile != nullptr) { + filter.addFd(new PersistedFilterFd(mMaxPersistedPrivacyPolicy, + mPersistedFile->getDataFileFd(), mPersistedFile)); } - if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) { - ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY); - return REPORT_NEEDS_DROPBOX; + // 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; + + IncidentMetadata metadata; + int persistedPrivacyPolicy = PRIVACY_POLICY_UNSET; + std::string buildType = android::base::GetProperty("ro.build.type", ""); + const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng"; + + (*reportByteSize) = 0; + + // Tell everyone that we're starting. + ALOGI("Starting incident report"); + mBatch->forEachListener([](const auto& listener) { listener->onReportStarted(); }); + + if (mBatch->hasPersistedReports()) { + // Open a work file to contain the contents of all of the persisted reports. + // For this block, if we can't initialize the report file for some reason, + // then we will remove the persisted ReportRequests from the report, but + // continue with the streaming ones. + mPersistedFile = mWorkDirectory->createReportFile(); + ALOGI("Report will be persisted: envelope: %s data: %s", + mPersistedFile->getEnvelopeFileName().c_str(), + mPersistedFile->getDataFileName().c_str()); + + // Record all of the metadata to the persisted file's metadata file. + // It will be read from there and reconstructed as the actual reports + // are sent out. + if (mPersistedFile != nullptr) { + mBatch->forEachPersistedRequest([this, &persistedPrivacyPolicy]( + const sp<ReportRequest>& request) { + mPersistedFile->addReport(request->args); + if (request->args.getPrivacyPolicy() < persistedPrivacyPolicy) { + persistedPrivacyPolicy = request->args.getPrivacyPolicy(); + } + }); + mPersistedFile->setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy); + err = mPersistedFile->saveEnvelope(); + if (err != NO_ERROR) { + mWorkDirectory->remove(mPersistedFile); + mPersistedFile = nullptr; + } + mWriter.setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy); + } + + if (mPersistedFile != nullptr) { + err = mPersistedFile->startWritingDataFile(); + if (err != NO_ERROR) { + mWorkDirectory->remove(mPersistedFile); + mPersistedFile = nullptr; + } + } + + if (mPersistedFile != nullptr) { + mWriter.setPersistedFile(mPersistedFile); + } else { + ALOGW("Error creating the persisted file, so clearing persisted reports."); + // If we couldn't open the file (permissions err, etc), then + // we still want to proceed with any streaming reports, but + // cancel all of the persisted ones. + mBatch->forEachPersistedRequest([](const sp<ReportRequest>& request) { + sp<IIncidentReportStatusListener> listener = request->getListener(); + if (listener != nullptr) { + listener->onReportFailed(); + } + }); + mBatch->clearPersistedRequests(); + } } - sp<DropBoxManager> dropbox = new DropBoxManager(); + // 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; + } - // Enumerate, count and add up size - int count = 0; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') { - continue; + // 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); } - String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name; - if (stat(filename.string(), &st) != 0) { - ALOGE("Unable to stat file %s", filename.string()); + }); + + // If writing to any of the headers failed, we don't want to keep processing + // sections for it. + cancel_and_remove_failed_requests(); + + // For each of the report fields, see if we need it, and if so, execute the command + // and report to those that care that we're doing it. + for (const Section** section = SECTION_LIST; *section; section++) { + const int sectionId = (*section)->id; + + // If 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", + sectionId, (*section)->name.string()); continue; } - if (!S_ISREG(st.st_mode)) { + + // If nobody wants this section, skip it. + if (!mBatch->containsSection(sectionId)) { 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; + 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; } - // 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++; + // 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()); } - ALOGD("Successfully uploaded %d files to Dropbox.", count); - closedir(dir); - return REPORT_FINISHED; +DONE: + // Finish up the persisted file. + if (mPersistedFile != nullptr) { + mPersistedFile->closeDataFile(); + + // Set the stored metadata + IncidentReportArgs combinedArgs; + mBatch->getCombinedPersistedArgs(&combinedArgs); + IncidentMetadata persistedMetadata; + make_metadata(&persistedMetadata, metadata, mPersistedFile->getTimestampNs(), + persistedPrivacyPolicy, &combinedArgs); + mPersistedFile->setMetadata(persistedMetadata); + + mPersistedFile->markCompleted(); + err = mPersistedFile->saveEnvelope(); + if (err != NO_ERROR) { + ALOGW("mPersistedFile->saveEnvelope returned %s. Won't send broadcast", + strerror(-err)); + // Abandon ship. + mWorkDirectory->remove(mPersistedFile); + } + } + + // Write the metadata to the streaming ones + mBatch->forEachStreamingRequest([reportId, &metadata](const sp<ReportRequest>& request) { + IncidentMetadata streamingMetadata; + make_metadata(&streamingMetadata, metadata, reportId, + request->args.getPrivacyPolicy(), request); + status_t nonFatalErr = write_section(request->getFd(), FIELD_ID_METADATA, + streamingMetadata); + if (nonFatalErr != NO_ERROR) { + ALOGW("Error writing the metadata to streaming incident report. This is the last" + " thing so we won't return an error: %s", strerror(nonFatalErr)); + } + }); + + // Finish up the streaming ones. + mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) { + request->closeFd(); + }); + + // Tell the listeners that we're done. + if (err == NO_ERROR) { + mBatch->forEachListener([](const auto& listener) { + listener->onReportFinished(); + }); + } else { + mBatch->forEachListener([](const auto& listener) { + listener->onReportFailed(); + }); + } + + ALOGI("Done taking incident report err=%s", strerror(-err)); +} + +void Reporter::cancel_and_remove_failed_requests() { + // Handle a failure in the persisted file + if (mPersistedFile != nullptr) { + if (mPersistedFile->getWriteError() != NO_ERROR) { + ALOGW("Error writing to the persisted file (%s). Closing it and canceling.", + strerror(-mPersistedFile->getWriteError())); + mBatch->forEachPersistedRequest([this](const sp<ReportRequest>& request) { + sp<IIncidentReportStatusListener> listener = request->getListener(); + if (listener != nullptr) { + listener->onReportFailed(); + } + mBatch->removeRequest(request); + }); + mWriter.setPersistedFile(nullptr); + mPersistedFile->closeDataFile(); + mWorkDirectory->remove(mPersistedFile); + mPersistedFile = nullptr; + } + } + + // Handle failures in the streaming files + vector<sp<ReportRequest>> failed; + mBatch->getFailedRequests(&failed); + for (sp<ReportRequest>& request: failed) { + ALOGW("Error writing to a request stream (%s). Closing it and canceling.", + strerror(-request->getStatus())); + sp<IIncidentReportStatusListener> listener = request->getListener(); + if (listener != nullptr) { + listener->onReportFailed(); + } + request->closeFd(); // Will only close the streaming ones. + mBatch->removeRequest(request); + } } } // namespace incidentd diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h index 2a3abd7b4d97..e7a474fde40c 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -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(); + + // 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. + + /** + * Schedule a report for the "main" report, where it will be delivered to + * the uploaders and/or dropbox. + */ + void addPersistedReport(const IncidentReportArgs& args); + + /** + * 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); - void add(const sp<ReportRequest>& request); - void setMainFd(int fd); - void setMainDest(int dest); + /** + * Returns whether both queues are empty. + */ + bool empty() const; - typedef vector<sp<ReportRequest>>::iterator iterator; + /** + * Returns whether there are any persisted records. + */ + bool hasPersistedReports() const { return mPersistedRequests.size() > 0; } - iterator begin() { return mRequests.begin(); } - iterator end() { return mRequests.end(); } + /** + * Return the persisted request for the given component, or nullptr. + */ + sp<ReportRequest> getPersistedRequest(const ComponentName& component); - int mainFd() { return mMainFd; } - int mainDest() { return mMainDest; } - IncidentMetadata& metadata() { return mMetadata; } - map<int, IncidentMetadata::SectionStats>& allSectionStats() { return mSectionStats; } + /** + * 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); -private: - vector<sp<ReportRequest>> mRequests; - IncidentReportArgs mSections; - int mMainFd; - int mMainDest; + /** + * Remove all of the broadcast (persisted) requests. + */ + void clearPersistedRequests(); + + /** + * Get the requests that have encountered errors. + */ + void getFailedRequests(vector<sp<ReportRequest>>* requests); - IncidentMetadata mMetadata; - map<int, IncidentMetadata::SectionStats> mSectionStats; + /** + * Remove the request from whichever list it's in. + */ + void removeRequest(const sp<ReportRequest>& request); + + +private: + map<ComponentName, sp<ReportRequest>> mPersistedRequests; + vector<sp<ReportRequest>> mStreamingRequests; }; // ================================================================================ -class Reporter : public virtual RefBase { +class ReportWriter { public: - enum run_report_status_t { REPORT_FINISHED = 0, REPORT_NEEDS_DROPBOX = 1 }; + ReportWriter(const sp<ReportBatch>& batch); + ~ReportWriter(); - ReportRequestSet batch; + void setPersistedFile(sp<ReportFile> file); + void setMaxPersistedPrivacyPolicy(uint8_t privacyPolicy); - Reporter(); // PROD must use this constructor. - explicit Reporter(const char* directory); // For testing purpose only. - virtual ~Reporter(); + void startSection(int sectionId); + void endSection(IncidentMetadata::SectionStats* sectionStats); - // Run the report as described in the batch and args parameters. - run_report_status_t runReport(size_t* reportByteSize); + void setSectionStats(const FdBuffer& buffer); - static run_report_status_t upload_backlog(); + 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: - String8 mIncidentDirectory; + // 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; - string mFilename; - off_t mMaxSize; - size_t mMaxCount; - time_t mStartTime; + /* + * 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; - status_t create_file(int* fd); + 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: + Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch); + + virtual ~Reporter(); + + // Run the report as described in the batch and args parameters. + void runReport(size_t* reportByteSize); - bool isTest = true; // default to true for testing +private: + sp<WorkDirectory> mWorkDirectory; + ReportWriter mWriter; + sp<ReportBatch> mBatch; + sp<ReportFile> mPersistedFile; + + void cancel_and_remove_failed_requests(); }; } // namespace incidentd } // namespace os } // namespace android - -#endif // REPORTER_H diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 32ec1ba90cda..1e8261ee1832 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -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 @@ -64,114 +63,6 @@ static pid_t fork_execute_incident_helper(const int id, Fpipe* p2cPipe, Fpipe* c } // ================================================================================ -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), timeoutMs(timeoutMs), @@ -180,86 +71,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; } FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs) @@ -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); diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index 86d956ff75d8..f89824c07b87 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -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; diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp new file mode 100644 index 000000000000..aa376ddee082 --- /dev/null +++ b/cmds/incidentd/src/WorkDirectory.cpp @@ -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 + diff --git a/cmds/incidentd/src/WorkDirectory.h b/cmds/incidentd/src/WorkDirectory.h new file mode 100644 index 000000000000..e344371c3682 --- /dev/null +++ b/cmds/incidentd/src/WorkDirectory.h @@ -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 + diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp index af685d8adeb8..dfaf89392f90 100644 --- a/cmds/incidentd/src/incidentd_util.cpp +++ b/cmds/incidentd/src/incidentd_util.cpp @@ -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
\ No newline at end of file +} // namespace android diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h index 3dac2c4c3759..cc30768fa704 100644 --- a/cmds/incidentd/src/incidentd_util.h +++ b/cmds/incidentd/src/incidentd_util.h @@ -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 diff --git a/cmds/incidentd/src/proto_util.cpp b/cmds/incidentd/src/proto_util.cpp new file mode 100644 index 000000000000..be2f24f97d02 --- /dev/null +++ b/cmds/incidentd/src/proto_util.cpp @@ -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 + diff --git a/cmds/incidentd/src/proto_util.h b/cmds/incidentd/src/proto_util.h new file mode 100644 index 000000000000..b9df6cbf296d --- /dev/null +++ b/cmds/incidentd/src/proto_util.h @@ -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 + + diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp index e2883ba04508..7d20a74437ce 100644 --- a/cmds/incidentd/src/report_directory.cpp +++ b/cmds/incidentd/src/report_directory.cpp @@ -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
\ No newline at end of file +} // namespace android diff --git a/cmds/incidentd/src/report_file.proto b/cmds/incidentd/src/report_file.proto new file mode 100644 index 000000000000..7563da2c2148 --- /dev/null +++ b/cmds/incidentd/src/report_file.proto @@ -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; +} + diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp index 3f92c2a18328..7edfadf5f90d 100644 --- a/cmds/incidentd/tests/FdBuffer_test.cpp +++ b/cmds/incidentd/tests/FdBuffer_test.cpp @@ -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(); + + 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()); - it.rp()->rewind(); - it.rp()->move(buffer.size()); - EXPECT_EQ(it.bytesRead(), testdata.size()); - EXPECT_FALSE(it.hasNext()); + 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()); - - it.rp()->rewind(); - while (it.hasNext()) { - char c = 'A' + (it.bytesRead() % 64 / 8); - ASSERT_TRUE(it.next() == c); + sp<ProtoReader> reader = buffer.data()->read(); + reader->move(fourMB); + + 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); } } } diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp deleted file mode 100644 index 871226b7a6a9..000000000000 --- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp +++ /dev/null @@ -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); -} diff --git a/cmds/incidentd/tests/PrivacyFilter_test.cpp b/cmds/incidentd/tests/PrivacyFilter_test.cpp new file mode 100644 index 000000000000..5c05aac0ac57 --- /dev/null +++ b/cmds/incidentd/tests/PrivacyFilter_test.cpp @@ -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 diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp index b5e41d767295..9becf173ee1a 100644 --- a/cmds/incidentd/tests/Reporter_test.cpp +++ b/cmds/incidentd/tests/Reporter_test.cpp @@ -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 +} +*/ diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp index 24454ed15d40..858f7d07c930 100644 --- a/cmds/incidentd/tests/Section_test.cpp +++ b/cmds/incidentd/tests/Section_test.cpp @@ -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)); } +*/ diff --git a/cmds/incidentd/tests/section_list.cpp b/cmds/incidentd/tests/section_list.cpp index 1d7f2b611f08..3a45af028518 100644 --- a/cmds/incidentd/tests/section_list.cpp +++ b/cmds/incidentd/tests/section_list.cpp @@ -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; +}; + +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, DEST_LOCAL, NULL}; -Privacy sub_field_2{2, 9, NULL, DEST_AUTOMATIC, 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
\ No newline at end of file +} // namespace android diff --git a/cmds/incidentd/tests/test_proto.proto b/cmds/incidentd/tests/test_proto.proto new file mode 100644 index 000000000000..f57070be64de --- /dev/null +++ b/cmds/incidentd/tests/test_proto.proto @@ -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; +} + diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index ce07d6d12f72..8cd409e5e8c1 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -118,6 +118,7 @@ cc_defaults { static_libs: [ "libhealthhalutils", + "libplatformprotos", ], shared_libs: [ diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index a6699e7e6152..286e76ec108a 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -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); } } diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index f78ae38aabd8..52ecdc8425af 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -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); } } diff --git a/cmds/statsd/src/external/GpuStatsPuller.cpp b/cmds/statsd/src/external/GpuStatsPuller.cpp index 130bd85d56c6..3fa932fddd04 100644 --- a/cmds/statsd/src/external/GpuStatsPuller.cpp +++ b/cmds/statsd/src/external/GpuStatsPuller.cpp @@ -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 ""; diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 9a00637ee834..24408fc8fc0b 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -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) { diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 5435c8420519..69816cbbd92f 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -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; diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 59d4865f8977..cdef87451f63 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -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()); } diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp index 7c2d2420528c..ff1cb4ff1450 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -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()); diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index a9305accb1be..f1cad92c336b 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -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; diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index eec3c735057c..b03517e607b3 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -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()); diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index c04a40cfebd9..d9fa4e99d54d 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -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()); } diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp index c170b12dc242..38bc19452afa 100644 --- a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp +++ b/cmds/statsd/tests/external/IncidentReportArgs_test.cpp @@ -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
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 90b9e812182d..afa05a93c55a 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -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; diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java index a5460e92d73a..18147b572125 100644 --- a/core/java/android/content/ComponentName.java +++ b/core/java/android/content/ComponentName.java @@ -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); } diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl index b67b99fabb9e..5e024b9e35d8 100644 --- a/core/java/android/os/IIncidentManager.aidl +++ b/core/java/android/os/IIncidentManager.aidl @@ -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); } diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java index 0bdf6f10d88d..08afe31f05dd 100644 --- a/core/java/android/os/IncidentManager.java +++ b/core/java/android/os/IncidentManager.java @@ -75,6 +75,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. * * @hide @@ -97,6 +104,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) { diff --git a/core/proto/android/app/alarmmanager.proto b/core/proto/android/app/alarmmanager.proto index 58df9225bb7e..fe276363d71a 100644 --- a/core/proto/android/app/alarmmanager.proto +++ b/core/proto/android/app/alarmmanager.proto @@ -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; diff --git a/core/proto/android/app/notification.proto b/core/proto/android/app/notification.proto index a6f13d76b9f6..bf0b35245ec5 100644 --- a/core/proto/android/app/notification.proto +++ b/core/proto/android/app/notification.proto @@ -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. diff --git a/core/proto/android/app/notification_channel.proto b/core/proto/android/app/notification_channel.proto index 435d32f59a35..c835b90ec969 100644 --- a/core/proto/android/app/notification_channel.proto +++ b/core/proto/android/app/notification_channel.proto @@ -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. diff --git a/core/proto/android/app/notification_channel_group.proto b/core/proto/android/app/notification_channel_group.proto index 6d6ceb2f7cbe..c064bb124eb8 100644 --- a/core/proto/android/app/notification_channel_group.proto +++ b/core/proto/android/app/notification_channel_group.proto @@ -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. diff --git a/core/proto/android/app/notificationmanager.proto b/core/proto/android/app/notificationmanager.proto index 27204ccae0d9..b88315e2dbee 100644 --- a/core/proto/android/app/notificationmanager.proto +++ b/core/proto/android/app/notificationmanager.proto @@ -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. diff --git a/core/proto/android/app/pendingintent.proto b/core/proto/android/app/pendingintent.proto index 04ce8507cd63..b8e61a3fd0d4 100644 --- a/core/proto/android/app/pendingintent.proto +++ b/core/proto/android/app/pendingintent.proto @@ -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. diff --git a/core/proto/android/app/profilerinfo.proto b/core/proto/android/app/profilerinfo.proto index 20fa3adbf5b9..d318533b77e5 100644 --- a/core/proto/android/app/profilerinfo.proto +++ b/core/proto/android/app/profilerinfo.proto @@ -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; diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto index 6cc1a40de873..18439da1a56f 100644 --- a/core/proto/android/app/window_configuration.proto +++ b/core/proto/android/app/window_configuration.proto @@ -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 { diff --git a/core/proto/android/content/clipdata.proto b/core/proto/android/content/clipdata.proto index 4f1c308f0981..72bbb2ab045c 100644 --- a/core/proto/android/content/clipdata.proto +++ b/core/proto/android/content/clipdata.proto @@ -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 { diff --git a/core/proto/android/content/clipdescription.proto b/core/proto/android/content/clipdescription.proto index bc0e9407faf8..71455632fadb 100644 --- a/core/proto/android/content/clipdescription.proto +++ b/core/proto/android/content/clipdescription.proto @@ -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 { diff --git a/core/proto/android/content/component_name.proto b/core/proto/android/content/component_name.proto index 232d68584335..5cd0b05a4d37 100644 --- a/core/proto/android/content/component_name.proto +++ b/core/proto/android/content/component_name.proto @@ -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. diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto index 06f9735c3dea..57ced09240f2 100644 --- a/core/proto/android/content/configuration.proto +++ b/core/proto/android/content/configuration.proto @@ -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. diff --git a/core/proto/android/content/featureinfo.proto b/core/proto/android/content/featureinfo.proto index 87bf404c6245..1473ba97c9be 100644 --- a/core/proto/android/content/featureinfo.proto +++ b/core/proto/android/content/featureinfo.proto @@ -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; diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto index 99ed6875075e..2de538d358c4 100644 --- a/core/proto/android/content/intent.proto +++ b/core/proto/android/content/intent.proto @@ -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 { diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto index 86743bf6b91c..d8af754dc2ea 100644 --- a/core/proto/android/content/locale.proto +++ b/core/proto/android/content/locale.proto @@ -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; diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto index ebb2fa62a351..4a7d04361a5b 100644 --- a/core/proto/android/content/package_item_info.proto +++ b/core/proto/android/content/package_item_info.proto @@ -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; diff --git a/core/proto/android/graphics/point.proto b/core/proto/android/graphics/point.proto index 04d879fd405c..8180a061e2da 100644 --- a/core/proto/android/graphics/point.proto +++ b/core/proto/android/graphics/point.proto @@ -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; diff --git a/core/proto/android/graphics/rect.proto b/core/proto/android/graphics/rect.proto index c216b2bdbda1..0bb0494406d9 100644 --- a/core/proto/android/graphics/rect.proto +++ b/core/proto/android/graphics/rect.proto @@ -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; diff --git a/core/proto/android/internal/locallog.proto b/core/proto/android/internal/locallog.proto index df0b90b058cd..ecf76a1063d2 100644 --- a/core/proto/android/internal/locallog.proto +++ b/core/proto/android/internal/locallog.proto @@ -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; diff --git a/core/proto/android/media/audioattributes.proto b/core/proto/android/media/audioattributes.proto index d679d9c24f73..288b55518bf3 100644 --- a/core/proto/android/media/audioattributes.proto +++ b/core/proto/android/media/audioattributes.proto @@ -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. diff --git a/core/proto/android/net/network.proto b/core/proto/android/net/network.proto index e13ca9f682eb..ca9ae61ab582 100644 --- a/core/proto/android/net/network.proto +++ b/core/proto/android/net/network.proto @@ -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. diff --git a/core/proto/android/net/networkcapabilities.proto b/core/proto/android/net/networkcapabilities.proto index 0338bf8f37b6..be0cad18a24d 100644 --- a/core/proto/android/net/networkcapabilities.proto +++ b/core/proto/android/net/networkcapabilities.proto @@ -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. diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto index d260b13a29eb..b35a0203ff02 100644 --- a/core/proto/android/net/networkrequest.proto +++ b/core/proto/android/net/networkrequest.proto @@ -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. diff --git a/core/proto/android/os/backtrace.proto b/core/proto/android/os/backtrace.proto index 8bbae179f520..31ff241cb846 100644 --- a/core/proto/android/os/backtrace.proto +++ b/core/proto/android/os/backtrace.proto @@ -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; diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto index 516fa7b9336b..892ebf70ca75 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -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; diff --git a/core/proto/android/os/batterytype.proto b/core/proto/android/os/batterytype.proto index 2388c1edea7e..82e194fdf335 100644 --- a/core/proto/android/os/batterytype.proto +++ b/core/proto/android/os/batterytype.proto @@ -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; diff --git a/core/proto/android/os/bundle.proto b/core/proto/android/os/bundle.proto index 5556936c3c78..dc5932962042 100644 --- a/core/proto/android/os/bundle.proto +++ b/core/proto/android/os/bundle.proto @@ -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 { diff --git a/core/proto/android/os/cpufreq.proto b/core/proto/android/os/cpufreq.proto index 46f4901d8a29..b86da1a52195 100644 --- a/core/proto/android/os/cpufreq.proto +++ b/core/proto/android/os/cpufreq.proto @@ -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; diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto index ce69fc9d4037..7477db4e8a66 100644 --- a/core/proto/android/os/cpuinfo.proto +++ b/core/proto/android/os/cpuinfo.proto @@ -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; diff --git a/libs/incident/proto/android/os/header.proto b/core/proto/android/os/header.proto index d463f87055b3..d463f87055b3 100644 --- a/libs/incident/proto/android/os/header.proto +++ b/core/proto/android/os/header.proto diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index a5350c9dc1b8..dfb6c0817043 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -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; diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto index 5021a06b1769..885e62dac523 100644 --- a/core/proto/android/os/kernelwake.proto +++ b/core/proto/android/os/kernelwake.proto @@ -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; diff --git a/core/proto/android/os/looper.proto b/core/proto/android/os/looper.proto index b9b8cf584502..d1ef7bc40b83 100644 --- a/core/proto/android/os/looper.proto +++ b/core/proto/android/os/looper.proto @@ -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; diff --git a/core/proto/android/os/message.proto b/core/proto/android/os/message.proto index 8aaec7094f6e..9b48b67fc125 100644 --- a/core/proto/android/os/message.proto +++ b/core/proto/android/os/message.proto @@ -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 { diff --git a/core/proto/android/os/messagequeue.proto b/core/proto/android/os/messagequeue.proto index 61bbee709fd8..9800865622dd 100644 --- a/core/proto/android/os/messagequeue.proto +++ b/core/proto/android/os/messagequeue.proto @@ -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; diff --git a/libs/incident/proto/android/os/metadata.proto b/core/proto/android/os/metadata.proto index 3b0e9c9aa17a..6242b27bc5c9 100644 --- a/libs/incident/proto/android/os/metadata.proto +++ b/core/proto/android/os/metadata.proto @@ -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. diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto index 65e713900ae7..c795e2edfc20 100644 --- a/core/proto/android/os/pagetypeinfo.proto +++ b/core/proto/android/os/pagetypeinfo.proto @@ -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; diff --git a/core/proto/android/os/patternmatcher.proto b/core/proto/android/os/patternmatcher.proto index 520f2f5b8542..ab2010db5829 100644 --- a/core/proto/android/os/patternmatcher.proto +++ b/core/proto/android/os/patternmatcher.proto @@ -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; diff --git a/core/proto/android/os/persistablebundle.proto b/core/proto/android/os/persistablebundle.proto index 712f87c9b147..785250c77de2 100644 --- a/core/proto/android/os/persistablebundle.proto +++ b/core/proto/android/os/persistablebundle.proto @@ -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 { diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto index 20b0a7446ae8..52b092c34052 100644 --- a/core/proto/android/os/powermanager.proto +++ b/core/proto/android/os/powermanager.proto @@ -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. */ diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto index f7edaf4aef20..62f75bb9ac84 100644 --- a/core/proto/android/os/procrank.proto +++ b/core/proto/android/os/procrank.proto @@ -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 { diff --git a/core/proto/android/os/ps.proto b/core/proto/android/os/ps.proto index e032b574607f..993bfb64c674 100644 --- a/core/proto/android/os/ps.proto +++ b/core/proto/android/os/ps.proto @@ -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; diff --git a/core/proto/android/os/statsdata.proto b/core/proto/android/os/statsdata.proto index 25d76b869259..b89b606d0f26 100644 --- a/core/proto/android/os/statsdata.proto +++ b/core/proto/android/os/statsdata.proto @@ -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 { diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto index 1f63be93fb94..d06e1a6c1ccd 100644 --- a/core/proto/android/os/system_properties.proto +++ b/core/proto/android/os/system_properties.proto @@ -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; diff --git a/core/proto/android/os/worksource.proto b/core/proto/android/os/worksource.proto index 0a9c2ed6e259..1763d446848e 100644 --- a/core/proto/android/os/worksource.proto +++ b/core/proto/android/os/worksource.proto @@ -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; diff --git a/libs/incident/proto/android/privacy.proto b/core/proto/android/privacy.proto index 1ef36df8121f..1ef36df8121f 100644 --- a/libs/incident/proto/android/privacy.proto +++ b/core/proto/android/privacy.proto diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index 76a3b5d7f06a..e43b6a04b93c 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -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; diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 62df6e73deea..d124feb2436e 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -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. diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 27a18ee9e52a..91d5bc8e45f2 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -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. diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index 41a74982585d..f8143de8121f 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -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. diff --git a/libs/incident/proto/android/section.proto b/core/proto/android/section.proto index 5afe22a3095f..5afe22a3095f 100644 --- a/libs/incident/proto/android/section.proto +++ b/core/proto/android/section.proto diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 79a5dd78ffb3..4af9fc0ed782 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -34,7 +34,7 @@ import "frameworks/base/core/proto/android/os/powermanager.proto"; import "frameworks/base/core/proto/android/server/intentresolver.proto"; import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; import "frameworks/base/core/proto/android/util/common.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto index b74f28d38fa8..490f7295c851 100644 --- a/core/proto/android/server/alarmmanagerservice.proto +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -21,7 +21,7 @@ import "frameworks/base/core/proto/android/app/pendingintent.proto"; import "frameworks/base/core/proto/android/internal/locallog.proto"; import "frameworks/base/core/proto/android/os/worksource.proto"; import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; package com.android.server; diff --git a/core/proto/android/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto index 0bcc48887101..70627edf2cb3 100644 --- a/core/proto/android/server/animationadapter.proto +++ b/core/proto/android/server/animationadapter.proto @@ -18,7 +18,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/graphics/point.proto"; import "frameworks/base/core/proto/android/view/remote_animation_target.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; package com.android.server.wm; option java_multiple_files = true; diff --git a/core/proto/android/server/appwindowthumbnail.proto b/core/proto/android/server/appwindowthumbnail.proto index a1be7218893c..f22cdc541158 100644 --- a/core/proto/android/server/appwindowthumbnail.proto +++ b/core/proto/android/server/appwindowthumbnail.proto @@ -17,7 +17,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/server/surfaceanimator.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; package com.android.server.wm; option java_multiple_files = true; diff --git a/core/proto/android/server/face.proto b/core/proto/android/server/face.proto index 6ecf3289b588..8b77586d3ff5 100644 --- a/core/proto/android/server/face.proto +++ b/core/proto/android/server/face.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package com.android.server.biometrics.face; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "FaceServiceProto"; diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto index c5eb85c5d17b..a264f18f921c 100644 --- a/core/proto/android/server/fingerprint.proto +++ b/core/proto/android/server/fingerprint.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package com.android.server.biometrics.fingerprint; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "FingerprintServiceProto"; diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto index 54f30c3b9106..89424bc9017c 100644 --- a/core/proto/android/server/forceappstandbytracker.proto +++ b/core/proto/android/server/forceappstandbytracker.proto @@ -17,7 +17,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/server/statlogger.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; package com.android.server; diff --git a/core/proto/android/server/intentresolver.proto b/core/proto/android/server/intentresolver.proto index e67723e5abfa..7ac50e0c0970 100644 --- a/core/proto/android/server/intentresolver.proto +++ b/core/proto/android/server/intentresolver.proto @@ -19,7 +19,7 @@ option java_multiple_files = true; package com.android.server; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message IntentResolverProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 6c9d13a572ee..1e0b0d84e04d 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -30,7 +30,7 @@ import "frameworks/base/core/proto/android/os/bundle.proto"; import "frameworks/base/core/proto/android/os/persistablebundle.proto"; import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; import "frameworks/base/core/proto/android/server/job/enums.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; // Next tag: 21 message JobSchedulerServiceDumpProto { diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index 9bf1825a8da6..091e1c2bb05a 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -28,7 +28,7 @@ import "frameworks/base/core/proto/android/os/worksource.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message PowerManagerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/server/rolemanagerservice.proto b/core/proto/android/server/rolemanagerservice.proto index 3453a6649e02..146522ce9c8b 100644 --- a/core/proto/android/server/rolemanagerservice.proto +++ b/core/proto/android/server/rolemanagerservice.proto @@ -20,7 +20,7 @@ package com.android.server.role; option java_multiple_files = true; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message RoleManagerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/server/statlogger.proto b/core/proto/android/server/statlogger.proto index 65b1af79136b..8593da806dae 100644 --- a/core/proto/android/server/statlogger.proto +++ b/core/proto/android/server/statlogger.proto @@ -20,7 +20,7 @@ package com.android.server; option java_multiple_files = true; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; // Dump from StatLogger. message StatLoggerProto { diff --git a/core/proto/android/server/surfaceanimator.proto b/core/proto/android/server/surfaceanimator.proto index e3e8baae77ed..15f47140bec7 100644 --- a/core/proto/android/server/surfaceanimator.proto +++ b/core/proto/android/server/surfaceanimator.proto @@ -18,7 +18,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/server/animationadapter.proto"; import "frameworks/base/core/proto/android/view/surfacecontrol.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; package com.android.server.wm; option java_multiple_files = true; diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto index 050ec7a0a95f..f26eefad24e1 100644 --- a/core/proto/android/server/usagestatsservice.proto +++ b/core/proto/android/server/usagestatsservice.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package com.android.server.usage; import "frameworks/base/core/proto/android/content/configuration.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 3767ed553181..dbd219141e3e 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -27,7 +27,7 @@ import "frameworks/base/core/proto/android/view/displayinfo.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; import "frameworks/base/core/proto/android/view/surface.proto"; import "frameworks/base/core/proto/android/view/windowlayoutparams.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; package com.android.server.wm; diff --git a/core/proto/android/server/wirelesschargerdetector.proto b/core/proto/android/server/wirelesschargerdetector.proto index 2118deb6edeb..1c98fb9c4b76 100644 --- a/core/proto/android/server/wirelesschargerdetector.proto +++ b/core/proto/android/server/wirelesschargerdetector.proto @@ -19,7 +19,7 @@ package com.android.server.power; option java_multiple_files = true; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message WirelessChargerDetectorProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/service/adb.proto b/core/proto/android/service/adb.proto index 006081399f64..493f9b87d3ab 100644 --- a/core/proto/android/service/adb.proto +++ b/core/proto/android/service/adb.proto @@ -20,7 +20,7 @@ package android.service.adb; option java_multiple_files = true; option java_outer_classname = "AdbServiceProto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message AdbServiceDumpProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/service/battery.proto b/core/proto/android/service/battery.proto index 34cb2292fc5f..586411f8ad96 100644 --- a/core/proto/android/service/battery.proto +++ b/core/proto/android/service/battery.proto @@ -21,7 +21,7 @@ option java_multiple_files = true; option java_outer_classname = "BatteryServiceProto"; import "frameworks/base/core/proto/android/os/enums.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message BatteryServiceDumpProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/service/batterystats.proto b/core/proto/android/service/batterystats.proto index 25b47d3f88f2..3ae45caf1bde 100644 --- a/core/proto/android/service/batterystats.proto +++ b/core/proto/android/service/batterystats.proto @@ -21,7 +21,7 @@ option java_multiple_files = true; option java_outer_classname = "BatteryStatsServiceProto"; import "frameworks/base/core/proto/android/os/batterystats.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; // Dump of batterystats aggregate data (dumpsys batterystats --proto). message BatteryStatsServiceDumpProto { diff --git a/core/proto/android/service/diskstats.proto b/core/proto/android/service/diskstats.proto index 1012eb0b17a6..f79de394a50f 100644 --- a/core/proto/android/service/diskstats.proto +++ b/core/proto/android/service/diskstats.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package android.service.diskstats; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "DiskStatsServiceProto"; diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index bb32495e1d30..11f046748b32 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -20,7 +20,7 @@ package android.service; option java_multiple_files = true; option java_outer_classname = "GraphicsStatsServiceProto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; // This file is based on frameworks/base/libs/hwui/protos/graphicsstats.proto. // Please try to keep the two files in sync. diff --git a/core/proto/android/service/netstats.proto b/core/proto/android/service/netstats.proto index 02d44838e1bc..8ebb4a9f6649 100644 --- a/core/proto/android/service/netstats.proto +++ b/core/proto/android/service/netstats.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package android.service; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "NetworkStatsServiceProto"; diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index 4ef26dd59240..1ec05fb5e9fc 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -25,7 +25,7 @@ import "frameworks/base/core/proto/android/app/notification_channel_group.proto" import "frameworks/base/core/proto/android/app/notificationmanager.proto"; import "frameworks/base/core/proto/android/content/component_name.proto"; 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"; message NotificationServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index 7f96d701cdbf..6ffa0c943037 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -18,7 +18,7 @@ syntax = "proto2"; package android.service.pm; import "frameworks/base/core/proto/android/content/featureinfo.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "PackageServiceProto"; diff --git a/core/proto/android/service/print.proto b/core/proto/android/service/print.proto index a44915618b04..abf1b29a210d 100644 --- a/core/proto/android/service/print.proto +++ b/core/proto/android/service/print.proto @@ -21,7 +21,7 @@ option java_multiple_files = true; option java_outer_classname = "PrintServiceProto"; import "frameworks/base/core/proto/android/content/component_name.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message PrintServiceDumpProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto index da801ffcc99a..f49a04422c0e 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -22,7 +22,7 @@ option java_outer_classname = "ProcessStatsServiceProto"; import "frameworks/base/core/proto/android/util/common.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; /** * Data from ProcStatsService Dumpsys diff --git a/core/proto/android/service/runtime.proto b/core/proto/android/service/runtime.proto index ecbccef7a94c..440264d5a5cf 100644 --- a/core/proto/android/service/runtime.proto +++ b/core/proto/android/service/runtime.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package android.service.runtime; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "RuntimeServiceProto"; diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index 367c54086ade..2e1de79b199f 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -22,7 +22,7 @@ option java_outer_classname = "UsbServiceProto"; import "frameworks/base/core/proto/android/content/component_name.proto"; import "frameworks/base/core/proto/android/service/enums.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; message UsbServiceDumpProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/util/common.proto b/core/proto/android/util/common.proto index f8f78851d74a..aad24d140395 100644 --- a/core/proto/android/util/common.proto +++ b/core/proto/android/util/common.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package android.util; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; diff --git a/core/proto/android/util/event_log_tags.proto b/core/proto/android/util/event_log_tags.proto index 457219fde835..40bab9e16a01 100644 --- a/core/proto/android/util/event_log_tags.proto +++ b/core/proto/android/util/event_log_tags.proto @@ -19,7 +19,7 @@ package android.util; option java_multiple_files = true; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; // Proto representation of event.logtags. // Usually sit in /system/etc/event-log-tags. diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto index 416c055fe401..09870ae55cfe 100644 --- a/core/proto/android/util/log.proto +++ b/core/proto/android/util/log.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package android.util; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; diff --git a/core/proto/android/view/displaycutout.proto b/core/proto/android/view/displaycutout.proto index 0a3310148145..ff98e99b4725 100644 --- a/core/proto/android/view/displaycutout.proto +++ b/core/proto/android/view/displaycutout.proto @@ -17,7 +17,7 @@ syntax = "proto2"; 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"; package android.view; option java_multiple_files = true; diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto index 29757fc073f7..49c0a290c368 100644 --- a/core/proto/android/view/displayinfo.proto +++ b/core/proto/android/view/displayinfo.proto @@ -17,7 +17,7 @@ syntax = "proto2"; package android.view; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; diff --git a/core/proto/android/view/remote_animation_target.proto b/core/proto/android/view/remote_animation_target.proto index 808c5143fe8e..24d27858bd20 100644 --- a/core/proto/android/view/remote_animation_target.proto +++ b/core/proto/android/view/remote_animation_target.proto @@ -23,7 +23,7 @@ import "frameworks/base/core/proto/android/app/window_configuration.proto"; import "frameworks/base/core/proto/android/graphics/point.proto"; import "frameworks/base/core/proto/android/graphics/rect.proto"; import "frameworks/base/core/proto/android/view/surfacecontrol.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; /** Proto representation for android.view.RemoteAnimationTarget.java class. */ message RemoteAnimationTargetProto { diff --git a/core/proto/android/view/surfacecontrol.proto b/core/proto/android/view/surfacecontrol.proto index 8a252beeae59..cbb243ba7872 100644 --- a/core/proto/android/view/surfacecontrol.proto +++ b/core/proto/android/view/surfacecontrol.proto @@ -19,7 +19,7 @@ package android.view; option java_multiple_files = true; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; /** * Represents a {@link android.view.SurfaceControl} object. diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto index 8a011e9067d8..075ebcfc5e86 100644 --- a/core/proto/android/view/windowlayoutparams.proto +++ b/core/proto/android/view/windowlayoutparams.proto @@ -18,7 +18,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/graphics/pixelformat.proto"; import "frameworks/base/core/proto/android/view/display.proto"; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; +import "frameworks/base/core/proto/android/privacy.proto"; package android.view; option java_multiple_files = true; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8bfa038c8e8f..3b063b7200dc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -116,6 +116,7 @@ <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" /> <protected-broadcast android:name="android.app.action.SHOW_DEVICE_MONITORING_DIALOG" /> <protected-broadcast android:name="android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED" /> + <protected-broadcast android:name="android.intent.action.INCIDENT_REPORT_READY" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" /> diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp index 905e3039ff88..150f6dcde5d0 100644 --- a/libs/incident/Android.bp +++ b/libs/incident/Android.bp @@ -27,23 +27,29 @@ cc_library_shared { "libbinder", "liblog", "libutils", + "libprotobuf-cpp-lite", + ], + + static_libs: [ + "libplatformprotos", + ], + + whole_static_libs: [ + "libincidentcompanion", ], aidl: { - include_dirs: ["frameworks/base/core/java"], + include_dirs: [ + "frameworks/base/core/java", + "frameworks/native/libs/incidentcompanion/binder", + ], export_aidl_headers: true, }, srcs: [ ":libincident_aidl", - "proto/android/os/metadata.proto", "src/IncidentReportArgs.cpp", ], - proto: { - type: "lite", - export_proto_headers: true, - }, - export_include_dirs: ["include"], -}
\ No newline at end of file +} diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h index f056d3b6c9e8..4391a9b12b76 100644 --- a/libs/incident/include/android/os/IncidentReportArgs.h +++ b/libs/incident/include/android/os/IncidentReportArgs.h @@ -29,10 +29,11 @@ namespace os { using namespace std; -// DESTINATION enum value, sync with proto/android/privacy.proto -const uint8_t DEST_LOCAL = 0; -const uint8_t DEST_EXPLICIT = 100; -const uint8_t DEST_AUTOMATIC = 200; +// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto +const uint8_t PRIVACY_POLICY_LOCAL = 0; +const uint8_t PRIVACY_POLICY_EXPLICIT = 100; +const uint8_t PRIVACY_POLICY_AUTOMATIC = 200; +const uint8_t PRIVACY_POLICY_UNSET = 255; class IncidentReportArgs : public Parcelable { @@ -45,7 +46,7 @@ public: virtual status_t readFromParcel(const Parcel* in); void setAll(bool all); - void setDest(int dest); + void setPrivacyPolicy(int privacyPolicy); void addSection(int section); void setReceiverPkg(const string& pkg); void setReceiverCls(const string& cls); @@ -53,10 +54,10 @@ public: inline bool all() const { return mAll; } bool containsSection(int section) const; - inline int dest() const { return mDest; } + inline int getPrivacyPolicy() const { return mPrivacyPolicy; } inline const set<int>& sections() const { return mSections; } - inline const String16& receiverPkg() const { return mReceiverPkg; } - inline const String16& receiverCls() const { return mReceiverCls; } + inline const string& receiverPkg() const { return mReceiverPkg; } + inline const string& receiverCls() const { return mReceiverCls; } inline const vector<vector<uint8_t>>& headers() const { return mHeaders; } void merge(const IncidentReportArgs& that); @@ -65,9 +66,9 @@ private: set<int> mSections; vector<vector<uint8_t>> mHeaders; bool mAll; - int mDest; - String16 mReceiverPkg; - String16 mReceiverCls; + int mPrivacyPolicy; + string mReceiverPkg; + string mReceiverCls; }; } diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp index 46c8dcf967d7..4268638f62cd 100644 --- a/libs/incident/src/IncidentReportArgs.cpp +++ b/libs/incident/src/IncidentReportArgs.cpp @@ -26,7 +26,7 @@ namespace os { IncidentReportArgs::IncidentReportArgs() :mSections(), mAll(false), - mDest(-1) + mPrivacyPolicy(-1) { } @@ -34,7 +34,9 @@ IncidentReportArgs::IncidentReportArgs(const IncidentReportArgs& that) :mSections(that.mSections), mHeaders(that.mHeaders), mAll(that.mAll), - mDest(that.mDest) + mPrivacyPolicy(that.mPrivacyPolicy), + mReceiverPkg(that.mReceiverPkg), + mReceiverCls(that.mReceiverCls) { } @@ -76,17 +78,17 @@ IncidentReportArgs::writeToParcel(Parcel* out) const } } - err = out->writeInt32(mDest); + err = out->writeInt32(mPrivacyPolicy); if (err != NO_ERROR) { return err; } - err = out->writeString16(mReceiverPkg); + err = out->writeString16(String16(mReceiverPkg.c_str())); if (err != NO_ERROR) { return err; } - err = out->writeString16(mReceiverCls); + err = out->writeString16(String16(mReceiverCls.c_str())); if (err != NO_ERROR) { return err; } @@ -137,15 +139,15 @@ IncidentReportArgs::readFromParcel(const Parcel* in) } } - int32_t dest; - err = in->readInt32(&dest); + int32_t privacyPolicy; + err = in->readInt32(&privacyPolicy); if (err != NO_ERROR) { return err; } - mDest = dest; + mPrivacyPolicy = privacyPolicy; - mReceiverPkg = in->readString16(); - mReceiverCls = in->readString16(); + mReceiverPkg = String8(in->readString16()).string(); + mReceiverCls = String8(in->readString16()).string(); return OK; } @@ -160,9 +162,9 @@ IncidentReportArgs::setAll(bool all) } void -IncidentReportArgs::setDest(int dest) +IncidentReportArgs::setPrivacyPolicy(int privacyPolicy) { - mDest = dest; + mPrivacyPolicy = privacyPolicy; } void @@ -176,13 +178,13 @@ IncidentReportArgs::addSection(int section) void IncidentReportArgs::setReceiverPkg(const string& pkg) { - mReceiverPkg = String16(pkg.c_str()); + mReceiverPkg = pkg; } void IncidentReportArgs::setReceiverCls(const string& cls) { - mReceiverCls = String16(cls.c_str()); + mReceiverCls = cls; } void @@ -200,15 +202,18 @@ IncidentReportArgs::containsSection(int section) const void IncidentReportArgs::merge(const IncidentReportArgs& that) { - if (mAll) { - return; - } else if (that.mAll) { - mAll = true; - mSections.clear(); - } else { - for (set<int>::const_iterator it=that.mSections.begin(); - it!=that.mSections.end(); it++) { - mSections.insert(*it); + for (const vector<uint8_t>& header: that.mHeaders) { + mHeaders.push_back(header); + } + if (!mAll) { + if (that.mAll) { + mAll = true; + mSections.clear(); + } else { + for (set<int>::const_iterator it=that.mSections.begin(); + it!=that.mSections.end(); it++) { + mSections.insert(*it); + } } } } diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp index 44bc97adb946..b0af99732ddb 100644 --- a/libs/protoutil/Android.bp +++ b/libs/protoutil/Android.bp @@ -25,12 +25,15 @@ cc_defaults { srcs: [ "src/EncodedBuffer.cpp", + "src/ProtoFileReader.cpp", "src/ProtoOutputStream.cpp", + "src/ProtoReader.cpp", "src/protobuf.cpp", ], shared_libs: [ "libbase", + "libutils", "libcutils", "liblog", ], diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h index 0b7f6e46be65..f9590ee7cd6d 100644 --- a/libs/protoutil/include/android/util/EncodedBuffer.h +++ b/libs/protoutil/include/android/util/EncodedBuffer.h @@ -17,6 +17,11 @@ #ifndef ANDROID_UTIL_ENCODED_BUFFER_H #define ANDROID_UTIL_ENCODED_BUFFER_H +#include <android/util/ProtoReader.h> + +#include <utils/Errors.h> +#include <utils/RefBase.h> + #include <stdint.h> #include <vector> @@ -34,12 +39,12 @@ namespace util { * *Index: Index of a buffer within the mBuffers list. * *Offset: Position within a buffer. */ -class EncodedBuffer +class EncodedBuffer : public virtual RefBase { public: EncodedBuffer(); explicit EncodedBuffer(size_t chunkSize); - ~EncodedBuffer(); + virtual ~EncodedBuffer(); class Pointer { public: @@ -80,8 +85,9 @@ public: Pointer* wp(); /** - * Returns the current position of write pointer, if the write buffer is full, it will automatically - * rotate to a new buffer with given chunkSize. If NULL is returned, it means NO_MEMORY + * Returns the current position of write pointer, if the write buffer is full, it will + * automatically rotate to a new buffer with given chunkSize. If NULL is returned, it + * means NO_MEMORY. */ uint8_t* writeBuffer(); @@ -120,6 +126,21 @@ public: */ size_t writeHeader(uint32_t fieldId, uint8_t wireType); + /** + * Copy the contents of the parameter into the write buffer. + */ + status_t writeRaw(uint8_t const* buf, size_t size); + + /** + * Copy the entire contents of the ProtoReader into the write buffer. + */ + status_t writeRaw(const sp<ProtoReader>& that); + + /** + * Copy the size bytes of contents of the ProtoReader into the write buffer. + */ + status_t writeRaw(const sp<ProtoReader>& that, size_t size); + /********************************* Edit APIs ************************************************/ /** * Returns the edit pointer. @@ -157,63 +178,35 @@ public: void copy(size_t srcPos, size_t size); /********************************* Read APIs ************************************************/ - class iterator; - friend class iterator; - class iterator { + /** + * Returns the Reader of EncodedBuffer so it guarantees consumers won't be able to + * modify the buffer. + */ + sp<ProtoReader> read(); + +private: + class Reader; + friend class Reader; + class Reader : public ProtoReader { public: - explicit iterator(const EncodedBuffer& buffer); - - /** - * Returns the number of bytes written in the buffer - */ - size_t size() const; - - /** - * Returns the size of total bytes read. - */ - size_t bytesRead() const; - - /** - * Returns the read pointer. - */ - Pointer* rp(); - - /** - * Returns the current position of read pointer, if NULL is returned, it reaches end of buffer. - */ - uint8_t const* readBuffer(); - - /** - * Returns the readable size in the current read buffer. - */ - size_t currentToRead(); - - /** - * Returns true if next bytes is available for read. - */ - bool hasNext(); - - /** - * Reads the current byte and moves pointer 1 bit. - */ - uint8_t next(); - - /** - * Read varint from iterator, the iterator will point to next available byte. - */ - uint64_t readRawVarint(); + explicit Reader(const sp<EncodedBuffer>& buffer); + virtual ~Reader(); + + virtual ssize_t size() const; + virtual size_t bytesRead() const; + virtual uint8_t const* readBuffer(); + virtual size_t currentToRead(); + virtual bool hasNext(); + virtual uint8_t next(); + virtual uint64_t readRawVarint(); + virtual void move(size_t amt); private: - const EncodedBuffer& mData; + const sp<EncodedBuffer> mData; Pointer mRp; + friend class EncodedBuffer; }; - /** - * Returns the iterator of EncodedBuffer so it guarantees consumers won't be able to modified the buffer. - */ - iterator begin() const; - -private: size_t mChunkSize; std::vector<uint8_t*> mBuffers; diff --git a/libs/protoutil/include/android/util/ProtoFileReader.h b/libs/protoutil/include/android/util/ProtoFileReader.h new file mode 100644 index 000000000000..cb3d012a9401 --- /dev/null +++ b/libs/protoutil/include/android/util/ProtoFileReader.h @@ -0,0 +1,75 @@ +/* + * 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 <cstdint> +#include <string> + +#include <android/util/EncodedBuffer.h> + +namespace android { +namespace util { + +/** + * A ProtoReader on top of a file descriptor. + */ +class ProtoFileReader : public ProtoReader +{ +public: + /** + * Read from this file descriptor. + */ + ProtoFileReader(int fd); + + /** + * Does NOT close the file. + */ + virtual ~ProtoFileReader(); + + // From ProtoReader. + virtual ssize_t size() const; + virtual size_t bytesRead() const; + virtual uint8_t const* readBuffer(); + virtual size_t currentToRead(); + virtual bool hasNext(); + virtual uint8_t next(); + virtual uint64_t readRawVarint(); + virtual void move(size_t amt); + + status_t getError() const; +private: + int mFd; // File descriptor for input. + status_t mStatus; // Any errors encountered during read. + ssize_t mSize; // How much total data there is, or -1 if we can't tell. + size_t mPos; // How much data has been read so far. + size_t mOffset; // Offset in current buffer. + size_t mMaxOffset; // How much data is left to read in mBuffer. + const int mChunkSize; // Size of mBuffer. + uint8_t mBuffer[32*1024]; + + /** + * If there is currently more data to read in the buffer, returns true. + * If there is not more, then tries to read. If more data can be read, + * it does so and returns true. If there is no more data, returns false. + * Resets mOffset and mMaxOffset as necessary. Does not advance mOffset. + */ + bool ensure_data(); +}; + +} +} + diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h index ad765592eb55..360e8d3e8c71 100644 --- a/libs/protoutil/include/android/util/ProtoOutputStream.h +++ b/libs/protoutil/include/android/util/ProtoOutputStream.h @@ -121,7 +121,7 @@ public: * it is not able to write to ProtoOutputStream any more since the data is compact. */ size_t size(); // Get the size of the serialized protobuf. - EncodedBuffer::iterator data(); // Get the reader apis of the data. + sp<ProtoReader> data(); // Get the reader apis of the data. bool flush(int fd); // Flush data directly to a file descriptor. /** @@ -135,7 +135,7 @@ public: void writeRawByte(uint8_t byte); private: - EncodedBuffer mBuffer; + sp<EncodedBuffer> mBuffer; size_t mCopyBegin; bool mCompact; uint32_t mDepth; diff --git a/libs/protoutil/include/android/util/ProtoReader.h b/libs/protoutil/include/android/util/ProtoReader.h new file mode 100644 index 000000000000..204eb7dc9b0e --- /dev/null +++ b/libs/protoutil/include/android/util/ProtoReader.h @@ -0,0 +1,77 @@ +/* + * 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 <utils/RefBase.h> + +#include <stdint.h> +#include <vector> + +namespace android { +namespace util { + +class ProtoReader : public virtual RefBase { +public: + ProtoReader(); + ~ProtoReader(); + + /** + * Returns the number of bytes written in the buffer + */ + virtual ssize_t size() const = 0; + + /** + * Returns the size of total bytes read. + */ + virtual size_t bytesRead() const = 0; + + /** + * Returns the current position of read pointer, if NULL is returned, it reaches + * end of buffer. + */ + virtual uint8_t const* readBuffer() = 0; + + /** + * Returns the readable size in the current read buffer. + */ + virtual size_t currentToRead() = 0; + + /** + * Returns true if next bytes is available for read. + */ + virtual bool hasNext() = 0; + + /** + * Reads the current byte and moves pointer 1 bit. + */ + virtual uint8_t next() = 0; + + /** + * Read varint from the reader, the reader will point to next available byte. + */ + virtual uint64_t readRawVarint() = 0; + + /** + * Advance the read pointer. + */ + virtual void move(size_t amt) = 0; +}; + +} // util +} // android + diff --git a/libs/protoutil/src/EncodedBuffer.cpp b/libs/protoutil/src/EncodedBuffer.cpp index c017851a1623..7ffd8874a8fb 100644 --- a/libs/protoutil/src/EncodedBuffer.cpp +++ b/libs/protoutil/src/EncodedBuffer.cpp @@ -208,6 +208,63 @@ EncodedBuffer::writeHeader(uint32_t fieldId, uint8_t wireType) return writeRawVarint32((fieldId << FIELD_ID_SHIFT) | wireType); } +status_t +EncodedBuffer::writeRaw(uint8_t const* buf, size_t size) +{ + while (size > 0) { + uint8_t* target = writeBuffer(); + if (target == NULL) { + return -ENOMEM; + } + size_t chunk = currentToWrite(); + if (chunk > size) { + chunk = size; + } + memcpy(target, buf, chunk); + size -= chunk; + buf += chunk; + mWp.move(chunk); + } + return NO_ERROR; +} + +status_t +EncodedBuffer::writeRaw(const sp<ProtoReader>& reader) +{ + status_t err; + uint8_t const* buf; + while ((buf = reader->readBuffer()) != nullptr) { + size_t amt = reader->currentToRead(); + err = writeRaw(buf, amt); + reader->move(amt); + if (err != NO_ERROR) { + return err; + } + } + return NO_ERROR; +} + +status_t +EncodedBuffer::writeRaw(const sp<ProtoReader>& reader, size_t size) +{ + status_t err; + uint8_t const* buf; + while (size > 0 && (buf = reader->readBuffer()) != nullptr) { + size_t amt = reader->currentToRead(); + if (size < amt) { + amt = size; + } + err = writeRaw(buf, amt); + reader->move(amt); + size -= amt; + if (err != NO_ERROR) { + return err; + } + } + return size == 0 ? NO_ERROR : NOT_ENOUGH_DATA; +} + + /******************************** Edit APIs ************************************************/ EncodedBuffer::Pointer* EncodedBuffer::ep() @@ -283,66 +340,63 @@ EncodedBuffer::copy(size_t srcPos, size_t size) } /********************************* Read APIs ************************************************/ -EncodedBuffer::iterator -EncodedBuffer::begin() const +sp<ProtoReader> +EncodedBuffer::read() { - return EncodedBuffer::iterator(*this); + return new EncodedBuffer::Reader(this); } -EncodedBuffer::iterator::iterator(const EncodedBuffer& buffer) +EncodedBuffer::Reader::Reader(const sp<EncodedBuffer>& buffer) :mData(buffer), - mRp(buffer.mChunkSize) + mRp(buffer->mChunkSize) { } -size_t -EncodedBuffer::iterator::size() const -{ - return mData.size(); +EncodedBuffer::Reader::~Reader() { } -size_t -EncodedBuffer::iterator::bytesRead() const +ssize_t +EncodedBuffer::Reader::size() const { - return mRp.pos(); + return (ssize_t)mData->size(); } -EncodedBuffer::Pointer* -EncodedBuffer::iterator::rp() +size_t +EncodedBuffer::Reader::bytesRead() const { - return &mRp; + return mRp.pos(); } uint8_t const* -EncodedBuffer::iterator::readBuffer() +EncodedBuffer::Reader::readBuffer() { - return hasNext() ? const_cast<uint8_t const*>(mData.at(mRp)) : NULL; + return hasNext() ? const_cast<uint8_t const*>(mData->at(mRp)) : NULL; } size_t -EncodedBuffer::iterator::currentToRead() +EncodedBuffer::Reader::currentToRead() { - return (mData.mWp.index() > mRp.index()) ? - mData.mChunkSize - mRp.offset() : - mData.mWp.offset() - mRp.offset(); + return (mData->mWp.index() > mRp.index()) ? + mData->mChunkSize - mRp.offset() : + mData->mWp.offset() - mRp.offset(); } bool -EncodedBuffer::iterator::hasNext() +EncodedBuffer::Reader::hasNext() { - return mRp.pos() < mData.mWp.pos(); + return mRp.pos() < mData->mWp.pos(); } uint8_t -EncodedBuffer::iterator::next() +EncodedBuffer::Reader::next() { - uint8_t res = *(mData.at(mRp)); + uint8_t res = *(mData->at(mRp)); mRp.move(); return res; } uint64_t -EncodedBuffer::iterator::readRawVarint() +EncodedBuffer::Reader::readRawVarint() { uint64_t val = 0, shift = 0; while (true) { @@ -354,5 +408,11 @@ EncodedBuffer::iterator::readRawVarint() return val; } +void +EncodedBuffer::Reader::move(size_t amt) +{ + mRp.move(amt); +} + } // util } // android diff --git a/libs/protoutil/src/ProtoFileReader.cpp b/libs/protoutil/src/ProtoFileReader.cpp new file mode 100644 index 000000000000..074170a6e2c3 --- /dev/null +++ b/libs/protoutil/src/ProtoFileReader.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2018 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 LOG_TAG "libprotoutil" + +#include <android/util/ProtoFileReader.h> +#include <cutils/log.h> + +#include <cinttypes> +#include <type_traits> + +#include <unistd.h> + +namespace android { +namespace util { + +/** + * Get the amount of data remaining in the file in fd, or -1 if the file size can't be measured. + * It's not the whole file, but this allows us to skip any preamble that might have already + * been passed over. + */ +ssize_t get_file_size(int fd) { + off_t current = lseek(fd, 0, SEEK_CUR); + if (current < 0) { + return -1; + } + off_t end = lseek(fd, 0, SEEK_END); + if (end < 0) { + return -1; + } + off_t err = lseek(fd, current, SEEK_SET); + if (err < 0) { + ALOGW("get_file_size could do SEEK_END but not SEEK_SET. We might have skipped data."); + return -1; + } + return (ssize_t)(end-current); +} + +// ========================================================================= +ProtoFileReader::ProtoFileReader(int fd) + :mFd(fd), + mStatus(NO_ERROR), + mSize(get_file_size(fd)), + mPos(0), + mOffset(0), + mChunkSize(sizeof(mBuffer)) { +} + +ProtoFileReader::~ProtoFileReader() { +} + +ssize_t +ProtoFileReader::size() const +{ + return (ssize_t)mSize; +} + +size_t +ProtoFileReader::bytesRead() const +{ + return mPos; +} + +uint8_t const* +ProtoFileReader::readBuffer() +{ + return hasNext() ? mBuffer + mOffset : NULL; +} + +size_t +ProtoFileReader::currentToRead() +{ + return mMaxOffset - mOffset; +} + +bool +ProtoFileReader::hasNext() +{ + return ensure_data(); +} + +uint8_t +ProtoFileReader::next() +{ + if (!ensure_data()) { + // Shouldn't get to here. Always call hasNext() before calling next(). + return 0; + } + return mBuffer[mOffset++]; +} + +uint64_t +ProtoFileReader::readRawVarint() +{ + uint64_t val = 0, shift = 0; + while (true) { + if (!hasNext()) { + ALOGW("readRawVarint() called without hasNext() called first."); + mStatus = NOT_ENOUGH_DATA; + return 0; + } + uint8_t byte = next(); + val |= (INT64_C(0x7F) & byte) << shift; + if ((byte & 0x80) == 0) break; + shift += 7; + } + return val; +} + +void +ProtoFileReader::move(size_t amt) +{ + while (mStatus == NO_ERROR && amt > 0) { + if (!ensure_data()) { + return; + } + const size_t chunk = mMaxOffset - mOffset < amt ? amt : mMaxOffset - mOffset; + mOffset += chunk; + amt -= chunk; + } +} + +status_t +ProtoFileReader::getError() const { + return mStatus; +} + +bool +ProtoFileReader::ensure_data() { + if (mStatus != NO_ERROR) { + return false; + } + if (mOffset < mMaxOffset) { + return true; + } + ssize_t amt = TEMP_FAILURE_RETRY(read(mFd, mBuffer, mChunkSize)); + if (amt == 0) { + return false; + } else if (amt < 0) { + mStatus = -errno; + return false; + } else { + mOffset = 0; + mMaxOffset = amt; + return true; + } +} + + +} // util +} // android + diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp index ff3fad6055e1..ccbb83b2d342 100644 --- a/libs/protoutil/src/ProtoOutputStream.cpp +++ b/libs/protoutil/src/ProtoOutputStream.cpp @@ -27,7 +27,7 @@ namespace android { namespace util { ProtoOutputStream::ProtoOutputStream() - :mBuffer(), + :mBuffer(new EncodedBuffer()), mCopyBegin(0), mCompact(false), mDepth(0), @@ -44,7 +44,7 @@ ProtoOutputStream::~ProtoOutputStream() void ProtoOutputStream::clear() { - mBuffer.clear(); + mBuffer->clear(); mCopyBegin = 0; mCompact = false; mDepth = 0; @@ -226,13 +226,13 @@ ProtoOutputStream::start(uint64_t fieldId) } uint32_t id = (uint32_t)fieldId; - size_t prevPos = mBuffer.wp()->pos(); - mBuffer.writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); - size_t sizePos = mBuffer.wp()->pos(); + size_t prevPos = mBuffer->wp()->pos(); + mBuffer->writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); + size_t sizePos = mBuffer->wp()->pos(); mDepth++; mObjectId++; - mBuffer.writeRawFixed64(mExpectedObjectToken); // push previous token into stack. + mBuffer->writeRawFixed64(mExpectedObjectToken); // push previous token into stack. mExpectedObjectToken = makeToken(sizePos - prevPos, (bool)(fieldId & FIELD_COUNT_REPEATED), mDepth, mObjectId, sizePos); @@ -258,26 +258,26 @@ ProtoOutputStream::end(uint64_t token) uint32_t sizePos = getSizePosFromToken(token); // number of bytes written in this start-end session. - int childRawSize = mBuffer.wp()->pos() - sizePos - 8; + int childRawSize = mBuffer->wp()->pos() - sizePos - 8; // retrieve the old token from stack. - mBuffer.ep()->rewind()->move(sizePos); - mExpectedObjectToken = mBuffer.readRawFixed64(); + mBuffer->ep()->rewind()->move(sizePos); + mExpectedObjectToken = mBuffer->readRawFixed64(); // If raw size is larger than 0, write the negative value here to indicate a compact is needed. if (childRawSize > 0) { - mBuffer.editRawFixed32(sizePos, -childRawSize); - mBuffer.editRawFixed32(sizePos+4, -1); + mBuffer->editRawFixed32(sizePos, -childRawSize); + mBuffer->editRawFixed32(sizePos+4, -1); } else { // reset wp which erase the header tag of the message when its size is 0. - mBuffer.wp()->rewind()->move(sizePos - getTagSizeFromToken(token)); + mBuffer->wp()->rewind()->move(sizePos - getTagSizeFromToken(token)); } } size_t ProtoOutputStream::bytesWritten() { - return mBuffer.size(); + return mBuffer->size(); } bool @@ -288,26 +288,26 @@ ProtoOutputStream::compact() { return false; } // record the size of the original buffer. - size_t rawBufferSize = mBuffer.size(); + size_t rawBufferSize = mBuffer->size(); if (rawBufferSize == 0) return true; // nothing to do if the buffer is empty; // reset edit pointer and recursively compute encoded size of messages. - mBuffer.ep()->rewind(); + mBuffer->ep()->rewind(); if (editEncodedSize(rawBufferSize) == 0) { ALOGE("Failed to editEncodedSize."); return false; } // reset both edit pointer and write pointer, and compact recursively. - mBuffer.ep()->rewind(); - mBuffer.wp()->rewind(); + mBuffer->ep()->rewind(); + mBuffer->wp()->rewind(); if (!compactSize(rawBufferSize)) { ALOGE("Failed to compactSize."); return false; } // copy the reset to the buffer. if (mCopyBegin < rawBufferSize) { - mBuffer.copy(mCopyBegin, rawBufferSize - mCopyBegin); + mBuffer->copy(mCopyBegin, rawBufferSize - mCopyBegin); } // mark true means it is not legal to write to this ProtoOutputStream anymore @@ -322,34 +322,34 @@ ProtoOutputStream::compact() { size_t ProtoOutputStream::editEncodedSize(size_t rawSize) { - size_t objectStart = mBuffer.ep()->pos(); + size_t objectStart = mBuffer->ep()->pos(); size_t objectEnd = objectStart + rawSize; size_t encodedSize = 0; int childRawSize, childEncodedSize; size_t childEncodedSizePos; - while (mBuffer.ep()->pos() < objectEnd) { - uint32_t tag = (uint32_t)mBuffer.readRawVarint(); + while (mBuffer->ep()->pos() < objectEnd) { + uint32_t tag = (uint32_t)mBuffer->readRawVarint(); encodedSize += get_varint_size(tag); switch (read_wire_type(tag)) { case WIRE_TYPE_VARINT: do { encodedSize++; - } while ((mBuffer.readRawByte() & 0x80) != 0); + } while ((mBuffer->readRawByte() & 0x80) != 0); break; case WIRE_TYPE_FIXED64: encodedSize += 8; - mBuffer.ep()->move(8); + mBuffer->ep()->move(8); break; case WIRE_TYPE_LENGTH_DELIMITED: - childRawSize = (int)mBuffer.readRawFixed32(); - childEncodedSizePos = mBuffer.ep()->pos(); - childEncodedSize = (int)mBuffer.readRawFixed32(); + childRawSize = (int)mBuffer->readRawFixed32(); + childEncodedSizePos = mBuffer->ep()->pos(); + childEncodedSize = (int)mBuffer->readRawFixed32(); if (childRawSize >= 0 && childRawSize == childEncodedSize) { - mBuffer.ep()->move(childRawSize); + mBuffer->ep()->move(childRawSize); } else if (childRawSize < 0 && childEncodedSize == -1){ childEncodedSize = editEncodedSize(-childRawSize); - mBuffer.editRawFixed32(childEncodedSizePos, childEncodedSize); + mBuffer->editRawFixed32(childEncodedSizePos, childEncodedSize); } else { ALOGE("Bad raw or encoded values: raw=%d, encoded=%d at %zu", childRawSize, childEncodedSize, childEncodedSizePos); @@ -359,7 +359,7 @@ ProtoOutputStream::editEncodedSize(size_t rawSize) break; case WIRE_TYPE_FIXED32: encodedSize += 4; - mBuffer.ep()->move(4); + mBuffer->ep()->move(4); break; default: ALOGE("Unexpected wire type %d in editEncodedSize at [%zu, %zu]", @@ -378,30 +378,30 @@ ProtoOutputStream::editEncodedSize(size_t rawSize) bool ProtoOutputStream::compactSize(size_t rawSize) { - size_t objectStart = mBuffer.ep()->pos(); + size_t objectStart = mBuffer->ep()->pos(); size_t objectEnd = objectStart + rawSize; int childRawSize, childEncodedSize; - while (mBuffer.ep()->pos() < objectEnd) { - uint32_t tag = (uint32_t)mBuffer.readRawVarint(); + while (mBuffer->ep()->pos() < objectEnd) { + uint32_t tag = (uint32_t)mBuffer->readRawVarint(); switch (read_wire_type(tag)) { case WIRE_TYPE_VARINT: - while ((mBuffer.readRawByte() & 0x80) != 0) {} + while ((mBuffer->readRawByte() & 0x80) != 0) {} break; case WIRE_TYPE_FIXED64: - mBuffer.ep()->move(8); + mBuffer->ep()->move(8); break; case WIRE_TYPE_LENGTH_DELIMITED: - mBuffer.copy(mCopyBegin, mBuffer.ep()->pos() - mCopyBegin); + mBuffer->copy(mCopyBegin, mBuffer->ep()->pos() - mCopyBegin); - childRawSize = (int)mBuffer.readRawFixed32(); - childEncodedSize = (int)mBuffer.readRawFixed32(); - mCopyBegin = mBuffer.ep()->pos(); + childRawSize = (int)mBuffer->readRawFixed32(); + childEncodedSize = (int)mBuffer->readRawFixed32(); + mCopyBegin = mBuffer->ep()->pos(); // write encoded size to buffer. - mBuffer.writeRawVarint32(childEncodedSize); + mBuffer->writeRawVarint32(childEncodedSize); if (childRawSize >= 0 && childRawSize == childEncodedSize) { - mBuffer.ep()->move(childEncodedSize); + mBuffer->ep()->move(childEncodedSize); } else if (childRawSize < 0){ if (!compactSize(-childRawSize)) return false; } else { @@ -411,7 +411,7 @@ ProtoOutputStream::compactSize(size_t rawSize) } break; case WIRE_TYPE_FIXED32: - mBuffer.ep()->move(4); + mBuffer->ep()->move(4); break; default: ALOGE("Unexpected wire type %d in compactSize at [%zu, %zu]", @@ -429,7 +429,7 @@ ProtoOutputStream::size() ALOGE("compact failed, the ProtoOutputStream data is corrupted!"); return 0; } - return mBuffer.size(); + return mBuffer->size(); } bool @@ -438,43 +438,45 @@ ProtoOutputStream::flush(int fd) if (fd < 0) return false; if (!compact()) return false; - EncodedBuffer::iterator it = mBuffer.begin(); - while (it.readBuffer() != NULL) { - if (!android::base::WriteFully(fd, it.readBuffer(), it.currentToRead())) return false; - it.rp()->move(it.currentToRead()); + sp<ProtoReader> reader = mBuffer->read(); + while (reader->readBuffer() != NULL) { + if (!android::base::WriteFully(fd, reader->readBuffer(), reader->currentToRead())) { + return false; + } + reader->move(reader->currentToRead()); } return true; } -EncodedBuffer::iterator +sp<ProtoReader> ProtoOutputStream::data() { if (!compact()) { ALOGE("compact failed, the ProtoOutputStream data is corrupted!"); - mBuffer.clear(); + mBuffer->clear(); } - return mBuffer.begin(); + return mBuffer->read(); } void ProtoOutputStream::writeRawVarint(uint64_t varint) { - mBuffer.writeRawVarint64(varint); + mBuffer->writeRawVarint64(varint); } void ProtoOutputStream::writeLengthDelimitedHeader(uint32_t id, size_t size) { - mBuffer.writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); + mBuffer->writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED); // reserves 64 bits for length delimited fields, if first field is negative, compact it. - mBuffer.writeRawFixed32(size); - mBuffer.writeRawFixed32(size); + mBuffer->writeRawFixed32(size); + mBuffer->writeRawFixed32(size); } void ProtoOutputStream::writeRawByte(uint8_t byte) { - mBuffer.writeRawByte(byte); + mBuffer->writeRawByte(byte); } @@ -494,99 +496,99 @@ inline To bit_cast(From const &from) { inline void ProtoOutputStream::writeDoubleImpl(uint32_t id, double val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED64); - mBuffer.writeRawFixed64(bit_cast<double, uint64_t>(val)); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED64); + mBuffer->writeRawFixed64(bit_cast<double, uint64_t>(val)); } inline void ProtoOutputStream::writeFloatImpl(uint32_t id, float val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED32); - mBuffer.writeRawFixed32(bit_cast<float, uint32_t>(val)); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED32); + mBuffer->writeRawFixed32(bit_cast<float, uint32_t>(val)); } inline void ProtoOutputStream::writeInt64Impl(uint32_t id, int64_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint64(val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint64(val); } inline void ProtoOutputStream::writeInt32Impl(uint32_t id, int32_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32(val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32(val); } inline void ProtoOutputStream::writeUint64Impl(uint32_t id, uint64_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint64(val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint64(val); } inline void ProtoOutputStream::writeUint32Impl(uint32_t id, uint32_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32(val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32(val); } inline void ProtoOutputStream::writeFixed64Impl(uint32_t id, uint64_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED64); - mBuffer.writeRawFixed64(val); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED64); + mBuffer->writeRawFixed64(val); } inline void ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED32); - mBuffer.writeRawFixed32(val); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED32); + mBuffer->writeRawFixed32(val); } inline void ProtoOutputStream::writeSFixed64Impl(uint32_t id, int64_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED64); - mBuffer.writeRawFixed64(val); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED64); + mBuffer->writeRawFixed64(val); } inline void ProtoOutputStream::writeSFixed32Impl(uint32_t id, int32_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_FIXED32); - mBuffer.writeRawFixed32(val); + mBuffer->writeHeader(id, WIRE_TYPE_FIXED32); + mBuffer->writeRawFixed32(val); } inline void ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, int64_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint64((val << 1) ^ (val >> 63)); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint64((val << 1) ^ (val >> 63)); } inline void ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int32_t val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32((val << 1) ^ (val >> 31)); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32((val << 1) ^ (val >> 31)); } inline void ProtoOutputStream::writeEnumImpl(uint32_t id, int val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32((uint32_t) val); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32((uint32_t) val); } inline void ProtoOutputStream::writeBoolImpl(uint32_t id, bool val) { - mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32(val ? 1 : 0); + mBuffer->writeHeader(id, WIRE_TYPE_VARINT); + mBuffer->writeRawVarint32(val ? 1 : 0); } inline void @@ -595,7 +597,7 @@ ProtoOutputStream::writeUtf8StringImpl(uint32_t id, const char* val, size_t size if (val == NULL) return; writeLengthDelimitedHeader(id, size); for (size_t i=0; i<size; i++) { - mBuffer.writeRawByte((uint8_t)val[i]); + mBuffer->writeRawByte((uint8_t)val[i]); } } @@ -605,7 +607,7 @@ ProtoOutputStream::writeMessageBytesImpl(uint32_t id, const char* val, size_t si if (val == NULL) return; writeLengthDelimitedHeader(id, size); for (size_t i=0; i<size; i++) { - mBuffer.writeRawByte(val[i]); + mBuffer->writeRawByte(val[i]); } } diff --git a/libs/protoutil/src/ProtoReader.cpp b/libs/protoutil/src/ProtoReader.cpp new file mode 100644 index 000000000000..4f2a9f1f5978 --- /dev/null +++ b/libs/protoutil/src/ProtoReader.cpp @@ -0,0 +1,30 @@ +/* + * 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 LOG_TAG "libprotoutil" + +#include <android/util/ProtoReader.h> + +namespace android { +namespace util { + +ProtoReader::ProtoReader() { +} + +ProtoReader::~ProtoReader() { +} + +} // util +} // android diff --git a/libs/services/Android.bp b/libs/services/Android.bp index 3d57fbdd0dcd..1b9939d9a598 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -18,6 +18,7 @@ cc_library_shared { name: "libservices", srcs: [ ":IDropBoxManagerService.aidl", + "src/content/ComponentName.cpp", "src/os/DropBoxManager.cpp", "src/os/StatsDimensionsValue.cpp", "src/os/StatsLogEventWrapper.cpp", diff --git a/libs/services/include/android/content/ComponentName.h b/libs/services/include/android/content/ComponentName.h new file mode 100644 index 000000000000..6bf46b4bc28e --- /dev/null +++ b/libs/services/include/android/content/ComponentName.h @@ -0,0 +1,56 @@ +/* + * 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 <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/Status.h> +#include <string> +#include <vector> + +namespace android { +namespace content { + +using namespace std; + +class ComponentName: public android::Parcelable { + public: + ComponentName(); + ComponentName(const ComponentName& that); + ComponentName(const string& pkg, const string& cls); + virtual ~ComponentName(); + + bool operator<(const ComponentName& that) const; + + const string& getPackageName() const { return mPackage; } + const string& getClassName() const { return mClass; } + + virtual android::status_t writeToParcel(android::Parcel* out) const override; + virtual android::status_t readFromParcel(const android::Parcel* in) override; + +private: + string mPackage; + string mClass; +}; + + + +} // namespace os +} // namespace android + + + diff --git a/libs/services/src/content/ComponentName.cpp b/libs/services/src/content/ComponentName.cpp new file mode 100644 index 000000000000..adb67ee7c61a --- /dev/null +++ b/libs/services/src/content/ComponentName.cpp @@ -0,0 +1,88 @@ +/* + * 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 <android/content/ComponentName.h> + +namespace android { +namespace content { + +ComponentName::ComponentName() + :mPackage(), + mClass() { +} + +ComponentName::ComponentName(const ComponentName& that) + :mPackage(that.mPackage), + mClass(that.mClass) { +} + +ComponentName::ComponentName(const string& pkg, const string& cls) + :mPackage(pkg), + mClass(cls) { +} + +ComponentName::~ComponentName() { +} + +bool ComponentName::operator<(const ComponentName& that) const { + if (mPackage < that.mPackage) { + return true; + } else if (mPackage > that.mPackage) { + return false; + } + return mClass < that.mClass; +} + +status_t ComponentName::readFromParcel(const Parcel* in) { + status_t err; + + // Note: This is a subtle variation from the java version, which + // requires non-null strings, but does not require non-empty strings. + // This code implicitly requires non-null strings, because it's impossible, + // but reading null strings that were somehow written by the java + // code would turn them into empty strings. + + err = in->readUtf8FromUtf16(&mPackage); + if (err != NO_ERROR) { + return err; + } + + err = in->readUtf8FromUtf16(&mClass); + if (err != NO_ERROR) { + return err; + } + + return NO_ERROR; +} + +status_t ComponentName::writeToParcel(android::Parcel* out) const { + status_t err; + + err = out->writeUtf8AsUtf16(mPackage); + if (err != NO_ERROR) { + return err; + } + + err = out->writeUtf8AsUtf16(mClass); + if (err != NO_ERROR) { + return err; + } + + return NO_ERROR; +} + +}} // namespace android::content + diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp index 681d5f780739..429f996bd65e 100644 --- a/libs/services/src/os/DropBoxManager.cpp +++ b/libs/services/src/os/DropBoxManager.cpp @@ -225,7 +225,10 @@ DropBoxManager::add(const Entry& entry) if (service == NULL) { return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); } - return service->add(entry); + ALOGD("About to call service->add()"); + Status status = service->add(entry); + ALOGD("service->add returned %s", status.toString8().string()); + return status; } }} // namespace android::os diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index b393d8758f3b..f0f8adbb6dda 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -231,6 +231,8 @@ public final class DropBoxManagerService extends SystemService { final String tag = entry.getTag(); try { int flags = entry.getFlags(); + Slog.i(TAG, "add tag=" + tag + " isTagEnabled=" + isTagEnabled(tag) + + " flags=0x" + Integer.toHexString(flags)); if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException(); init(); @@ -285,7 +287,8 @@ public final class DropBoxManagerService extends SystemService { long len = temp.length(); if (len > max) { - Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)"); + Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + + max + " bytes)"); temp.delete(); temp = null; // Pass temp = null to createEntry() to leave a tombstone break; diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java index 6f2bfc3cd8d2..55e054b6c8cd 100644 --- a/services/core/java/com/android/server/incident/IncidentCompanionService.java +++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java @@ -16,10 +16,23 @@ package com.android.server.incident; +import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.Binder; import android.os.IIncidentAuthListener; import android.os.IIncidentCompanion; +import android.os.IIncidentManager; +import android.os.IncidentManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; @@ -36,6 +49,14 @@ public class IncidentCompanionService extends SystemService { static final String TAG = "IncidentCompanionService"; /** + * The two permissions, for sendBroadcastAsUserMultiplePermissions. + */ + private static final String[] DUMP_AND_USAGE_STATS_PERMISSIONS = new String[] { + android.Manifest.permission.DUMP, + android.Manifest.permission.PACKAGE_USAGE_STATS + }; + + /** * Tracker for reports pending approval. */ private PendingReports mPendingReports; @@ -45,16 +66,21 @@ public class IncidentCompanionService extends SystemService { */ private final class BinderService extends IIncidentCompanion.Stub { /** - * ONEWAY binder call to initiate authorizing the report. + * ONEWAY binder call to initiate authorizing the report. If you don't need + * IncidentCompanionService to check whether the calling UID matches then + * pass 0 for callingUid. Either way, the caller must have DUMP and USAGE_STATS + * permissions to retrieve the data, so it ends up being about the same. */ @Override - public void authorizeReport(int callingUid, final String callingPackage, int flags, - final IIncidentAuthListener listener) { + public void authorizeReport(int callingUid, final String callingPackage, + final String receiverClass, final String reportId, + final int flags, final IIncidentAuthListener listener) { enforceRequestAuthorizationPermission(); final long ident = Binder.clearCallingIdentity(); try { - mPendingReports.authorizeReport(callingUid, callingPackage, flags, listener); + mPendingReports.authorizeReport(callingUid, callingPackage, + receiverClass, reportId, flags, listener); } finally { Binder.restoreCallingIdentity(ident); } @@ -82,6 +108,38 @@ public class IncidentCompanionService extends SystemService { } /** + * ONEWAY implementation to send broadcast from incidentd, which is native. + */ + @Override + public void sendReportReadyBroadcast(String pkg, String cls) { + enforceRequestAuthorizationPermission(); + + final long ident = Binder.clearCallingIdentity(); + try { + final Context context = getContext(); + + final int primaryUser = getAndValidateUser(context); + if (primaryUser == UserHandle.USER_NULL) { + return; + } + + final Intent intent = new Intent(Intent.ACTION_INCIDENT_REPORT_READY); + intent.setComponent(new ComponentName(pkg, cls)); + + Log.d(TAG, "sendReportReadyBroadcast sending primaryUser=" + primaryUser + + " userHandle=" + UserHandle.getUserHandleForUid(primaryUser) + + " intent=" + intent); + + // Send it to the primary user. Only they can do incident reports. + context.sendBroadcastAsUserMultiplePermissions(intent, + UserHandle.getUserHandleForUid(primaryUser), + DUMP_AND_USAGE_STATS_PERMISSIONS); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** * SYNCHRONOUS binder call to get the list of reports that are pending confirmation * by the user. */ @@ -122,7 +180,80 @@ public class IncidentCompanionService extends SystemService { } /** - * Implementation of adb shell dumpsys debugreportcompanion. + * SYNCHRONOUS binder call to get the list of incident reports waiting for a receiver. + */ + @Override + public List<String> getIncidentReportList(String pkg, String cls) throws RemoteException { + enforceAccessReportsPermissions(null); + + final long ident = Binder.clearCallingIdentity(); + try { + return getIIncidentManager().getIncidentReportList(pkg, cls); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * SYNCHRONOUS binder call to commit an incident report + */ + @Override + public void deleteIncidentReports(String pkg, String cls, String id) + throws RemoteException { + if (pkg == null || cls == null || id == null + || pkg.length() == 0 || cls.length() == 0 || id.length() == 0) { + throw new RuntimeException("Invalid pkg, cls or id"); + } + enforceAccessReportsPermissions(pkg); + + final long ident = Binder.clearCallingIdentity(); + try { + getIIncidentManager().deleteIncidentReports(pkg, cls, id); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * SYNCHRONOUS binder call to delete all incident reports for a package. + */ + @Override + public void deleteAllIncidentReports(String pkg) throws RemoteException { + if (pkg == null || pkg.length() == 0) { + throw new RuntimeException("Invalid pkg"); + } + enforceAccessReportsPermissions(pkg); + + final long ident = Binder.clearCallingIdentity(); + try { + getIIncidentManager().deleteAllIncidentReports(pkg); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * SYNCHRONOUS binder call to get the IncidentReport object. + */ + @Override + public IncidentManager.IncidentReport getIncidentReport(String pkg, String cls, String id) + throws RemoteException { + if (pkg == null || cls == null || id == null + || pkg.length() == 0 || cls.length() == 0 || id.length() == 0) { + throw new RuntimeException("Invalid pkg, cls or id"); + } + enforceAccessReportsPermissions(pkg); + + final long ident = Binder.clearCallingIdentity(); + try { + return getIIncidentManager().getIncidentReport(pkg, cls, id); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * SYNCHRONOUS implementation of adb shell dumpsys debugreportcompanion. */ @Override protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { @@ -150,6 +281,51 @@ public class IncidentCompanionService extends SystemService { android.Manifest.permission.APPROVE_INCIDENT_REPORTS, null); } + /** + * Enforce that the calling process either has APPROVE_INCIDENT_REPORTS or + * (DUMP and PACKAGE_USAGE_STATS). This lets the approver get, because showing + * information about the report is a prerequisite for letting the user decide. + * + * If pkg is null, it is not checked, so make sure that you check it for null first + * if you do need the packages to match. + * + * Inside the binder interface class because we want to do all of the authorization + * here, before calling out to the helper objects. + */ + private void enforceAccessReportsPermissions(String pkg) { + if (getContext().checkCallingPermission( + android.Manifest.permission.APPROVE_INCIDENT_REPORTS) + != PackageManager.PERMISSION_GRANTED) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DUMP, null); + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.PACKAGE_USAGE_STATS, null); + if (pkg == null) { + enforceCallerIsSameApp(pkg); + } + } + } + + /** + * Throw a SecurityException if the incoming binder call is not from pkg. + */ + private void enforceCallerIsSameApp(String pkg) throws SecurityException { + try { + final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getCallingUserId(); + final ApplicationInfo ai = getContext().getPackageManager() + .getApplicationInfoAsUser(pkg, 0, userId); + if (ai == null) { + throw new SecurityException("Unknown package " + pkg); + } + if (!UserHandle.isSameApp(ai.uid, uid)) { + throw new SecurityException("Calling uid " + uid + " gave package " + + pkg + " which is owned by uid " + ai.uid); + } + } catch (PackageManager.NameNotFoundException re) { + throw new SecurityException("Unknown package " + pkg + "\n" + re); + } + } } /** @@ -182,5 +358,52 @@ public class IncidentCompanionService extends SystemService { break; } } + + /** + * Looks up incidentd every time, so we don't need a complex handshake between + * incidentd and IncidentCompanionService. + */ + private IIncidentManager getIIncidentManager() throws RemoteException { + return IIncidentManager.Stub.asInterface( + ServiceManager.getService(Context.INCIDENT_SERVICE)); + } + + /** + * Check whether the current user is the primary user, and return the user id if they are. + * Returns UserHandle.USER_NULL if not valid. + */ + public static int getAndValidateUser(Context context) { + // Current user + UserInfo currentUser; + try { + currentUser = ActivityManager.getService().getCurrentUser(); + } catch (RemoteException ex) { + // We're already inside the system process. + throw new RuntimeException(ex); + } + + // Primary user + final UserManager um = UserManager.get(context); + final UserInfo primaryUser = um.getPrimaryUser(); + + // Check that we're using the right user. + if (currentUser == null) { + Log.w(TAG, "No current user. Nobody to approve the report." + + " The report will be denied."); + return UserHandle.USER_NULL; + } + if (primaryUser == null) { + Log.w(TAG, "No primary user. Nobody to approve the report." + + " The report will be denied."); + return UserHandle.USER_NULL; + } + if (primaryUser.id != currentUser.id) { + Log.w(TAG, "Only the primary user can approve bugreports, but they are not" + + " the current user. The report will be denied."); + return UserHandle.USER_NULL; + } + + return primaryUser.id; + } } diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java index 519ed41670f6..a749d2657dff 100644 --- a/services/core/java/com/android/server/incident/PendingReports.java +++ b/services/core/java/com/android/server/incident/PendingReports.java @@ -16,14 +16,12 @@ package com.android.server.incident; -import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.UserInfo; import android.net.Uri; import android.os.Handler; import android.os.IIncidentAuthListener; @@ -31,7 +29,6 @@ import android.os.IncidentManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; -import android.os.UserManager; import android.util.Log; import java.io.FileDescriptor; @@ -76,24 +73,29 @@ class PendingReports { public IIncidentAuthListener listener; public long addedRealtime; public long addedWalltime; + public String receiverClass; + public String reportId; /** * Construct a PendingReportRec, with an auto-incremented id. */ - PendingReportRec(String callingPackage, int flags, IIncidentAuthListener listener) { + PendingReportRec(String callingPackage, String receiverClass, String reportId, int flags, + IIncidentAuthListener listener) { this.id = mNextPendingId++; this.callingPackage = callingPackage; this.flags = flags; this.listener = listener; this.addedRealtime = SystemClock.elapsedRealtime(); this.addedWalltime = System.currentTimeMillis(); + this.receiverClass = receiverClass; + this.reportId = reportId; } /** * Get the Uri that contains the flattened data. */ Uri getUri() { - return (new Uri.Builder()) + final Uri.Builder builder = (new Uri.Builder()) .scheme(IncidentManager.URI_SCHEME) .authority(IncidentManager.URI_AUTHORITY) .path(IncidentManager.URI_PATH) @@ -101,8 +103,15 @@ class PendingReports { .appendQueryParameter(IncidentManager.URI_PARAM_CALLING_PACKAGE, callingPackage) .appendQueryParameter(IncidentManager.URI_PARAM_FLAGS, Integer.toString(flags)) .appendQueryParameter(IncidentManager.URI_PARAM_TIMESTAMP, - Long.toString(addedWalltime)) - .build(); + Long.toString(addedWalltime)); + if (receiverClass != null && receiverClass.length() > 0) { + builder.appendQueryParameter(IncidentManager.URI_PARAM_RECEIVER_CLASS, + receiverClass); + } + if (reportId != null && reportId.length() > 0) { + builder.appendQueryParameter(IncidentManager.URI_PARAM_REPORT_ID, reportId); + } + return builder.build(); } } @@ -121,13 +130,15 @@ class PendingReports { * <p> * The security checks are handled by IncidentCompanionService. */ - public void authorizeReport(int callingUid, final String callingPackage, final int flags, + public void authorizeReport(int callingUid, final String callingPackage, + final String receiverClass, final String reportId, final int flags, final IIncidentAuthListener listener) { // Starting the system server is complicated, and rather than try to // have a complicated lifecycle that we share with dumpstated and incidentd, // we will accept the request, and then display it whenever it becomes possible to. mRequestQueue.enqueue(listener.asBinder(), true, () -> { - authorizeReportImpl(callingUid, callingPackage, flags, listener); + authorizeReportImpl(callingUid, callingPackage, receiverClass, reportId, + flags, listener); }); } @@ -248,10 +259,11 @@ class PendingReports { /** * Start the confirmation process. */ - private void authorizeReportImpl(int callingUid, final String callingPackage, int flags, - final IIncidentAuthListener listener) { + private void authorizeReportImpl(int callingUid, final String callingPackage, + final String receiverClass, final String reportId, + int flags, final IIncidentAuthListener listener) { // Enforce that the calling package pertains to the callingUid. - if (!isPackageInUid(callingUid, callingPackage)) { + if (callingUid != 0 && !isPackageInUid(callingUid, callingPackage)) { Log.w(TAG, "Calling uid " + callingUid + " doesn't match package " + callingPackage); denyReportBeforeAddingRec(listener, callingPackage); @@ -277,7 +289,7 @@ class PendingReports { // Save the record for when the PermissionController comes back to authorize it. PendingReportRec rec = null; synchronized (mLock) { - rec = new PendingReportRec(callingPackage, flags, listener); + rec = new PendingReportRec(callingPackage, receiverClass, reportId, flags, listener); mPending.add(rec); } @@ -406,37 +418,7 @@ class PendingReports { * Returns UserHandle.USER_NULL if not valid. */ private int getAndValidateUser() { - // Current user - UserInfo currentUser; - try { - currentUser = ActivityManager.getService().getCurrentUser(); - } catch (RemoteException ex) { - // We're already inside the system process. - throw new RuntimeException(ex); - } - - // Primary user - final UserManager um = UserManager.get(mContext); - final UserInfo primaryUser = um.getPrimaryUser(); - - // Check that we're using the right user. - if (currentUser == null) { - Log.w(TAG, "No current user. Nobody to approve the report." - + " The report will be denied."); - return UserHandle.USER_NULL; - } - if (primaryUser == null) { - Log.w(TAG, "No primary user. Nobody to approve the report." - + " The report will be denied."); - return UserHandle.USER_NULL; - } - if (primaryUser.id != currentUser.id) { - Log.w(TAG, "Only the primary user can approve bugreports, but they are not" - + " the current user. The report will be denied."); - return UserHandle.USER_NULL; - } - - return primaryUser.id; + return IncidentCompanionService.getAndValidateUser(mContext); } /** diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp index be33afcf0f38..17a3c7a91329 100644 --- a/tools/incident_report/main.cpp +++ b/tools/incident_report/main.cpp @@ -277,147 +277,6 @@ print_message(Out* out, Descriptor const* descriptor, GenericMessage const* mess } // ================================================================================ -static uint8_t* -write_raw_varint(uint8_t* buf, uint32_t val) -{ - uint8_t* p = buf; - while (true) { - if ((val & ~0x7F) == 0) { - *p++ = (uint8_t)val; - return p; - } else { - *p++ = (uint8_t)((val & 0x7F) | 0x80); - val >>= 7; - } - } -} - -static int -write_all(int fd, uint8_t const* buf, size_t size) -{ - while (size > 0) { - ssize_t amt = ::write(fd, buf, size); - if (amt < 0) { - return errno; - } - size -= amt; - buf += amt; - } - return 0; -} - -static int -adb_incident_workaround(const char* adbSerial, const vector<string>& sections) -{ - const int maxAllowedSize = 20 * 1024 * 1024; // 20MB - unique_ptr<uint8_t[]> buffer(new uint8_t[maxAllowedSize]); - - for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { - Descriptor const* descriptor = IncidentProto::descriptor(); - FieldDescriptor const* field; - - // Get the name and field id. - string name = *it; - char* end; - int id = strtol(name.c_str(), &end, 0); - if (*end == '\0') { - // If it's an id, find out the string. - field = descriptor->FindFieldByNumber(id); - if (field == NULL) { - fprintf(stderr, "Unable to find field number: %d\n", id); - return 1; - } - name = field->name(); - } else { - // If it's a string, find out the id. - field = descriptor->FindFieldByName(name); - if (field == NULL) { - fprintf(stderr, "Unable to find field: %s\n", name.c_str()); - return 1; - } - id = field->number(); - } - - int pfd[2]; - if (pipe(pfd) != 0) { - fprintf(stderr, "pipe failed: %s\n", strerror(errno)); - return 1; - } - - pid_t pid = fork(); - if (pid == -1) { - fprintf(stderr, "fork failed: %s\n", strerror(errno)); - return 1; - } else if (pid == 0) { - // child - dup2(pfd[1], STDOUT_FILENO); - close(pfd[0]); - close(pfd[1]); - - char const** args = (char const**)malloc(sizeof(char*) * 8); - int argpos = 0; - args[argpos++] = "adb"; - if (adbSerial != NULL) { - args[argpos++] = "-s"; - args[argpos++] = adbSerial; - } - args[argpos++] = "shell"; - args[argpos++] = "dumpsys"; - args[argpos++] = name.c_str(); - args[argpos++] = "--proto"; - args[argpos++] = NULL; - execvp(args[0], (char*const*)args); - fprintf(stderr, "execvp failed: %s\n", strerror(errno)); - free(args); - return 1; - } else { - // parent - close(pfd[1]); - - size_t size = 0; - while (size < maxAllowedSize) { - ssize_t amt = read(pfd[0], buffer.get() + size, maxAllowedSize - size); - if (amt == 0) { - break; - } else if (amt == -1) { - fprintf(stderr, "read error: %s\n", strerror(errno)); - return 1; - } - size += amt; - } - - int status; - do { - waitpid(pid, &status, 0); - } while (!WIFEXITED(status)); - if (WEXITSTATUS(status) != 0) { - return WEXITSTATUS(status); - } - - if (size > 0) { - uint8_t header[20]; - uint8_t* p = write_raw_varint(header, (id << 3) | 2); - p = write_raw_varint(p, size); - int err = write_all(STDOUT_FILENO, header, p-header); - if (err != 0) { - fprintf(stderr, "write error: %s\n", strerror(err)); - return 1; - } - err = write_all(STDOUT_FILENO, buffer.get(), size); - if (err != 0) { - fprintf(stderr, "write error: %s\n", strerror(err)); - return 1; - } - } - - close(pfd[0]); - } - } - - return 0; -} - -// ================================================================================ static void usage(FILE* out) { @@ -449,7 +308,6 @@ main(int argc, char** argv) const char* inFilename = NULL; const char* outFilename = NULL; const char* adbSerial = NULL; - bool adbIncidentWorkaround = true; pid_t childPid = -1; vector<string> sections; const char* privacy = NULL; @@ -475,9 +333,6 @@ main(int argc, char** argv) case 'h': usage(stdout); return 0; - case 'w': - adbIncidentWorkaround = false; - break; case 'p': privacy = optarg; break; @@ -517,19 +372,10 @@ main(int argc, char** argv) fprintf(stderr, "fork failed: %s\n", strerror(errno)); return 1; } else if (childPid == 0) { + // child dup2(pfd[1], STDOUT_FILENO); close(pfd[0]); close(pfd[1]); - // child - if (adbIncidentWorkaround) { - // TODO: Until the device side incident command is checked in, - // the incident_report builds the outer Incident proto by hand - // from individual adb shell dumpsys <service> --proto calls, - // with a maximum allowed output size. - return adb_incident_workaround(adbSerial, sections); - } - - // TODO: This is what the real implementation will be... char const** args = (char const**)malloc(sizeof(char*) * (8 + sections.size())); int argpos = 0; args[argpos++] = "adb"; |