summaryrefslogtreecommitdiff
path: root/cmds/incidentd/src
diff options
context:
space:
mode:
Diffstat (limited to 'cmds/incidentd/src')
-rw-r--r--cmds/incidentd/src/FdBuffer.cpp22
-rw-r--r--cmds/incidentd/src/FdBuffer.h2
-rw-r--r--cmds/incidentd/src/IncidentService.cpp58
-rw-r--r--cmds/incidentd/src/IncidentService.h16
-rw-r--r--cmds/incidentd/src/PrivacyFilter.cpp20
-rw-r--r--cmds/incidentd/src/Reporter.cpp164
-rw-r--r--cmds/incidentd/src/Reporter.h19
-rw-r--r--cmds/incidentd/src/Section.cpp336
-rw-r--r--cmds/incidentd/src/Section.h52
-rw-r--r--cmds/incidentd/src/WorkDirectory.cpp36
-rw-r--r--cmds/incidentd/src/incidentd_util.cpp138
-rw-r--r--cmds/incidentd/src/incidentd_util.h53
-rw-r--r--cmds/incidentd/src/report_file.proto5
13 files changed, 698 insertions, 223 deletions
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index d295b84baf67..78c322edcccc 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -17,6 +17,7 @@
#include "Log.h"
#include "FdBuffer.h"
+#include "incidentd_util.h"
#include <log/log.h>
#include <utils/SystemClock.h>
@@ -31,17 +32,24 @@ namespace os {
namespace incidentd {
const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB
-const ssize_t MAX_BUFFER_COUNT = 6144; // 96 MB max
+const ssize_t MAX_BUFFER_SIZE = 96 * 1024 * 1024; // 96 MB
-FdBuffer::FdBuffer()
- :mBuffer(new EncodedBuffer(BUFFER_SIZE)),
+FdBuffer::FdBuffer(): FdBuffer(get_buffer_from_pool(), /* isBufferPooled= */ true) {
+}
+
+FdBuffer::FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled)
+ :mBuffer(buffer),
mStartTime(-1),
mFinishTime(-1),
mTimedOut(false),
- mTruncated(false) {
+ mTruncated(false),
+ mIsBufferPooled(isBufferPooled) {
}
FdBuffer::~FdBuffer() {
+ if (mIsBufferPooled) {
+ return_buffer_to_pool(mBuffer);
+ }
}
status_t FdBuffer::read(int fd, int64_t timeout) {
@@ -51,7 +59,7 @@ 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_SIZE) {
mTruncated = true;
VLOG("Truncating data");
break;
@@ -106,7 +114,7 @@ status_t FdBuffer::readFully(int fd) {
mStartTime = uptimeMillis();
while (true) {
- if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
+ if (mBuffer->size() >= MAX_BUFFER_SIZE) {
// Don't let it get too big.
mTruncated = true;
VLOG("Truncating data");
@@ -156,7 +164,7 @@ 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_SIZE) {
VLOG("Truncating data");
mTruncated = true;
break;
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index a3493604f425..9b2794d165fb 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -35,6 +35,7 @@ using namespace android::util;
class FdBuffer {
public:
FdBuffer();
+ FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled = false);
~FdBuffer();
/**
@@ -114,6 +115,7 @@ private:
int64_t mFinishTime;
bool mTimedOut;
bool mTruncated;
+ bool mIsBufferPooled;
};
} // namespace incidentd
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2357cd..dc1612575f38 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -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()) {
}
@@ -149,6 +152,7 @@ void ReportHandler::handleMessage(const Message& message) {
}
void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
+ unique_lock<mutex> lock(mLock);
mBatch->addPersistedReport(args);
mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
@@ -156,6 +160,7 @@ void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener, int streamFd) {
+ unique_lock<mutex> lock(mLock);
mBatch->addStreamingReport(args, listener, streamFd);
mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
@@ -185,7 +190,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 +242,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 +332,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 +349,46 @@ Status IncidentService::reportIncidentToDumpstate(unique_fd stream,
return Status::ok();
}
+Status IncidentService::registerSection(const int id, const String16& name16,
+ const sp<IIncidentDumpCallback>& callback) {
+ const String8 name = String8(name16);
+ const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ ALOGI("Uid %d registers section %d '%s'", callingUid, id, name.c_str());
+ if (callback == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ }
+ 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.c_str(), callingUid, callback);
+ return Status::ok();
+ }
+ }
+ mRegisteredSections.push_back(new BringYourOwnSection(id, name.c_str(), callingUid, callback));
+ return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+ uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ ALOGI("Uid %d unregisters section %d", callingUid, id);
+
+ 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,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f233e11b..49fc566d71af 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -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.
diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp
index d00ecdde5c63..0d427d1021a6 100644
--- a/cmds/incidentd/src/PrivacyFilter.cpp
+++ b/cmds/incidentd/src/PrivacyFilter.cpp
@@ -19,9 +19,6 @@
#include "incidentd_util.h"
#include "PrivacyFilter.h"
#include "proto_util.h"
-
-#include "incidentd_util.h"
-#include "proto_util.h"
#include "Section.h"
#include <android-base/file.h>
@@ -129,6 +126,8 @@ public:
FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data,
uint8_t bufferLevel);
+ ~FieldStripper();
+
/**
* Take the data that we have, and filter it down so that no fields
* are more sensitive than the given privacy policy.
@@ -167,6 +166,7 @@ private:
*/
uint8_t mCurrentLevel;
+ sp<EncodedBuffer> mEncodedBuffer;
};
FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data,
@@ -174,19 +174,25 @@ FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>&
:mRestrictions(restrictions),
mData(data),
mSize(data->size()),
- mCurrentLevel(bufferLevel) {
+ mCurrentLevel(bufferLevel),
+ mEncodedBuffer(get_buffer_from_pool()) {
if (mSize < 0) {
ALOGW("FieldStripper constructed with a ProtoReader that doesn't support size."
" Data will be missing.");
}
}
+FieldStripper::~FieldStripper() {
+ return_buffer_to_pool(mEncodedBuffer);
+}
+
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;
+ mEncodedBuffer->clear();
+ ProtoOutputStream proto(mEncodedBuffer);
// Optimization when no strip happens.
if (mRestrictions == NULL || spec.RequireAll()) {
@@ -267,7 +273,7 @@ status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel,
// 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 {
+ [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool {
return a->getPrivacyPolicy() < b->getPrivacyPolicy();
});
@@ -370,7 +376,7 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel,
write_field_or_skip(NULL, reader, fieldTag, true);
}
}
-
+ clear_buffer_pool();
err = reader->getError();
if (err != NO_ERROR) {
ALOGW("filter_and_write_report reader had an error: %s", strerror(-err));
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe6c9b1..86a78f095f52 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -35,10 +35,12 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <time.h>
+#include <wait.h>
namespace android {
namespace os {
@@ -51,6 +53,8 @@ using namespace android::util;
* frameworks/base/core/proto/android/os/incident.proto
*/
const int FIELD_ID_METADATA = 2;
+// Args for exec gzip
+static const char* GZIP[] = {"/system/bin/gzip", NULL};
IncidentMetadata_Destination privacy_policy_to_dest(uint8_t privacyPolicy) {
switch (privacyPolicy) {
@@ -142,7 +146,8 @@ ReportRequest::ReportRequest(const IncidentReportArgs& a,
mListener(listener),
mFd(fd),
mIsStreaming(fd >= 0),
- mStatus(NO_ERROR) {
+ mStatus(OK),
+ mZipPid(-1) {
}
ReportRequest::~ReportRequest() {
@@ -153,7 +158,14 @@ ReportRequest::~ReportRequest() {
}
bool ReportRequest::ok() {
- return mFd >= 0 && mStatus == NO_ERROR;
+ if (mStatus != OK) {
+ return false;
+ }
+ if (!args.gzip()) {
+ return mFd >= 0;
+ }
+ // Send a blank signal to check if mZipPid is alive
+ return mZipPid > 0 && kill(mZipPid, 0) == 0;
}
bool ReportRequest::containsSection(int sectionId) const {
@@ -161,10 +173,45 @@ bool ReportRequest::containsSection(int sectionId) const {
}
void ReportRequest::closeFd() {
- if (mIsStreaming && mFd >= 0) {
+ if (!mIsStreaming) {
+ return;
+ }
+ if (mFd >= 0) {
close(mFd);
mFd = -1;
}
+ if (mZipPid > 0) {
+ mZipPipe.close();
+ // Gzip may take some time.
+ status_t err = wait_child(mZipPid, /* timeout_ms= */ 10 * 1000);
+ if (err != 0) {
+ ALOGW("[ReportRequest] abnormal child process: %s", strerror(-err));
+ }
+ }
+}
+
+int ReportRequest::getFd() {
+ return mZipPid > 0 ? mZipPipe.writeFd().get() : mFd;
+}
+
+status_t ReportRequest::initGzipIfNecessary() {
+ if (!mIsStreaming || !args.gzip()) {
+ return OK;
+ }
+ if (!mZipPipe.init()) {
+ ALOGE("[ReportRequest] Failed to setup pipe for gzip");
+ mStatus = -errno;
+ return mStatus;
+ }
+ int status = 0;
+ pid_t pid = fork_execute_cmd((char* const*)GZIP, mZipPipe.readFd().release(), mFd, &status);
+ if (pid < 0 || status != 0) {
+ mStatus = status;
+ return mStatus;
+ }
+ mZipPid = pid;
+ mFd = -1;
+ return OK;
}
// ================================================================================
@@ -364,7 +411,6 @@ void ReportWriter::startSection(int sectionId) {
mSectionBufferSuccess = false;
mHadError = false;
mSectionErrors.clear();
-
}
void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +516,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() {
@@ -560,6 +609,13 @@ void Reporter::runReport(size_t* reportByteSize) {
reportId = (spec.tv_sec) * 1000 + spec.tv_nsec;
}
+ mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) {
+ status_t err = request->initGzipIfNecessary();
+ if (err != 0) {
+ ALOGW("Error forking gzip: %s", strerror(err));
+ }
+ });
+
// 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).
@@ -580,50 +636,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;
+ if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
+ goto DONE;
}
+ }
- 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.");
+ for (const Section* section : mRegisteredSections) {
+ 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());
}
DONE:
@@ -677,10 +698,59 @@ DONE:
listener->onReportFailed();
});
}
-
+ clear_buffer_pool();
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) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961ab8b43..bd47a23d369e 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -15,12 +15,14 @@
*/
#pragma once
+#include "incidentd_util.h"
#include "FdBuffer.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/IIncidentDumpCallback.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
@@ -39,6 +41,7 @@ using namespace std;
using namespace android::content;
using namespace android::os;
+class BringYourOwnSection;
class Section;
// ================================================================================
@@ -61,10 +64,12 @@ public:
sp<IIncidentReportStatusListener> getListener() { return mListener; }
- int getFd() { return mFd; }
+ int getFd();
int setPersistedFd(int fd);
+ status_t initGzipIfNecessary();
+
void closeFd();
private:
@@ -72,6 +77,8 @@ private:
int mFd;
bool mIsStreaming;
status_t mStatus;
+ pid_t mZipPid;
+ Fpipe mZipPipe;
};
// ================================================================================
@@ -122,7 +129,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 +258,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 +272,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();
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 6cbfd47bb660..e56ed39ea32e 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -20,9 +20,9 @@
#include <dirent.h>
#include <errno.h>
-
#include <mutex>
#include <set>
+#include <thread>
#include <android-base/file.h>
#include <android-base/properties.h>
@@ -35,12 +35,14 @@
#include <log/log_event_list.h>
#include <log/logprint.h>
#include <private/android_logger.h>
+#include <sys/mman.h>
#include "FdBuffer.h"
#include "Privacy.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"
+#include "frameworks/base/core/proto/android/util/textdump.proto.h"
#include "incidentd_util.h"
namespace android {
@@ -104,7 +106,6 @@ status_t FileSection::Execute(ReportWriter* writer) const {
return NO_ERROR;
}
- FdBuffer buffer;
Fpipe p2cPipe;
Fpipe c2pPipe;
// initiate pipes to pass data to/from incident_helper
@@ -120,6 +121,7 @@ status_t FileSection::Execute(ReportWriter* writer) const {
}
// parent process
+ FdBuffer buffer;
status_t readStatus = buffer.readProcessedDataInStream(fd.get(), std::move(p2cPipe.writeFd()),
std::move(c2pPipe.readFd()),
this->timeoutMs, mIsSysfs);
@@ -134,7 +136,7 @@ status_t FileSection::Execute(ReportWriter* writer) const {
status_t ihStatus = wait_child(pid);
if (ihStatus != NO_ERROR) {
ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-ihStatus));
- return ihStatus;
+ return OK; // Not a fatal error.
}
return writer->writeSection(buffer);
@@ -233,7 +235,7 @@ struct WorkerThreadData : public virtual RefBase {
Fpipe pipe;
// Lock protects these fields
- mutex lock;
+ std::mutex lock;
bool workerDone;
status_t workerError;
@@ -260,78 +262,42 @@ void sigpipe_handler(int signum) {
}
}
-static void* worker_thread_func(void* cookie) {
- // Don't crash the service if we write to a closed pipe (which can happen if
- // dumping times out).
- signal(SIGPIPE, sigpipe_handler);
-
- WorkerThreadData* data = (WorkerThreadData*)cookie;
- status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
-
- {
- unique_lock<mutex> lock(data->lock);
- data->workerDone = true;
- data->workerError = err;
- }
-
- data->pipe.writeFd().reset();
- data->decStrong(data->section);
- // data might be gone now. don't use it after this point in this thread.
- return NULL;
-}
-
status_t WorkerThreadSection::Execute(ReportWriter* writer) const {
- status_t err = NO_ERROR;
- pthread_t thread;
- pthread_attr_t attr;
- bool workerDone = false;
- FdBuffer buffer;
-
- // Data shared between this thread and the worker thread.
+ // Create shared data and pipe. Don't put data on the stack since this thread may exit early.
sp<WorkerThreadData> data = new WorkerThreadData(this);
-
- // Create the pipe
if (!data->pipe.init()) {
return -errno;
}
-
- // Create the thread
- err = pthread_attr_init(&attr);
- if (err != 0) {
- return -err;
- }
- // TODO: Do we need to tweak thread priority?
- err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- if (err != 0) {
- 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) {
+ std::thread([data, this]() {
+ // Don't crash the service if writing to a closed pipe (may happen if dumping times out)
+ signal(SIGPIPE, sigpipe_handler);
+ status_t err = data->section->BlockingCall(data->pipe.writeFd());
+ {
+ std::scoped_lock<std::mutex> lock(data->lock);
+ data->workerDone = true;
+ data->workerError = err;
+ // unique_fd is not thread safe. If we don't lock it, reset() may pause half way while
+ // the other thread executes to the end, calling ~Fpipe, which is a race condition.
+ data->pipe.writeFd().reset();
+ }
data->decStrong(this);
- return -err;
- }
+ }).detach();
// Loop reading until either the timeout or the worker side is done (i.e. eof).
+ status_t err = NO_ERROR;
+ bool workerDone = false;
+ FdBuffer buffer;
err = buffer.read(data->pipe.readFd().get(), this->timeoutMs);
if (err != NO_ERROR) {
ALOGE("[%s] reader failed with error '%s'", this->name.string(), strerror(-err));
}
- // Done with the read fd. The worker thread closes the write one so
- // we never race and get here first.
- data->pipe.readFd().reset();
-
// If the worker side is finished, then return its error (which may overwrite
// our possible error -- but it's more interesting anyway). If not, then we timed out.
{
- unique_lock<mutex> lock(data->lock);
+ std::scoped_lock<std::mutex> lock(data->lock);
+ data->pipe.close();
if (data->workerError != NO_ERROR) {
err = data->workerError;
ALOGE("[%s] worker failed with error '%s'", this->name.string(), strerror(-err));
@@ -390,7 +356,6 @@ CommandSection::CommandSection(int id, const char* command, ...) : Section(id) {
CommandSection::~CommandSection() { free(mCommand); }
status_t CommandSection::Execute(ReportWriter* writer) const {
- FdBuffer buffer;
Fpipe cmdPipe;
Fpipe ihPipe;
@@ -411,6 +376,7 @@ status_t CommandSection::Execute(ReportWriter* writer) const {
}
cmdPipe.writeFd().reset();
+ FdBuffer buffer;
status_t readStatus = buffer.read(ihPipe.readFd().get(), this->timeoutMs);
writer->setSectionStats(buffer);
if (readStatus != NO_ERROR || buffer.timedOut()) {
@@ -457,7 +423,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);
@@ -466,19 +432,120 @@ status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
return NAME_NOT_FOUND;
}
- service->dump(pipeWriteFd, mArgs);
+ service->dump(pipeWriteFd.get(), mArgs);
return NO_ERROR;
}
// ================================================================================
+TextDumpsysSection::TextDumpsysSection(int id, const char* service, ...)
+ :Section(id), mService(service) {
+ name = "dumpsys ";
+ name += service;
+
+ va_list args;
+ va_start(args, service);
+ while (true) {
+ const char* arg = va_arg(args, const char*);
+ if (arg == NULL) {
+ break;
+ }
+ mArgs.add(String16(arg));
+ name += " ";
+ name += arg;
+ }
+ va_end(args);
+}
+
+TextDumpsysSection::~TextDumpsysSection() {}
+
+status_t TextDumpsysSection::Execute(ReportWriter* writer) const {
+ // checkService won't wait for the service to show up like getService will.
+ sp<IBinder> service = defaultServiceManager()->checkService(mService);
+ if (service == NULL) {
+ ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).string());
+ return NAME_NOT_FOUND;
+ }
+
+ // Create pipe
+ Fpipe dumpPipe;
+ if (!dumpPipe.init()) {
+ ALOGW("[%s] failed to setup pipe", this->name.string());
+ return -errno;
+ }
+
+ // Run dumping thread
+ const uint64_t start = Nanotime();
+ std::thread worker([write_fd = std::move(dumpPipe.writeFd()), service = std::move(service),
+ this]() mutable {
+ // Don't crash the service if writing to a closed pipe (may happen if dumping times out)
+ signal(SIGPIPE, sigpipe_handler);
+ status_t err = service->dump(write_fd.get(), this->mArgs);
+ if (err != OK) {
+ ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err));
+ }
+ write_fd.reset();
+ });
+
+ // Collect dump content
+ FdBuffer buffer;
+ ProtoOutputStream proto;
+ proto.write(TextDumpProto::COMMAND, std::string(name.string()));
+ proto.write(TextDumpProto::DUMP_DURATION_NS, int64_t(Nanotime() - start));
+ buffer.write(proto.data());
+
+ sp<EncodedBuffer> internalBuffer = buffer.data();
+ internalBuffer->writeHeader((uint32_t)TextDumpProto::CONTENT, WIRE_TYPE_LENGTH_DELIMITED);
+ size_t editPos = internalBuffer->wp()->pos();
+ internalBuffer->wp()->move(8); // reserve 8 bytes for the varint of the data size
+ size_t dataBeginPos = internalBuffer->wp()->pos();
+
+ status_t readStatus = buffer.read(dumpPipe.readFd(), this->timeoutMs);
+ dumpPipe.readFd().reset();
+ writer->setSectionStats(buffer);
+ if (readStatus != OK || buffer.timedOut()) {
+ ALOGW("[%s] failed to read from dumpsys: %s, timedout: %s", this->name.string(),
+ strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+ worker.detach();
+ return readStatus;
+ }
+ worker.join(); // wait for worker to finish
+
+ // Revisit the actual size from dumpsys and edit the internal buffer accordingly.
+ size_t dumpSize = buffer.size() - dataBeginPos;
+ internalBuffer->wp()->rewind()->move(editPos);
+ internalBuffer->writeRawVarint32(dumpSize);
+ internalBuffer->copy(dataBeginPos, dumpSize);
+
+ return writer->writeSection(buffer);
+}
+
+// ================================================================================
// initialization only once in Section.cpp.
map<log_id_t, log_time> LogSection::gLastLogsRetrieved;
-LogSection::LogSection(int id, log_id_t logID) : WorkerThreadSection(id), mLogID(logID) {
- name = "logcat ";
- name += android_log_id_to_name(logID);
- switch (logID) {
+LogSection::LogSection(int id, const char* logID, ...) : WorkerThreadSection(id), mLogMode(logModeBase) {
+ name = "logcat -b ";
+ name += logID;
+
+ va_list args;
+ va_start(args, logID);
+ mLogID = android_name_to_log_id(logID);
+ while(true) {
+ const char* arg = va_arg(args, const char*);
+ if (arg == NULL) {
+ break;
+ }
+ if (!strcmp(arg, "-L")) {
+ // Read from last logcat buffer
+ mLogMode = mLogMode | ANDROID_LOG_PSTORE;
+ }
+ name += " ";
+ name += arg;
+ }
+ va_end(args);
+
+ switch (mLogID) {
case LOG_ID_EVENTS:
case LOG_ID_STATS:
case LOG_ID_SECURITY:
@@ -507,42 +574,51 @@ 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 {
+ // heap profile shows that liblog malloc & free significant amount of memory in this process.
+ // Hence forking a new process to prevent memory fragmentation.
+ pid_t pid = fork();
+ if (pid < 0) {
+ ALOGW("[%s] failed to fork", this->name.string());
+ return errno;
+ }
+ if (pid > 0) {
+ return wait_child(pid, this->timeoutMs);
+ }
// 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()
- ? android_logger_list_alloc(ANDROID_LOG_NONBLOCK, 0, 0)
- : android_logger_list_alloc_time(ANDROID_LOG_NONBLOCK,
- gLastLogsRetrieved[mLogID], 0),
+ ? android_logger_list_alloc(mLogMode, 0, 0)
+ : android_logger_list_alloc_time(mLogMode, gLastLogsRetrieved[mLogID], 0),
android_logger_list_free);
if (android_logger_open(loggers.get(), mLogID) == NULL) {
ALOGE("[%s] Can't get logger.", this->name.string());
- return -1;
+ _exit(EXIT_FAILURE);
}
log_msg msg;
log_time lastTimestamp(0);
ProtoOutputStream proto;
+ status_t err = OK;
while (true) { // keeps reading until logd buffer is fully read.
- status_t err = android_logger_list_read(loggers.get(), &msg);
- // err = 0 - no content, unexpected connection drop or EOF.
- // err = +ive number - size of retrieved data from logger
- // err = -ive number, OS supplied error _except_ for -EAGAIN
- // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data.
- if (err <= 0) {
- if (err != -EAGAIN) {
+ status_t status = android_logger_list_read(loggers.get(), &msg);
+ // status = 0 - no content, unexpected connection drop or EOF.
+ // status = +ive number - size of retrieved data from logger
+ // status = -ive number, OS supplied error _except_ for -EAGAIN
+ // status = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end.
+ if (status <= 0) {
+ if (status != -EAGAIN) {
ALOGW("[%s] fails to read a log_msg.\n", this->name.string());
+ err = -status;
}
- // dump previous logs and don't consider this error a failure.
break;
}
if (mBinary) {
// remove the first uint32 which is tag's index in event log tags
android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t),
msg.len() - sizeof(uint32_t));
- ;
android_log_list_element elem;
lastTimestamp.tv_sec = msg.entry.sec;
@@ -602,9 +678,10 @@ status_t LogSection::BlockingCall(int pipeWriteFd) const {
}
} else {
AndroidLogEntry entry;
- err = android_log_processLogBuffer(&msg.entry, &entry);
- if (err != NO_ERROR) {
+ status = android_log_processLogBuffer(&msg.entry, &entry);
+ if (status != OK) {
ALOGW("[%s] fails to process to an entry.\n", this->name.string());
+ err = status;
break;
}
lastTimestamp.tv_sec = entry.tv_sec;
@@ -623,17 +700,24 @@ status_t LogSection::BlockingCall(int pipeWriteFd) const {
trimTail(entry.message, entry.messageLen));
proto.end(token);
}
+ if (!proto.flush(pipeWriteFd.get())) {
+ if (errno == EPIPE) {
+ ALOGW("[%s] wrote to a broken pipe\n", this->name.string());
+ }
+ err = errno;
+ break;
+ }
+ proto.clear();
}
gLastLogsRetrieved[mLogID] = lastTimestamp;
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
- ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
- return EPIPE;
- }
- return NO_ERROR;
+ _exit(err);
}
// ================================================================================
+const int LINK_NAME_LEN = 64;
+const int EXE_NAME_LEN = 1024;
+
TombstoneSection::TombstoneSection(int id, const char* type, const int64_t timeoutMs)
: WorkerThreadSection(id, timeoutMs), mType(type) {
name = "tombstone ";
@@ -642,7 +726,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));
@@ -651,25 +735,37 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
const std::set<int> hal_pids = get_interesting_hal_pids();
- ProtoOutputStream proto;
+ auto pooledBuffer = get_buffer_from_pool();
+ ProtoOutputStream proto(pooledBuffer);
+ // dumpBufferSize should be a multiple of page size (4 KB) to reduce memory fragmentation
+ size_t dumpBufferSize = 64 * 1024; // 64 KB is enough for most tombstone dump
+ char* dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
struct dirent* d;
+ char link_name[LINK_NAME_LEN];
+ char exe_name[EXE_NAME_LEN];
status_t err = NO_ERROR;
while ((d = readdir(proc.get()))) {
int pid = atoi(d->d_name);
if (pid <= 0) {
continue;
}
-
- const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid);
- std::string exe;
- if (!android::base::Readlink(link_name, &exe)) {
- ALOGE("Section %s: Can't read '%s': %s\n", name.string(),
- link_name.c_str(), strerror(errno));
+ snprintf(link_name, LINK_NAME_LEN, "/proc/%d/exe", pid);
+ struct stat fileStat;
+ if (stat(link_name, &fileStat) != OK) {
continue;
}
+ ssize_t exe_name_len = readlink(link_name, exe_name, EXE_NAME_LEN);
+ if (exe_name_len < 0 || exe_name_len >= EXE_NAME_LEN) {
+ ALOGE("[%s] Can't read '%s': %s", name.string(), link_name, strerror(errno));
+ continue;
+ }
+ // readlink(2) does not put a null terminator at the end
+ exe_name[exe_name_len] = '\0';
bool is_java_process;
- if (exe == "/system/bin/app_process32" || exe == "/system/bin/app_process64") {
+ if (strncmp(exe_name, "/system/bin/app_process32", LINK_NAME_LEN) == 0 ||
+ strncmp(exe_name, "/system/bin/app_process64", LINK_NAME_LEN) == 0) {
if (mType != "java") continue;
// Don't bother dumping backtraces for the zygote.
if (IsZygote(pid)) {
@@ -678,7 +774,7 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
}
is_java_process = true;
- } else if (should_dump_native_traces(exe.c_str())) {
+ } else if (should_dump_native_traces(exe_name)) {
if (mType != "native") continue;
is_java_process = false;
} else if (hal_pids.find(pid) != hal_pids.end()) {
@@ -734,32 +830,58 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
ALOGE("[%s] child had an issue: %s\n", this->name.string(), strerror(-cStatus));
}
- auto dump = std::make_unique<char[]>(buffer.size());
+ // Resize dump buffer
+ if (dumpBufferSize < buffer.size()) {
+ munmap(dumpBuffer, dumpBufferSize);
+ while(dumpBufferSize < buffer.size()) dumpBufferSize = dumpBufferSize << 1;
+ dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ }
sp<ProtoReader> reader = buffer.data()->read();
int i = 0;
while (reader->hasNext()) {
- dump[i] = reader->next();
+ dumpBuffer[i] = reader->next();
i++;
}
uint64_t token = proto.start(android::os::BackTraceProto::TRACES);
proto.write(android::os::BackTraceProto::Stack::PID, pid);
- proto.write(android::os::BackTraceProto::Stack::DUMP, dump.get(), i);
+ proto.write(android::os::BackTraceProto::Stack::DUMP, dumpBuffer, i);
proto.write(android::os::BackTraceProto::Stack::DUMP_DURATION_NS,
static_cast<long long>(Nanotime() - start));
proto.end(token);
dumpPipe.readFd().reset();
- }
-
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
- ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
- if (err != NO_ERROR) {
- return EPIPE;
+ if (!proto.flush(pipeWriteFd.get())) {
+ if (errno == EPIPE) {
+ ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
+ }
+ err = errno;
+ break;
}
+ proto.clear();
}
-
+ munmap(dumpBuffer, dumpBufferSize);
+ return_buffer_to_pool(pooledBuffer);
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));
+ if(mCallback != nullptr) {
+ mCallback->onDumpSection(pfd);
+ }
+ return NO_ERROR;
+}
+
} // namespace incidentd
} // namespace os
} // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index f8649f8d21f1..698cc04c160e 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,7 @@
#include <stdarg.h>
#include <map>
+#include <android/os/IIncidentDumpCallback.h>
#include <log/log_read.h>
#include <utils/String16.h>
#include <utils/String8.h>
@@ -90,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;
};
/**
@@ -111,14 +112,30 @@ private:
};
/**
- * Section that calls dumpsys on a system service.
+ * Section that calls protobuf dumpsys on a system service, usually
+ * "dumpsys [service_name] --proto".
*/
class DumpsysSection : public WorkerThreadSection {
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;
+ Vector<String16> mArgs;
+};
+
+/**
+ * Section that calls text dumpsys on a system service, usually "dumpsys [service_name]".
+ */
+class TextDumpsysSection : public Section {
+public:
+ TextDumpsysSection(int id, const char* service, ...);
+ virtual ~TextDumpsysSection();
+
+ virtual status_t Execute(ReportWriter* writer) const;
private:
String16 mService;
@@ -133,7 +150,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;
@@ -147,15 +164,19 @@ class LogSection : public WorkerThreadSection {
// global last log retrieved timestamp for each log_id_t.
static map<log_id_t, log_time> gLastLogsRetrieved;
+ // log mode: non blocking.
+ const static int logModeBase = ANDROID_LOG_NONBLOCK;
+
public:
- LogSection(int id, log_id_t logID);
+ 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;
bool mBinary;
+ int mLogMode;
};
/**
@@ -166,12 +187,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
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 9963533c08ac..1944d6ecc720 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -16,10 +16,10 @@
#include "Log.h"
-#include "WorkDirectory.h"
-
+#include "incidentd_util.h"
#include "proto_util.h"
#include "PrivacyFilter.h"
+#include "WorkDirectory.h"
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <private/android_filesystem_config.h>
@@ -68,6 +68,9 @@ const ComponentName DROPBOX_SENTINEL("android", "DROPBOX");
/** metadata field id in IncidentProto */
const int FIELD_ID_INCIDENT_METADATA = 2;
+// Args for exec gzip
+static const char* GZIP[] = {"/system/bin/gzip", NULL};
+
/**
* Read a protobuf from disk into the message.
*/
@@ -292,6 +295,7 @@ void ReportFile::addReport(const IncidentReportArgs& args) {
report->set_cls(args.receiverCls());
report->set_privacy_policy(args.getPrivacyPolicy());
report->set_all_sections(args.all());
+ report->set_gzip(args.gzip());
for (int section: args.sections()) {
report->add_section(section);
}
@@ -417,6 +421,24 @@ status_t ReportFile::startFilteringData(int writeFd, const IncidentReportArgs& a
return BAD_VALUE;
}
+ pid_t zipPid = 0;
+ if (args.gzip()) {
+ Fpipe zipPipe;
+ if (!zipPipe.init()) {
+ ALOGE("[ReportFile] Failed to setup pipe for gzip");
+ close(writeFd);
+ return -errno;
+ }
+ int status = 0;
+ zipPid = fork_execute_cmd((char* const*)GZIP, zipPipe.readFd().release(), writeFd, &status);
+ close(writeFd);
+ if (zipPid < 0 || status != 0) {
+ ALOGE("[ReportFile] Failed to fork and exec gzip");
+ return status;
+ }
+ writeFd = zipPipe.writeFd().release();
+ }
+
status_t err;
for (const auto& report : mEnvelope.report()) {
@@ -437,6 +459,13 @@ status_t ReportFile::startFilteringData(int writeFd, const IncidentReportArgs& a
}
close(writeFd);
+ if (zipPid > 0) {
+ status_t err = wait_child(zipPid, /* timeout_ms= */ 10 * 1000);
+ if (err != 0) {
+ ALOGE("[ReportFile] abnormal child process: %s", strerror(-err));
+ }
+ return err;
+ }
return NO_ERROR;
}
@@ -621,7 +650,7 @@ void WorkDirectory::commitAll(const string& pkg) {
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,
@@ -815,6 +844,7 @@ void get_args_from_report(IncidentReportArgs* out, const ReportFileProto_Report&
out->setAll(report.all_sections());
out->setReceiverPkg(report.pkg());
out->setReceiverCls(report.cls());
+ out->setGzip(report.gzip());
const int sectionCount = report.section_size();
for (int i = 0; i < sectionCount; i++) {
diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp
index dfaf89392f90..150ab9991a2d 100644
--- a/cmds/incidentd/src/incidentd_util.cpp
+++ b/cmds/incidentd/src/incidentd_util.cpp
@@ -18,6 +18,8 @@
#include "incidentd_util.h"
+#include <android/util/EncodedBuffer.h>
+#include <fcntl.h>
#include <sys/prctl.h>
#include <wait.h>
@@ -27,8 +29,6 @@ namespace android {
namespace os {
namespace incidentd {
-using namespace android::base;
-
const Privacy* get_privacy_of_section(int id) {
int l = 0;
int r = PRIVACY_POLICY_COUNT - 1;
@@ -47,6 +47,30 @@ const Privacy* get_privacy_of_section(int id) {
return NULL;
}
+std::vector<sp<EncodedBuffer>> gBufferPool;
+std::mutex gBufferPoolLock;
+
+sp<EncodedBuffer> get_buffer_from_pool() {
+ std::scoped_lock<std::mutex> lock(gBufferPoolLock);
+ if (gBufferPool.size() == 0) {
+ return new EncodedBuffer();
+ }
+ sp<EncodedBuffer> buffer = gBufferPool.back();
+ gBufferPool.pop_back();
+ return buffer;
+}
+
+void return_buffer_to_pool(sp<EncodedBuffer> buffer) {
+ buffer->clear();
+ std::scoped_lock<std::mutex> lock(gBufferPoolLock);
+ gBufferPool.push_back(buffer);
+}
+
+void clear_buffer_pool() {
+ std::scoped_lock<std::mutex> lock(gBufferPoolLock);
+ gBufferPool.clear();
+}
+
// ================================================================================
Fpipe::Fpipe() : mRead(), mWrite() {}
@@ -64,28 +88,52 @@ unique_fd& Fpipe::readFd() { return mRead; }
unique_fd& Fpipe::writeFd() { return mWrite; }
-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();
+pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status) {
+ int in = -1;
+ if (input != nullptr) {
+ in = input->readFd().release();
+ // Auto close write end of the input pipe on exec to prevent leaking fd in child process
+ fcntl(input->writeFd().get(), F_SETFD, FD_CLOEXEC);
+ }
+ int out = output->writeFd().release();
+ // Auto close read end of the output pipe on exec
+ fcntl(output->readFd().get(), F_SETFD, FD_CLOEXEC);
+ return fork_execute_cmd(argv, in, out, status);
+}
+
+pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status) {
+ int dummy_status = 0;
+ if (status == nullptr) {
+ status = &dummy_status;
+ }
+ *status = 0;
+ pid_t pid = vfork();
+ if (pid < 0) {
+ *status = -errno;
+ return -1;
+ }
if (pid == 0) {
- if (input != NULL && (TEMP_FAILURE_RETRY(dup2(input->readFd().get(), STDIN_FILENO)) < 0 ||
- !input->close())) {
+ // In child
+ if (in >= 0 && (TEMP_FAILURE_RETRY(dup2(in, STDIN_FILENO)) < 0 || close(in))) {
ALOGW("Failed to dup2 stdin.");
_exit(EXIT_FAILURE);
}
- if (TEMP_FAILURE_RETRY(dup2(output->writeFd().get(), STDOUT_FILENO)) < 0 ||
- !output->close()) {
+ if (TEMP_FAILURE_RETRY(dup2(out, STDOUT_FILENO)) < 0 || close(out)) {
ALOGW("Failed to dup2 stdout.");
_exit(EXIT_FAILURE);
}
- /* make sure the child dies when incidentd dies */
+ // Make sure the child dies when incidentd dies
prctl(PR_SET_PDEATHSIG, SIGKILL);
execvp(argv[0], argv);
_exit(errno); // always exits with failure if any
}
- // close the fds used in child process.
- if (input != NULL) input->readFd().reset();
- output->writeFd().reset();
+ // In parent
+ if ((in >= 0 && close(in) < 0) || close(out) < 0) {
+ ALOGW("Failed to close pd. Killing child process");
+ *status = -errno;
+ kill_child(pid);
+ return -1;
+ }
return pid;
}
@@ -120,9 +168,6 @@ uint64_t Nanotime() {
}
// ================================================================================
-const int WAIT_MAX = 5;
-const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
-
static status_t statusCode(int status) {
if (WIFSIGNALED(status)) {
VLOG("return by signal: %s", strerror(WTERMSIG(status)));
@@ -134,25 +179,64 @@ static status_t statusCode(int status) {
return NO_ERROR;
}
+static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
+ sigset_t child_mask, old_mask;
+ sigemptyset(&child_mask);
+ sigaddset(&child_mask, SIGCHLD);
+
+ if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
+ ALOGW("sigprocmask failed: %s", strerror(errno));
+ return false;
+ }
+
+ timespec ts;
+ ts.tv_sec = timeout_ms / 1000;
+ ts.tv_nsec = (timeout_ms % 1000) * 1000000;
+ int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
+ int saved_errno = errno;
+
+ // Set the signals back the way they were.
+ if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
+ ALOGW("sigprocmask failed: %s", strerror(errno));
+ if (ret == 0) {
+ return false;
+ }
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ if (errno == EAGAIN) {
+ errno = ETIMEDOUT;
+ } else {
+ ALOGW("sigtimedwait failed: %s", strerror(errno));
+ }
+ return false;
+ }
+
+ pid_t child_pid = waitpid(pid, status, WNOHANG);
+ if (child_pid == pid) {
+ return true;
+ }
+ if (child_pid == -1) {
+ ALOGW("waitpid failed: %s", strerror(errno));
+ } else {
+ ALOGW("Waiting for pid %d, got pid %d instead", pid, child_pid);
+ }
+ return false;
+}
+
status_t kill_child(pid_t pid) {
int status;
- VLOG("try to kill child process %d", pid);
kill(pid, SIGKILL);
if (waitpid(pid, &status, 0) == -1) return -1;
return statusCode(status);
}
-status_t wait_child(pid_t pid) {
+status_t wait_child(pid_t pid, int timeout_ms) {
int status;
- bool died = false;
- // wait for child to report status up to 1 seconds
- for (int loop = 0; !died && loop < WAIT_MAX; loop++) {
- if (waitpid(pid, &status, WNOHANG) == pid) died = true;
- // sleep for 0.2 second
- nanosleep(&WAIT_INTERVAL_NS, NULL);
- }
- if (!died) return kill_child(pid);
- return statusCode(status);
+ if (waitpid_with_timeout(pid, timeout_ms, &status)) {
+ return statusCode(status);
+ }
+ return kill_child(pid);
}
} // namespace incidentd
diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h
index cc30768fa704..84998892e33c 100644
--- a/cmds/incidentd/src/incidentd_util.h
+++ b/cmds/incidentd/src/incidentd_util.h
@@ -19,18 +19,21 @@
#define INCIDENTD_UTIL_H
#include <stdarg.h>
-#include <unistd.h>
-
-#include <android-base/unique_fd.h>
#include <utils/Errors.h>
#include "Privacy.h"
namespace android {
+
+namespace util {
+class EncodedBuffer;
+}
+
namespace os {
namespace incidentd {
-using namespace android::base;
+using android::base::unique_fd;
+using android::util::EncodedBuffer;
/**
* Looks up Privacy of a section in the auto-gen PRIVACY_POLICY_LIST;
@@ -38,6 +41,25 @@ using namespace android::base;
const Privacy* get_privacy_of_section(int id);
/**
+ * Get an EncodedBuffer from an internal pool, or create and return a new one if the pool is empty.
+ * The EncodedBuffer should be returned after use.
+ * Thread safe.
+ */
+sp<EncodedBuffer> get_buffer_from_pool();
+
+/**
+ * Return the EncodedBuffer back to the pool for reuse.
+ * Thread safe.
+ */
+void return_buffer_to_pool(sp<EncodedBuffer> buffer);
+
+/**
+ * Clear the buffer pool to free memory, after taking an incident report.
+ * Thread safe.
+ */
+void clear_buffer_pool();
+
+/**
* This class wraps android::base::Pipe.
*/
class Fpipe {
@@ -56,11 +78,24 @@ private:
};
/**
- * Forks and exec a command with two pipes, one connects stdin for input,
- * one connects stdout for output. It returns the pid of the child.
- * Input pipe can be NULL to indicate child process doesn't read stdin.
+ * Forks and exec a command with two pipes and returns the pid of the child, or -1 when it fails.
+ *
+ * input connects stdin for input. output connects stdout for output. input can be nullptr to
+ * indicate that child process doesn't read stdin. This function will close in and out fds upon
+ * success. If status is not NULL, the status information will be stored in the int to which it
+ * points.
+ */
+pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status = nullptr);
+
+/**
+ * Forks and exec a command that reads from in fd and writes to out fd and returns the pid of the
+ * child, or -1 when it fails.
+ *
+ * in can be -1 to indicate that child process doesn't read stdin. This function will close in and
+ * out fds upon success. If status is not NULL, the status information will be stored in the int
+ * to which it points.
*/
-pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output);
+pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status = nullptr);
/**
* Grabs varargs from stack and stores them in heap with NULL-terminated array.
@@ -76,7 +111,7 @@ uint64_t Nanotime();
* Methods to wait or kill child process, return exit status code.
*/
status_t kill_child(pid_t pid);
-status_t wait_child(pid_t pid);
+status_t wait_child(pid_t pid, int timeout_ms = 1000);
status_t start_detached_thread(const function<void ()>& func);
diff --git a/cmds/incidentd/src/report_file.proto b/cmds/incidentd/src/report_file.proto
index 7563da2c2148..85fd2da6c65c 100644
--- a/cmds/incidentd/src/report_file.proto
+++ b/cmds/incidentd/src/report_file.proto
@@ -65,6 +65,11 @@ message ReportFileProto {
* the given client.
*/
optional bool share_approved = 8;
+
+ /**
+ * Whether the report is gzipped.
+ */
+ optional bool gzip = 9;
}
/**