diff options
Diffstat (limited to 'cmds/incidentd/src')
-rw-r--r-- | cmds/incidentd/src/FdBuffer.cpp | 22 | ||||
-rw-r--r-- | cmds/incidentd/src/FdBuffer.h | 2 | ||||
-rw-r--r-- | cmds/incidentd/src/IncidentService.cpp | 58 | ||||
-rw-r--r-- | cmds/incidentd/src/IncidentService.h | 16 | ||||
-rw-r--r-- | cmds/incidentd/src/PrivacyFilter.cpp | 20 | ||||
-rw-r--r-- | cmds/incidentd/src/Reporter.cpp | 164 | ||||
-rw-r--r-- | cmds/incidentd/src/Reporter.h | 19 | ||||
-rw-r--r-- | cmds/incidentd/src/Section.cpp | 336 | ||||
-rw-r--r-- | cmds/incidentd/src/Section.h | 52 | ||||
-rw-r--r-- | cmds/incidentd/src/WorkDirectory.cpp | 36 | ||||
-rw-r--r-- | cmds/incidentd/src/incidentd_util.cpp | 138 | ||||
-rw-r--r-- | cmds/incidentd/src/incidentd_util.h | 53 | ||||
-rw-r--r-- | cmds/incidentd/src/report_file.proto | 5 |
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; } /** |