Incident Report Extension API

Add an API for priv and system app to register a dump callback with
Incident Service.

Bug: 145924375
Test: Register a callback dumping a string. Capture an incident report
      and verify that the customized section exist.

Change-Id: I6fff6c1ee97e25963068d284ba37adce1bb5ec31
This commit is contained in:
Mike Ma
2019-12-17 10:56:17 -08:00
parent a4d4f94e21
commit 643de9238b
10 changed files with 317 additions and 64 deletions

View File

@@ -408,6 +408,7 @@ filegroup {
filegroup {
name: "libincident_aidl",
srcs: [
"core/java/android/os/IIncidentDumpCallback.aidl",
"core/java/android/os/IIncidentManager.aidl",
"core/java/android/os/IIncidentReportStatusListener.aidl",
],

View File

@@ -123,14 +123,17 @@ static string build_uri(const string& pkg, const string& cls, const string& id)
// ================================================================================
ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
const sp<Throttler>& throttler)
const sp<Broadcaster>& broadcaster,
const sp<Looper>& handlerLooper,
const sp<Throttler>& throttler,
const vector<BringYourOwnSection*>& registeredSections)
:mLock(),
mWorkDirectory(workDirectory),
mBroadcaster(broadcaster),
mHandlerLooper(handlerLooper),
mBacklogDelay(DEFAULT_DELAY_NS),
mThrottler(throttler),
mRegisteredSections(registeredSections),
mBatch(new ReportBatch()) {
}
@@ -185,7 +188,7 @@ void ReportHandler::take_report() {
return;
}
sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
// 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.
@@ -237,7 +240,7 @@ IncidentService::IncidentService(const sp<Looper>& handlerLooper) {
mWorkDirectory = new WorkDirectory();
mBroadcaster = new Broadcaster(mWorkDirectory);
mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
mThrottler);
mThrottler, mRegisteredSections);
mBroadcaster->setHandler(mHandler);
}
@@ -327,6 +330,11 @@ Status IncidentService::reportIncidentToDumpstate(unique_fd stream,
incidentArgs.addSection(id);
}
}
for (const Section* section : mRegisteredSections) {
if (!section_requires_specific_mention(section->id)) {
incidentArgs.addSection(section->id);
}
}
// The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
@@ -339,6 +347,45 @@ Status IncidentService::reportIncidentToDumpstate(unique_fd stream,
return Status::ok();
}
Status IncidentService::registerSection(const int id, const String16& name16,
const sp<IIncidentDumpCallback>& callback) {
const char* name = String8(name16).c_str();
ALOGI("Register section %d: %s", id, name);
if (callback == nullptr) {
return Status::fromExceptionCode(Status::EX_NULL_POINTER);
}
const uid_t callingUid = IPCThreadState::self()->getCallingUid();
for (int i = 0; i < mRegisteredSections.size(); i++) {
if (mRegisteredSections.at(i)->id == id) {
if (mRegisteredSections.at(i)->uid != callingUid) {
ALOGW("Error registering section %d: calling uid does not match", id);
return Status::fromExceptionCode(Status::EX_SECURITY);
}
mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback);
return Status::ok();
}
}
mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback));
return Status::ok();
}
Status IncidentService::unregisterSection(const int id) {
ALOGI("Unregister section %d", id);
uid_t callingUid = IPCThreadState::self()->getCallingUid();
for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
if ((*it)->id == id) {
if ((*it)->uid != callingUid) {
ALOGW("Error unregistering section %d: calling uid does not match", id);
return Status::fromExceptionCode(Status::EX_SECURITY);
}
mRegisteredSections.erase(it);
return Status::ok();
}
}
ALOGW("Section %d not found", id);
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
}
Status IncidentService::systemRunning() {
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,

View File

@@ -40,12 +40,16 @@ using namespace android::base;
using namespace android::binder;
using namespace android::os;
class BringYourOwnSection;
// ================================================================================
class ReportHandler : public MessageHandler {
public:
ReportHandler(const sp<WorkDirectory>& workDirectory,
const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
const sp<Throttler>& throttler);
const sp<Broadcaster>& broadcaster,
const sp<Looper>& handlerLooper,
const sp<Throttler>& throttler,
const vector<BringYourOwnSection*>& registeredSections);
virtual ~ReportHandler();
virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@ private:
nsecs_t mBacklogDelay;
sp<Throttler> mThrottler;
const vector<BringYourOwnSection*>& mRegisteredSections;
sp<ReportBatch> mBatch;
/**
@@ -126,6 +132,11 @@ public:
virtual Status reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener);
virtual Status registerSection(int id, const String16& name,
const sp<IIncidentDumpCallback>& callback);
virtual Status unregisterSection(int id);
virtual Status systemRunning();
virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@ private:
sp<Broadcaster> mBroadcaster;
sp<ReportHandler> mHandler;
sp<Throttler> mThrottler;
vector<BringYourOwnSection*> mRegisteredSections;
/**
* Commands print out help.

View File

@@ -364,7 +364,6 @@ void ReportWriter::startSection(int sectionId) {
mSectionBufferSuccess = false;
mHadError = false;
mSectionErrors.clear();
}
void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +469,13 @@ status_t ReportWriter::writeSection(const FdBuffer& buffer) {
// ================================================================================
Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
const sp<ReportBatch>& batch,
const vector<BringYourOwnSection*>& registeredSections)
:mWorkDirectory(workDirectory),
mWriter(batch),
mBatch(batch) {
mBatch(batch),
mRegisteredSections(registeredSections) {
}
Reporter::~Reporter() {
@@ -580,50 +582,15 @@ void Reporter::runReport(size_t* reportByteSize) {
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
for (const Section** section = SECTION_LIST; *section; section++) {
const int sectionId = (*section)->id;
// If nobody wants this section, skip it.
if (!mBatch->containsSection(sectionId)) {
continue;
}
ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
// Notify listener of starting
mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
listener->onReportSectionStatus(
sectionId, IIncidentReportStatusListener::STATUS_STARTING);
});
// Go get the data and write it into the file descriptors.
mWriter.startSection(sectionId);
err = (*section)->Execute(&mWriter);
mWriter.endSection(sectionMetadata);
// Sections returning errors are fatal. Most errors should not be fatal.
if (err != NO_ERROR) {
mWriter.error((*section), err, "Section failed. Stopping report.");
if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
goto DONE;
}
}
// The returned max data size is used for throttling too many incident reports.
(*reportByteSize) += sectionMetadata->report_size_bytes();
// For any requests that failed during this section, remove them now. We do this
// before calling back about section finished, so listeners do not erroniously get the
// impression that the section succeeded. But we do it here instead of inside
// writeSection so that the callback is done from a known context and not from the
// bowels of a section, where changing the batch could cause odd errors.
cancel_and_remove_failed_requests();
// Notify listener of finishing
mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
listener->onReportSectionStatus(
sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
});
ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
for (const Section* section : mRegisteredSections) {
if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
goto DONE;
}
}
DONE:
@@ -681,6 +648,55 @@ DONE:
ALOGI("Done taking incident report err=%s", strerror(-err));
}
status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
size_t* reportByteSize) {
const int sectionId = section->id;
// If nobody wants this section, skip it.
if (!mBatch->containsSection(sectionId)) {
return NO_ERROR;
}
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);
status_t 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.");
return err;
}
// 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());
return NO_ERROR;
}
void Reporter::cancel_and_remove_failed_requests() {
// Handle a failure in the persisted file
if (mPersistedFile != nullptr) {

View File

@@ -21,6 +21,7 @@
#include "frameworks/base/core/proto/android/os/metadata.pb.h"
#include <android/content/ComponentName.h>
#include <android/os/IIncidentReportStatusListener.h>
#include <android/os/IIncidentDumpCallback.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
@@ -39,6 +40,7 @@ using namespace std;
using namespace android::content;
using namespace android::os;
class BringYourOwnSection;
class Section;
// ================================================================================
@@ -122,7 +124,7 @@ public:
void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
/**
* Call func(request) for each file descriptor that has
* Call func(request) for each file descriptor.
*/
void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
@@ -251,7 +253,9 @@ private:
// ================================================================================
class Reporter : public virtual RefBase {
public:
Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
Reporter(const sp<WorkDirectory>& workDirectory,
const sp<ReportBatch>& batch,
const vector<BringYourOwnSection*>& registeredSections);
virtual ~Reporter();
@@ -263,6 +267,10 @@ private:
ReportWriter mWriter;
sp<ReportBatch> mBatch;
sp<ReportFile> mPersistedFile;
const vector<BringYourOwnSection*>& mRegisteredSections;
status_t execute_section(const Section* section, IncidentMetadata* metadata,
size_t* reportByteSize);
void cancel_and_remove_failed_requests();
};

View File

@@ -267,7 +267,7 @@ static void* worker_thread_func(void* cookie) {
signal(SIGPIPE, sigpipe_handler);
WorkerThreadData* data = (WorkerThreadData*)cookie;
status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
status_t err = data->section->BlockingCall(data->pipe.writeFd());
{
unique_lock<mutex> lock(data->lock);
@@ -458,7 +458,7 @@ DumpsysSection::DumpsysSection(int id, const char* service, ...)
DumpsysSection::~DumpsysSection() {}
status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
// checkService won't wait for the service to show up like getService will.
sp<IBinder> service = defaultServiceManager()->checkService(mService);
@@ -467,7 +467,7 @@ status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
return NAME_NOT_FOUND;
}
service->dump(pipeWriteFd, mArgs);
service->dump(pipeWriteFd.get(), mArgs);
return NO_ERROR;
}
@@ -526,7 +526,7 @@ static inline int32_t get4LE(uint8_t const* src) {
return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
}
status_t LogSection::BlockingCall(int pipeWriteFd) const {
status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
// Open log buffer and getting logs since last retrieved time if any.
unique_ptr<logger_list, void (*)(logger_list*)> loggers(
gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
@@ -643,7 +643,7 @@ status_t LogSection::BlockingCall(int pipeWriteFd) const {
}
}
gLastLogsRetrieved[mLogID] = lastTimestamp;
if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
return EPIPE;
}
@@ -660,7 +660,7 @@ TombstoneSection::TombstoneSection(int id, const char* type, const int64_t timeo
TombstoneSection::~TombstoneSection() {}
status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
if (proc.get() == nullptr) {
ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -768,7 +768,7 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
dumpPipe.readFd().reset();
}
if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
if (err != NO_ERROR) {
return EPIPE;
@@ -778,6 +778,22 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
return err;
}
// ================================================================================
BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
const sp<IIncidentDumpCallback>& callback)
: WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
name = "registered ";
name += customName;
}
BringYourOwnSection::~BringYourOwnSection() {}
status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
mCallback->onDumpSection(pfd);
return NO_ERROR;
}
} // namespace incidentd
} // namespace os
} // namespace android

View File

@@ -23,6 +23,8 @@
#include <stdarg.h>
#include <map>
#include <android/os/IIncidentDumpCallback.h>
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -89,7 +91,7 @@ public:
virtual status_t Execute(ReportWriter* writer) const;
virtual status_t BlockingCall(int pipeWriteFd) const = 0;
virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
};
/**
@@ -117,7 +119,7 @@ public:
DumpsysSection(int id, const char* service, ...);
virtual ~DumpsysSection();
virtual status_t BlockingCall(int pipeWriteFd) const;
virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -132,7 +134,7 @@ public:
SystemPropertyDumpsysSection(int id, const char* service, ...);
virtual ~SystemPropertyDumpsysSection();
virtual status_t BlockingCall(int pipeWriteFd) const;
virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -153,7 +155,7 @@ public:
LogSection(int id, const char* logID, ...);
virtual ~LogSection();
virtual status_t BlockingCall(int pipeWriteFd) const;
virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
log_id_t mLogID;
@@ -169,12 +171,29 @@ public:
TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
virtual ~TombstoneSection();
virtual status_t BlockingCall(int pipeWriteFd) const;
virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
std::string mType;
};
/**
* Section that gets data from a registered dump callback.
*/
class BringYourOwnSection : public WorkerThreadSection {
public:
const uid_t uid;
BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
const sp<IIncidentDumpCallback>& callback);
virtual ~BringYourOwnSection();
virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
const sp<IIncidentDumpCallback>& mCallback;
};
/**
* These sections will not be generated when doing an 'all' report, either

View File

@@ -0,0 +1,31 @@
/**
* 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.
*/
package android.os;
import android.os.ParcelFileDescriptor;
/**
* Callback from IIncidentManager to dump an extended section.
*
* @hide
*/
oneway interface IIncidentDumpCallback {
/**
* Dumps section data to the given ParcelFileDescriptor.
*/
void onDumpSection(in ParcelFileDescriptor fd);
}

View File

@@ -17,6 +17,7 @@
package android.os;
import android.os.IIncidentReportStatusListener;
import android.os.IIncidentDumpCallback;
import android.os.IncidentManager;
import android.os.IncidentReportArgs;
@@ -51,6 +52,19 @@ interface IIncidentManager {
oneway void reportIncidentToDumpstate(FileDescriptor stream,
@nullable IIncidentReportStatusListener listener);
/**
* Register a section callback with the given id and name. The callback function
* will be invoked when an incident report with all sections or sections matching
* the given id is being taken.
*/
oneway void registerSection(int id, String name, IIncidentDumpCallback callback);
/**
* Unregister a section callback associated with the given id. The section must be
* previously registered with registerSection(int, String, IIncidentDumpCallback).
*/
oneway void unregisterSection(int id);
/**
* Tell the incident daemon that the android system server is up and running.
*/

View File

@@ -31,6 +31,7 @@ import android.util.Slog;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -420,6 +421,39 @@ public class IncidentManager {
}
}
/**
* Callback for dumping an extended (usually vendor-supplied) incident report section
*
* @see #registerSection
* @see #unregisterSection
*
* @hide
*/
public static class DumpCallback {
private Executor mExecutor;
IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
@Override
public void onDumpSection(ParcelFileDescriptor pfd) {
if (mExecutor != null) {
mExecutor.execute(() -> {
DumpCallback.this.onDumpSection(
new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
});
} else {
DumpCallback.this.onDumpSection(
new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
}
}
};
/**
* Called when incidentd requests to dump this section.
*/
public void onDumpSection(OutputStream out) {
}
}
/**
* @hide
*/
@@ -527,6 +561,61 @@ public class IncidentManager {
}
}
/**
* Register a callback to dump an extended incident report section with the given id and name.
* The callback function will be invoked when an incident report with all sections or sections
* matching the given id is being taken.
*
* @hide
*/
public void registerSection(int id, String name, @NonNull DumpCallback callback) {
registerSection(id, name, mContext.getMainExecutor(), callback);
}
/**
* Register a callback to dump an extended incident report section with the given id and name,
* running on the supplied executor.
*
* @hide
*/
public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor,
@NonNull DumpCallback callback) {
try {
if (callback.mExecutor != null) {
throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+ " registerSection");
}
callback.mExecutor = executor;
final IIncidentManager service = getIIncidentManagerLocked();
if (service == null) {
Slog.e(TAG, "registerSection can't find incident binder service");
return;
}
service.registerSection(id, name, callback.mBinder);
} catch (RemoteException ex) {
Slog.e(TAG, "registerSection failed", ex);
}
}
/**
* Unregister an extended section dump function. The section must be previously registered with
* {@link #registerSection(int, String, DumpCallback)}
*
* @hide
*/
public void unregisterSection(int id) {
try {
final IIncidentManager service = getIIncidentManagerLocked();
if (service == null) {
Slog.e(TAG, "unregisterSection can't find incident binder service");
return;
}
service.unregisterSection(id);
} catch (RemoteException ex) {
Slog.e(TAG, "unregisterSection failed", ex);
}
}
/**
* Get the incident reports that are available for upload for the supplied
* broadcast recevier.