blob: 1ecb291c84a1926f9403d07634a96df4f90ceac0 [file] [log] [blame]
/*
* 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 LOG_TAG "incidentd"
#include "Reporter.h"
#include "protobuf.h"
#include "report_directory.h"
#include "section_list.h"
#include <private/android_filesystem_config.h>
#include <android/os/DropBoxManager.h>
#include <utils/SystemClock.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.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;
}
// ================================================================================
ReportRequest::ReportRequest(const IncidentReportArgs& a,
const sp<IIncidentReportStatusListener> &l, int f)
:args(a),
listener(l),
fd(f),
err(NO_ERROR)
{
}
ReportRequest::~ReportRequest()
{
}
// ================================================================================
ReportRequestSet::ReportRequestSet()
:mRequests(),
mWritableCount(0),
mMainFd(-1)
{
}
ReportRequestSet::~ReportRequestSet()
{
}
void
ReportRequestSet::add(const sp<ReportRequest>& request)
{
mRequests.push_back(request);
mWritableCount++;
}
void
ReportRequestSet::setMainFd(int fd)
{
mMainFd = fd;
mWritableCount++;
}
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--;
}
}
}
// The dropbox file
if (mMainFd >= 0) {
err = write_all(mMainFd, buf, size);
if (err != NO_ERROR) {
mMainFd = -1;
mWritableCount--;
}
}
// Return an error only when there are no FDs to write.
return mWritableCount > 0 ? NO_ERROR : err;
}
// ================================================================================
Reporter::Reporter()
:args(),
batch()
{
char buf[100];
// TODO: Make the max size smaller for user builds.
mMaxSize = 100 * 1024 * 1024;
mMaxCount = 100;
// 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()
{
}
Reporter::run_report_status_t
Reporter::runReport()
{
status_t err = NO_ERROR;
bool needMainFd = false;
int mainFd = -1;
// See if we need the main file
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
if ((*it)->fd < 0 && mainFd < 0) {
needMainFd = true;
break;
}
}
if (needMainFd) {
// Create the directory
err = create_directory(INCIDENT_DIRECTORY);
if (err != NO_ERROR) {
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);
// Open the file.
err = create_file(&mainFd);
if (err != NO_ERROR) {
goto done;
}
// Add to the set
batch.setMainFd(mainFd);
}
// Tell everyone that we're starting.
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.
}
}
// 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 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 ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
(*it)->listener->onReportSectionStatus(id,
IIncidentReportStatusListener::STATUS_STARTING);
}
}
// Execute - go get the data and write it into the file descriptors.
err = (*section)->Execute(&batch);
if (err != NO_ERROR) {
ALOGW("Incident section %s (%d) failed. Stopping report.",
(*section)->name.string(), id);
goto done;
}
// Notify listener of starting
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);
}
}
}
}
done:
// Close the file.
if (mainFd >= 0) {
close(mainFd);
}
// Tell everyone that we're done.
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
if ((*it)->listener != NULL) {
if (err == NO_ERROR) {
(*it)->listener->onReportFinished();
} else {
(*it)->listener->onReportFailed();
}
}
}
// Put the report into dropbox.
if (needMainFd && err == NO_ERROR) {
sp<DropBoxManager> dropbox = new DropBoxManager();
Status status = dropbox->addFile(String16("incident"), mFilename, 0);
ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string());
if (!status.isOk()) {
return REPORT_NEEDS_DROPBOX;
}
// 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());
}
return REPORT_FINISHED;
}
/**
* Create our output file and set the access permissions to -rw-rw----
*/
status_t
Reporter::create_file(int* fd)
{
const char* filename = mFilename.c_str();
*fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660);
if (*fd < 0) {
ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno));
return -errno;
}
// Override umask. Not super critical. If it fails go on with life.
chmod(filename, 0660);
if (chown(filename, AID_SYSTEM, AID_SYSTEM)) {
ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno));
status_t err = -errno;
unlink(mFilename.c_str());
return err;
}
return NO_ERROR;
}
// ================================================================================
Reporter::run_report_status_t
Reporter::upload_backlog()
{
DIR* dir;
struct dirent* entry;
struct stat st;
if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) {
ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string());
return REPORT_NEEDS_DROPBOX;
}
String8 dirbase(INCIDENT_DIRECTORY + "/");
sp<DropBoxManager> dropbox = new DropBoxManager();
// Enumerate, count and add up size
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
String8 filename = dirbase + entry->d_name;
if (stat(filename.string(), &st) != 0) {
ALOGE("Unable to stat file %s", filename.string());
continue;
}
if (!S_ISREG(st.st_mode)) {
continue;
}
Status status = dropbox->addFile(String16("incident"), filename.string(), 0);
ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string());
if (!status.isOk()) {
return REPORT_NEEDS_DROPBOX;
}
// 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(filename.string());
}
closedir(dir);
return REPORT_FINISHED;
}