| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #define DEBUG false |
| #include "Log.h" |
| |
| #include "Reporter.h" |
| |
| #include "incidentd_util.h" |
| #include "Privacy.h" |
| #include "PrivacyFilter.h" |
| #include "proto_util.h" |
| #include "report_directory.h" |
| #include "section_list.h" |
| |
| #include <android-base/file.h> |
| #include <android/os/DropBoxManager.h> |
| #include <android/util/protobuf.h> |
| #include <android/util/ProtoOutputStream.h> |
| #include <private/android_filesystem_config.h> |
| #include <utils/SystemClock.h> |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <string> |
| #include <time.h> |
| |
| namespace android { |
| namespace os { |
| namespace incidentd { |
| |
| using namespace android::util; |
| |
| /** |
| * The field id of the metadata section from |
| * frameworks/base/core/proto/android/os/incident.proto |
| */ |
| const int FIELD_ID_METADATA = 2; |
| |
| IncidentMetadata_Destination privacy_policy_to_dest(uint8_t privacyPolicy) { |
| switch (privacyPolicy) { |
| case PRIVACY_POLICY_AUTOMATIC: |
| return IncidentMetadata_Destination_AUTOMATIC; |
| case PRIVACY_POLICY_EXPLICIT: |
| return IncidentMetadata_Destination_EXPLICIT; |
| case PRIVACY_POLICY_LOCAL: |
| return IncidentMetadata_Destination_LOCAL; |
| default: |
| // Anything else reverts to automatic |
| return IncidentMetadata_Destination_AUTOMATIC; |
| } |
| } |
| |
| |
| static bool contains_section(const IncidentReportArgs& args, int sectionId) { |
| return args.containsSection(sectionId, section_requires_specific_mention(sectionId)); |
| } |
| |
| static bool contains_section(const sp<ReportRequest>& args, int sectionId) { |
| return args->containsSection(sectionId); |
| } |
| |
| // ARGS must have a containsSection(int) method |
| template <typename ARGS> |
| void make_metadata(IncidentMetadata* result, const IncidentMetadata& full, |
| int64_t reportId, int32_t privacyPolicy, ARGS args) { |
| result->set_report_id(reportId); |
| result->set_dest(privacy_policy_to_dest(privacyPolicy)); |
| |
| size_t sectionCount = full.sections_size(); |
| for (int sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { |
| const IncidentMetadata::SectionStats& sectionStats = full.sections(sectionIndex); |
| if (contains_section(args, sectionStats.id())) { |
| *result->add_sections() = sectionStats; |
| } |
| } |
| } |
| |
| // ================================================================================ |
| class StreamingFilterFd : public FilterFd { |
| public: |
| StreamingFilterFd(uint8_t privacyPolicy, int fd, const sp<ReportRequest>& request); |
| |
| virtual void onWriteError(status_t err); |
| |
| private: |
| sp<ReportRequest> mRequest; |
| }; |
| |
| StreamingFilterFd::StreamingFilterFd(uint8_t privacyPolicy, int fd, |
| const sp<ReportRequest>& request) |
| :FilterFd(privacyPolicy, fd), |
| mRequest(request) { |
| } |
| |
| void StreamingFilterFd::onWriteError(status_t err) { |
| mRequest->setStatus(err); |
| } |
| |
| |
| // ================================================================================ |
| class PersistedFilterFd : public FilterFd { |
| public: |
| PersistedFilterFd(uint8_t privacyPolicy, int fd, const sp<ReportFile>& reportFile); |
| |
| virtual void onWriteError(status_t err); |
| |
| private: |
| sp<ReportFile> mReportFile; |
| }; |
| |
| PersistedFilterFd::PersistedFilterFd(uint8_t privacyPolicy, int fd, |
| const sp<ReportFile>& reportFile) |
| :FilterFd(privacyPolicy, fd), |
| mReportFile(reportFile) { |
| } |
| |
| void PersistedFilterFd::onWriteError(status_t err) { |
| mReportFile->setWriteError(err); |
| } |
| |
| |
| // ================================================================================ |
| ReportRequest::ReportRequest(const IncidentReportArgs& a, |
| const sp<IIncidentReportStatusListener>& listener, int fd) |
| :args(a), |
| mListener(listener), |
| mFd(fd), |
| mIsStreaming(fd >= 0), |
| mStatus(NO_ERROR) { |
| } |
| |
| ReportRequest::~ReportRequest() { |
| if (mIsStreaming && mFd >= 0) { |
| // clean up the opened file descriptor |
| close(mFd); |
| } |
| } |
| |
| bool ReportRequest::ok() { |
| return mFd >= 0 && mStatus == NO_ERROR; |
| } |
| |
| bool ReportRequest::containsSection(int sectionId) const { |
| return args.containsSection(sectionId, section_requires_specific_mention(sectionId)); |
| } |
| |
| void ReportRequest::closeFd() { |
| if (mIsStreaming && mFd >= 0) { |
| close(mFd); |
| mFd = -1; |
| } |
| } |
| |
| // ================================================================================ |
| ReportBatch::ReportBatch() {} |
| |
| ReportBatch::~ReportBatch() {} |
| |
| void ReportBatch::addPersistedReport(const IncidentReportArgs& args) { |
| ComponentName component(args.receiverPkg(), args.receiverCls()); |
| map<ComponentName, sp<ReportRequest>>::iterator found = mPersistedRequests.find(component); |
| if (found == mPersistedRequests.end()) { |
| // not found |
| mPersistedRequests[component] = new ReportRequest(args, nullptr, -1); |
| } else { |
| // found |
| sp<ReportRequest> request = found->second; |
| request->args.merge(args); |
| } |
| } |
| |
| void ReportBatch::addStreamingReport(const IncidentReportArgs& args, |
| const sp<IIncidentReportStatusListener>& listener, int streamFd) { |
| mStreamingRequests.push_back(new ReportRequest(args, listener, streamFd)); |
| } |
| |
| bool ReportBatch::empty() const { |
| return mPersistedRequests.size() == 0 && mStreamingRequests.size() == 0; |
| } |
| |
| sp<ReportRequest> ReportBatch::getPersistedRequest(const ComponentName& component) { |
| map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.find(component); |
| if (it != mPersistedRequests.find(component)) { |
| return it->second; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| void ReportBatch::forEachPersistedRequest(const function<void (const sp<ReportRequest>&)>& func) { |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| func(it->second); |
| } |
| } |
| |
| void ReportBatch::forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func) { |
| for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); |
| request != mStreamingRequests.end(); request++) { |
| func(*request); |
| } |
| } |
| |
| void ReportBatch::forEachListener( |
| const function<void (const sp<IIncidentReportStatusListener>&)>& func) { |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| sp<IIncidentReportStatusListener> listener = it->second->getListener(); |
| if (listener != nullptr) { |
| func(listener); |
| } |
| } |
| for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); |
| request != mStreamingRequests.end(); request++) { |
| sp<IIncidentReportStatusListener> listener = (*request)->getListener(); |
| if (listener != nullptr) { |
| func(listener); |
| } |
| } |
| } |
| |
| void ReportBatch::forEachListener(int sectionId, |
| const function<void (const sp<IIncidentReportStatusListener>&)>& func) { |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| if (it->second->containsSection(sectionId)) { |
| sp<IIncidentReportStatusListener> listener = it->second->getListener(); |
| if (listener != nullptr) { |
| func(listener); |
| } |
| } |
| } |
| for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); |
| request != mStreamingRequests.end(); request++) { |
| if ((*request)->containsSection(sectionId)) { |
| sp<IIncidentReportStatusListener> listener = (*request)->getListener(); |
| if (listener != nullptr) { |
| func(listener); |
| } |
| } |
| } |
| } |
| |
| void ReportBatch::getCombinedPersistedArgs(IncidentReportArgs* result) { |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| result->merge(it->second->args); |
| } |
| } |
| |
| bool ReportBatch::containsSection(int sectionId) { |
| // We don't cache this, because in case of error, we remove requests |
| // from the batch, and this is easier than recomputing the set. |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| if (it->second->containsSection(sectionId)) { |
| return true; |
| } |
| } |
| for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); |
| request != mStreamingRequests.end(); request++) { |
| if ((*request)->containsSection(sectionId)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void ReportBatch::clearPersistedRequests() { |
| mPersistedRequests.clear(); |
| } |
| |
| void ReportBatch::transferStreamingRequests(const sp<ReportBatch>& that) { |
| for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); |
| request != mStreamingRequests.end(); request++) { |
| that->mStreamingRequests.push_back(*request); |
| } |
| mStreamingRequests.clear(); |
| } |
| |
| void ReportBatch::transferPersistedRequests(const sp<ReportBatch>& that) { |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| that->mPersistedRequests[it->first] = it->second; |
| } |
| mPersistedRequests.clear(); |
| } |
| |
| void ReportBatch::getFailedRequests(vector<sp<ReportRequest>>* requests) { |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| if (it->second->getStatus() != NO_ERROR) { |
| requests->push_back(it->second); |
| } |
| } |
| for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); |
| request != mStreamingRequests.end(); request++) { |
| if ((*request)->getStatus() != NO_ERROR) { |
| requests->push_back(*request); |
| } |
| } |
| } |
| |
| void ReportBatch::removeRequest(const sp<ReportRequest>& request) { |
| for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); |
| it != mPersistedRequests.end(); it++) { |
| if (it->second == request) { |
| mPersistedRequests.erase(it); |
| return; |
| } |
| } |
| for (vector<sp<ReportRequest>>::iterator it = mStreamingRequests.begin(); |
| it != mStreamingRequests.end(); it++) { |
| if (*it == request) { |
| mStreamingRequests.erase(it); |
| return; |
| } |
| } |
| } |
| |
| // ================================================================================ |
| ReportWriter::ReportWriter(const sp<ReportBatch>& batch) |
| :mBatch(batch), |
| mPersistedFile(), |
| mMaxPersistedPrivacyPolicy(PRIVACY_POLICY_UNSET) { |
| } |
| |
| ReportWriter::~ReportWriter() { |
| } |
| |
| void ReportWriter::setPersistedFile(sp<ReportFile> file) { |
| mPersistedFile = file; |
| } |
| |
| void ReportWriter::setMaxPersistedPrivacyPolicy(uint8_t privacyPolicy) { |
| mMaxPersistedPrivacyPolicy = privacyPolicy; |
| } |
| |
| void ReportWriter::startSection(int sectionId) { |
| mCurrentSectionId = sectionId; |
| mSectionStartTimeMs = uptimeMillis(); |
| |
| mSectionStatsCalledForSectionId = -1; |
| mDumpSizeBytes = 0; |
| mDumpDurationMs = 0; |
| mSectionTimedOut = false; |
| mSectionTruncated = false; |
| mSectionBufferSuccess = false; |
| mHadError = false; |
| mSectionErrors.clear(); |
| |
| } |
| |
| void ReportWriter::setSectionStats(const FdBuffer& buffer) { |
| mSectionStatsCalledForSectionId = mCurrentSectionId; |
| mDumpSizeBytes = buffer.size(); |
| mDumpDurationMs = buffer.durationMs(); |
| mSectionTimedOut = buffer.timedOut(); |
| mSectionTruncated = buffer.truncated(); |
| mSectionBufferSuccess = !buffer.timedOut() && !buffer.truncated(); |
| } |
| |
| void ReportWriter::endSection(IncidentMetadata::SectionStats* sectionMetadata) { |
| long endTime = uptimeMillis(); |
| |
| if (mSectionStatsCalledForSectionId != mCurrentSectionId) { |
| ALOGW("setSectionStats not called for section %d", mCurrentSectionId); |
| } |
| |
| sectionMetadata->set_id(mCurrentSectionId); |
| sectionMetadata->set_success((!mHadError) && mSectionBufferSuccess); |
| sectionMetadata->set_report_size_bytes(mMaxSectionDataFilteredSize); |
| sectionMetadata->set_exec_duration_ms(endTime - mSectionStartTimeMs); |
| sectionMetadata->set_dump_size_bytes(mDumpSizeBytes); |
| sectionMetadata->set_dump_duration_ms(mDumpDurationMs); |
| sectionMetadata->set_timed_out(mSectionTimedOut); |
| sectionMetadata->set_is_truncated(mSectionTruncated); |
| sectionMetadata->set_error_msg(mSectionErrors); |
| } |
| |
| void ReportWriter::warning(const Section* section, status_t err, const char* format, ...) { |
| va_list args; |
| va_start(args, format); |
| vflog(section, err, ANDROID_LOG_ERROR, "error", format, args); |
| va_end(args); |
| } |
| |
| void ReportWriter::error(const Section* section, status_t err, const char* format, ...) { |
| va_list args; |
| va_start(args, format); |
| vflog(section, err, ANDROID_LOG_WARN, "warning", format, args); |
| va_end(args); |
| } |
| |
| void ReportWriter::vflog(const Section* section, status_t err, int level, const char* levelText, |
| const char* format, va_list args) { |
| const char* prefixFormat = "%s in section %d (%d) '%s': "; |
| int prefixLen = snprintf(NULL, 0, prefixFormat, levelText, section->id, |
| err, strerror(-err)); |
| |
| va_list measureArgs; |
| va_copy(measureArgs, args); |
| int messageLen = vsnprintf(NULL, 0, format, args); |
| va_end(measureArgs); |
| |
| char* line = (char*)malloc(prefixLen + messageLen + 1); |
| if (line == NULL) { |
| // All hope is lost, just give up. |
| return; |
| } |
| |
| sprintf(line, prefixFormat, levelText, section->id, err, strerror(-err)); |
| |
| vsprintf(line + prefixLen, format, args); |
| |
| __android_log_write(level, LOG_TAG, line); |
| |
| if (mSectionErrors.length() == 0) { |
| mSectionErrors = line; |
| } else { |
| mSectionErrors += '\n'; |
| mSectionErrors += line; |
| } |
| |
| free(line); |
| |
| if (level >= ANDROID_LOG_ERROR) { |
| mHadError = true; |
| } |
| } |
| |
| // Reads data from FdBuffer and writes it to the requests file descriptor. |
| status_t ReportWriter::writeSection(const FdBuffer& buffer) { |
| PrivacyFilter filter(mCurrentSectionId, get_privacy_of_section(mCurrentSectionId)); |
| |
| // Add the fd for the persisted requests |
| if (mPersistedFile != nullptr) { |
| filter.addFd(new PersistedFilterFd(mMaxPersistedPrivacyPolicy, |
| mPersistedFile->getDataFileFd(), mPersistedFile)); |
| } |
| |
| // Add the fds for the streamed requests |
| mBatch->forEachStreamingRequest([&filter, this](const sp<ReportRequest>& request) { |
| if (request->ok() |
| && request->args.containsSection(mCurrentSectionId, |
| section_requires_specific_mention(mCurrentSectionId))) { |
| filter.addFd(new StreamingFilterFd(request->args.getPrivacyPolicy(), |
| request->getFd(), request)); |
| } |
| }); |
| |
| return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize); |
| } |
| |
| |
| // ================================================================================ |
| Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch) |
| :mWorkDirectory(workDirectory), |
| mWriter(batch), |
| mBatch(batch) { |
| } |
| |
| Reporter::~Reporter() { |
| } |
| |
| void Reporter::runReport(size_t* reportByteSize) { |
| status_t err = NO_ERROR; |
| |
| IncidentMetadata metadata; |
| int persistedPrivacyPolicy = PRIVACY_POLICY_UNSET; |
| |
| (*reportByteSize) = 0; |
| |
| // Tell everyone that we're starting. |
| ALOGI("Starting incident report"); |
| mBatch->forEachListener([](const auto& listener) { listener->onReportStarted(); }); |
| |
| if (mBatch->hasPersistedReports()) { |
| // Open a work file to contain the contents of all of the persisted reports. |
| // For this block, if we can't initialize the report file for some reason, |
| // then we will remove the persisted ReportRequests from the report, but |
| // continue with the streaming ones. |
| mPersistedFile = mWorkDirectory->createReportFile(); |
| ALOGI("Report will be persisted: envelope: %s data: %s", |
| mPersistedFile->getEnvelopeFileName().c_str(), |
| mPersistedFile->getDataFileName().c_str()); |
| |
| // Record all of the metadata to the persisted file's metadata file. |
| // It will be read from there and reconstructed as the actual reports |
| // are sent out. |
| if (mPersistedFile != nullptr) { |
| mBatch->forEachPersistedRequest([this, &persistedPrivacyPolicy]( |
| const sp<ReportRequest>& request) { |
| mPersistedFile->addReport(request->args); |
| if (request->args.getPrivacyPolicy() < persistedPrivacyPolicy) { |
| persistedPrivacyPolicy = request->args.getPrivacyPolicy(); |
| } |
| }); |
| mPersistedFile->setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy); |
| err = mPersistedFile->saveEnvelope(); |
| if (err != NO_ERROR) { |
| mWorkDirectory->remove(mPersistedFile); |
| mPersistedFile = nullptr; |
| } |
| mWriter.setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy); |
| } |
| |
| if (mPersistedFile != nullptr) { |
| err = mPersistedFile->startWritingDataFile(); |
| if (err != NO_ERROR) { |
| mWorkDirectory->remove(mPersistedFile); |
| mPersistedFile = nullptr; |
| } |
| } |
| |
| if (mPersistedFile != nullptr) { |
| mWriter.setPersistedFile(mPersistedFile); |
| } else { |
| ALOGW("Error creating the persisted file, so clearing persisted reports."); |
| // If we couldn't open the file (permissions err, etc), then |
| // we still want to proceed with any streaming reports, but |
| // cancel all of the persisted ones. |
| mBatch->forEachPersistedRequest([](const sp<ReportRequest>& request) { |
| sp<IIncidentReportStatusListener> listener = request->getListener(); |
| if (listener != nullptr) { |
| listener->onReportFailed(); |
| } |
| }); |
| mBatch->clearPersistedRequests(); |
| } |
| } |
| |
| // If we have a persisted ID, then we allow all the readers to see that. There's |
| // enough in the data to allow for a join, and nothing in here that intrisincally |
| // could ever prevent that, so just give them the ID. If we don't have that then we |
| // make and ID that's extremely likely to be unique, but clock resetting could allow |
| // it to be duplicate. |
| int64_t reportId; |
| if (mPersistedFile != nullptr) { |
| reportId = mPersistedFile->getTimestampNs(); |
| } else { |
| struct timespec spec; |
| clock_gettime(CLOCK_REALTIME, &spec); |
| reportId = (spec.tv_sec) * 1000 + spec.tv_nsec; |
| } |
| |
| // Write the incident report headers - each request gets its own headers. It's different |
| // from the other top-level fields in IncidentReport that are the sections where the rest |
| // is all shared data (although with their own individual privacy filtering). |
| mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) { |
| const vector<vector<uint8_t>>& headers = request->args.headers(); |
| for (vector<vector<uint8_t>>::const_iterator buf = headers.begin(); buf != headers.end(); |
| buf++) { |
| // If there was an error now, there will be an error later and we will remove |
| // it from the list then. |
| write_header_section(request->getFd(), buf->data(), buf->size()); |
| } |
| }); |
| |
| // If writing to any of the headers failed, we don't want to keep processing |
| // sections for it. |
| cancel_and_remove_failed_requests(); |
| |
| // For each of the report fields, see if we need it, and if so, execute the command |
| // and report to those that care that we're doing it. |
| for (const Section** section = SECTION_LIST; *section; section++) { |
| const int sectionId = (*section)->id; |
| |
| // If nobody wants this section, skip it. |
| if (!mBatch->containsSection(sectionId)) { |
| continue; |
| } |
| |
| ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string()); |
| IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections(); |
| |
| // Notify listener of starting |
| mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { |
| listener->onReportSectionStatus( |
| sectionId, IIncidentReportStatusListener::STATUS_STARTING); |
| }); |
| |
| // Go get the data and write it into the file descriptors. |
| mWriter.startSection(sectionId); |
| err = (*section)->Execute(&mWriter); |
| mWriter.endSection(sectionMetadata); |
| |
| // Sections returning errors are fatal. Most errors should not be fatal. |
| if (err != NO_ERROR) { |
| mWriter.error((*section), err, "Section failed. Stopping report."); |
| goto DONE; |
| } |
| |
| // The returned max data size is used for throttling too many incident reports. |
| (*reportByteSize) += sectionMetadata->report_size_bytes(); |
| |
| // For any requests that failed during this section, remove them now. We do this |
| // before calling back about section finished, so listeners do not erroniously get the |
| // impression that the section succeeded. But we do it here instead of inside |
| // writeSection so that the callback is done from a known context and not from the |
| // bowels of a section, where changing the batch could cause odd errors. |
| cancel_and_remove_failed_requests(); |
| |
| // Notify listener of finishing |
| mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { |
| listener->onReportSectionStatus( |
| sectionId, IIncidentReportStatusListener::STATUS_FINISHED); |
| }); |
| |
| ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string()); |
| } |
| |
| DONE: |
| // Finish up the persisted file. |
| if (mPersistedFile != nullptr) { |
| mPersistedFile->closeDataFile(); |
| |
| // Set the stored metadata |
| IncidentReportArgs combinedArgs; |
| mBatch->getCombinedPersistedArgs(&combinedArgs); |
| IncidentMetadata persistedMetadata; |
| make_metadata(&persistedMetadata, metadata, mPersistedFile->getTimestampNs(), |
| persistedPrivacyPolicy, combinedArgs); |
| mPersistedFile->setMetadata(persistedMetadata); |
| |
| mPersistedFile->markCompleted(); |
| err = mPersistedFile->saveEnvelope(); |
| if (err != NO_ERROR) { |
| ALOGW("mPersistedFile->saveEnvelope returned %s. Won't send broadcast", |
| strerror(-err)); |
| // Abandon ship. |
| mWorkDirectory->remove(mPersistedFile); |
| } |
| } |
| |
| // Write the metadata to the streaming ones |
| mBatch->forEachStreamingRequest([reportId, &metadata](const sp<ReportRequest>& request) { |
| IncidentMetadata streamingMetadata; |
| make_metadata(&streamingMetadata, metadata, reportId, |
| request->args.getPrivacyPolicy(), request); |
| status_t nonFatalErr = write_section(request->getFd(), FIELD_ID_METADATA, |
| streamingMetadata); |
| if (nonFatalErr != NO_ERROR) { |
| ALOGW("Error writing the metadata to streaming incident report. This is the last" |
| " thing so we won't return an error: %s", strerror(nonFatalErr)); |
| } |
| }); |
| |
| // Finish up the streaming ones. |
| mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) { |
| request->closeFd(); |
| }); |
| |
| // Tell the listeners that we're done. |
| if (err == NO_ERROR) { |
| mBatch->forEachListener([](const auto& listener) { |
| listener->onReportFinished(); |
| }); |
| } else { |
| mBatch->forEachListener([](const auto& listener) { |
| listener->onReportFailed(); |
| }); |
| } |
| |
| ALOGI("Done taking incident report err=%s", strerror(-err)); |
| } |
| |
| void Reporter::cancel_and_remove_failed_requests() { |
| // Handle a failure in the persisted file |
| if (mPersistedFile != nullptr) { |
| if (mPersistedFile->getWriteError() != NO_ERROR) { |
| ALOGW("Error writing to the persisted file (%s). Closing it and canceling.", |
| strerror(-mPersistedFile->getWriteError())); |
| mBatch->forEachPersistedRequest([this](const sp<ReportRequest>& request) { |
| sp<IIncidentReportStatusListener> listener = request->getListener(); |
| if (listener != nullptr) { |
| listener->onReportFailed(); |
| } |
| mBatch->removeRequest(request); |
| }); |
| mWriter.setPersistedFile(nullptr); |
| mPersistedFile->closeDataFile(); |
| mWorkDirectory->remove(mPersistedFile); |
| mPersistedFile = nullptr; |
| } |
| } |
| |
| // Handle failures in the streaming files |
| vector<sp<ReportRequest>> failed; |
| mBatch->getFailedRequests(&failed); |
| for (sp<ReportRequest>& request: failed) { |
| ALOGW("Error writing to a request stream (%s). Closing it and canceling.", |
| strerror(-request->getStatus())); |
| sp<IIncidentReportStatusListener> listener = request->getListener(); |
| if (listener != nullptr) { |
| listener->onReportFailed(); |
| } |
| request->closeFd(); // Will only close the streaming ones. |
| mBatch->removeRequest(request); |
| } |
| } |
| |
| } // namespace incidentd |
| } // namespace os |
| } // namespace android |