diff options
Diffstat (limited to 'cmds/incidentd/src/Reporter.cpp')
-rw-r--r-- | cmds/incidentd/src/Reporter.cpp | 256 |
1 files changed, 113 insertions, 143 deletions
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 1ecb291c84a1..12764f8ff09c 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -13,242 +13,210 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#define LOG_TAG "incidentd" +#define DEBUG false +#include "Log.h" #include "Reporter.h" -#include "protobuf.h" +#include "Privacy.h" #include "report_directory.h" #include "section_list.h" -#include <private/android_filesystem_config.h> #include <android/os/DropBoxManager.h> +#include <private/android_filesystem_config.h> #include <utils/SystemClock.h> -#include <sys/types.h> -#include <sys/stat.h> #include <dirent.h> -#include <fcntl.h> #include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> /** * The directory where the incident reports are stored. */ -static const String8 INCIDENT_DIRECTORY("/data/incidents"); - -static status_t -write_all(int fd, uint8_t const* buf, size_t size) -{ - while (size > 0) { - ssize_t amt = ::write(fd, buf, size); - if (amt < 0) { - return -errno; - } - size -= amt; - buf += amt; - } - return NO_ERROR; -} +static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/"; // ================================================================================ ReportRequest::ReportRequest(const IncidentReportArgs& a, - const sp<IIncidentReportStatusListener> &l, int f) - :args(a), - listener(l), - fd(f), - err(NO_ERROR) -{ -} + const sp<IIncidentReportStatusListener>& l, int f) + : args(a), listener(l), fd(f), err(NO_ERROR) {} -ReportRequest::~ReportRequest() -{ +ReportRequest::~ReportRequest() { + if (fd >= 0) { + // clean up the opened file descriptor + close(fd); + } } +bool ReportRequest::ok() { return fd >= 0 && err == NO_ERROR; } + // ================================================================================ ReportRequestSet::ReportRequestSet() - :mRequests(), - mWritableCount(0), - mMainFd(-1) -{ -} + : mRequests(), mSections(), mMainFd(-1), mMainDest(-1), mMetadata(), mSectionStats() {} -ReportRequestSet::~ReportRequestSet() -{ -} +ReportRequestSet::~ReportRequestSet() {} -void -ReportRequestSet::add(const sp<ReportRequest>& request) -{ +// TODO: dedup on exact same args and fd, report the status back to listener! +void ReportRequestSet::add(const sp<ReportRequest>& request) { mRequests.push_back(request); - mWritableCount++; + mSections.merge(request->args); + mMetadata.set_request_size(mMetadata.request_size() + 1); } -void -ReportRequestSet::setMainFd(int fd) -{ +void ReportRequestSet::setMainFd(int fd) { mMainFd = fd; - mWritableCount++; + mMetadata.set_use_dropbox(fd > 0); } -status_t -ReportRequestSet::write(uint8_t const* buf, size_t size) -{ - status_t err = EBADF; - - // The streaming ones - int const N = mRequests.size(); - for (int i=N-1; i>=0; i--) { - sp<ReportRequest> request = mRequests[i]; - if (request->fd >= 0 && request->err == NO_ERROR) { - err = write_all(request->fd, buf, size); - if (err != NO_ERROR) { - request->err = err; - mWritableCount--; - } - } +void ReportRequestSet::setMainDest(int dest) { + mMainDest = dest; + PrivacySpec spec = PrivacySpec::new_spec(dest); + switch (spec.dest) { + case android::os::DEST_AUTOMATIC: + mMetadata.set_dest(IncidentMetadata_Destination_AUTOMATIC); + break; + case android::os::DEST_EXPLICIT: + mMetadata.set_dest(IncidentMetadata_Destination_EXPLICIT); + break; + case android::os::DEST_LOCAL: + mMetadata.set_dest(IncidentMetadata_Destination_LOCAL); + break; } +} - // The dropbox file - if (mMainFd >= 0) { - err = write_all(mMainFd, buf, size); - if (err != NO_ERROR) { - mMainFd = -1; - mWritableCount--; - } - } +bool ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); } - // Return an error only when there are no FDs to write. - return mWritableCount > 0 ? NO_ERROR : err; +IncidentMetadata::SectionStats* ReportRequestSet::sectionStats(int id) { + if (mSectionStats.find(id) == mSectionStats.end()) { + auto stats = mMetadata.add_sections(); + stats->set_id(id); + mSectionStats[id] = stats; + } + return mSectionStats[id]; } - // ================================================================================ -Reporter::Reporter() - :args(), - batch() -{ +Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; }; + +Reporter::Reporter(const char* directory) : batch() { char buf[100]; // TODO: Make the max size smaller for user builds. mMaxSize = 100 * 1024 * 1024; mMaxCount = 100; + // string ends up with '/' is a directory + String8 dir = String8(directory); + if (directory[dir.size() - 1] != '/') dir += "/"; + mIncidentDirectory = dir.string(); + // There can't be two at the same time because it's on one thread. mStartTime = time(NULL); - strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); - mFilename = INCIDENT_DIRECTORY + buf; -} - -Reporter::~Reporter() -{ + strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); + mFilename = mIncidentDirectory + buf; } -Reporter::run_report_status_t -Reporter::runReport() -{ +Reporter::~Reporter() {} +Reporter::run_report_status_t Reporter::runReport(size_t* reportByteSize) { status_t err = NO_ERROR; bool needMainFd = false; int mainFd = -1; + int mainDest = -1; + HeaderSection headers; + MetadataSection metadataSection; // See if we need the main file - for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { if ((*it)->fd < 0 && mainFd < 0) { needMainFd = true; + mainDest = (*it)->args.dest(); break; } } if (needMainFd) { // Create the directory - err = create_directory(INCIDENT_DIRECTORY); + if (!isTest) err = create_directory(mIncidentDirectory); if (err != NO_ERROR) { - goto done; + goto DONE; } // If there are too many files in the directory (for whatever reason), // delete the oldest ones until it's under the limit. Doing this first // does mean that we can go over, so the max size is not a hard limit. - clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount); + if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount); // Open the file. err = create_file(&mainFd); if (err != NO_ERROR) { - goto done; + goto DONE; } // Add to the set batch.setMainFd(mainFd); + batch.setMainDest(mainDest); } // Tell everyone that we're starting. - for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { if ((*it)->listener != NULL) { (*it)->listener->onReportStarted(); } } // Write the incident headers - for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { - const sp<ReportRequest> request = (*it); - const vector<vector<int8_t>>& headers = request->args.headers(); - - for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); - buf++) { - int fd = request->fd >= 0 ? request->fd : mainFd; - - uint8_t buffer[20]; - uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER, - buf->size()); - write_all(fd, buffer, p-buffer); - - write_all(fd, (uint8_t const*)buf->data(), buf->size()); - // If there was an error now, there will be an error later and we will remove - // it from the list then. - } - } + headers.Execute(&batch); // 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++) { + for (const Section** section = SECTION_LIST; *section; section++) { const int id = (*section)->id; - ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); - - if (this->args.containsSection(id)) { - // Notify listener of starting - for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + if (this->batch.containsSection(id)) { + ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); + for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { - (*it)->listener->onReportSectionStatus(id, - IIncidentReportStatusListener::STATUS_STARTING); + (*it)->listener->onReportSectionStatus( + id, IIncidentReportStatusListener::STATUS_STARTING); } } // Execute - go get the data and write it into the file descriptors. + auto stats = batch.sectionStats(id); + int64_t startTime = uptimeMillis(); err = (*section)->Execute(&batch); + int64_t endTime = uptimeMillis(); + stats->set_success(err == NO_ERROR); + stats->set_exec_duration_ms(endTime - startTime); if (err != NO_ERROR) { - ALOGW("Incident section %s (%d) failed. Stopping report.", - (*section)->name.string(), id); - goto done; + ALOGW("Incident section %s (%d) failed: %s. Stopping report.", + (*section)->name.string(), id, strerror(-err)); + goto DONE; } + (*reportByteSize) += stats->report_size_bytes(); // Notify listener of starting - for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { - (*it)->listener->onReportSectionStatus(id, - IIncidentReportStatusListener::STATUS_FINISHED); + (*it)->listener->onReportSectionStatus( + id, IIncidentReportStatusListener::STATUS_FINISHED); } } + ALOGD("Finish incident report section %d '%s'", id, (*section)->name.string()); } } -done: +DONE: + // Reports the metdadata when taking the incident report. + if (!isTest) metadataSection.Execute(&batch); + // Close the file. if (mainFd >= 0) { close(mainFd); } // Tell everyone that we're done. - for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { + for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { if ((*it)->listener != NULL) { if (err == NO_ERROR) { (*it)->listener->onReportFinished(); @@ -270,7 +238,7 @@ done: // If the status was ok, delete the file. If not, leave it around until the next // boot or the next checkin. If the directory gets too big older files will // be rotated out. - unlink(mFilename.c_str()); + if (!isTest) unlink(mFilename.c_str()); } return REPORT_FINISHED; @@ -279,12 +247,10 @@ done: /** * Create our output file and set the access permissions to -rw-rw---- */ -status_t -Reporter::create_file(int* fd) -{ +status_t Reporter::create_file(int* fd) { const char* filename = mFilename.c_str(); - *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660); + *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660); if (*fd < 0) { ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); return -errno; @@ -293,7 +259,7 @@ Reporter::create_file(int* fd) // Override umask. Not super critical. If it fails go on with life. chmod(filename, 0660); - if (chown(filename, AID_SYSTEM, AID_SYSTEM)) { + if (chown(filename, AID_INCIDENTD, AID_INCIDENTD)) { ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); status_t err = -errno; unlink(mFilename.c_str()); @@ -303,28 +269,32 @@ Reporter::create_file(int* fd) return NO_ERROR; } -// ================================================================================ -Reporter::run_report_status_t -Reporter::upload_backlog() -{ +Reporter::run_report_status_t Reporter::upload_backlog() { DIR* dir; struct dirent* entry; struct stat st; + status_t err; + + ALOGD("Start uploading backlogs in %s", INCIDENT_DIRECTORY); + if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) { + ALOGE("directory doesn't exist: %s", strerror(-err)); + return REPORT_FINISHED; + } - if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) { - ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string()); + if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) { + ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY); return REPORT_NEEDS_DROPBOX; } - String8 dirbase(INCIDENT_DIRECTORY + "/"); sp<DropBoxManager> dropbox = new DropBoxManager(); // Enumerate, count and add up size + int count = 0; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') { continue; } - String8 filename = dirbase + entry->d_name; + String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name; if (stat(filename.string(), &st) != 0) { ALOGE("Unable to stat file %s", filename.string()); continue; @@ -343,10 +313,10 @@ Reporter::upload_backlog() // boot or the next checkin. If the directory gets too big older files will // be rotated out. unlink(filename.string()); + count++; } - + ALOGD("Successfully uploaded %d files to Dropbox.", count); closedir(dir); return REPORT_FINISHED; } - |