diff --git a/Android.mk b/Android.mk index 9d1b0ccb3ca2e..88be12f23a77e 100644 --- a/Android.mk +++ b/Android.mk @@ -38,7 +38,9 @@ ifneq ($(ANDROID_BUILD_EMBEDDED),true) include $(CLEAR_VARS) # FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk -LOCAL_SRC_FILES := $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) +LOCAL_SRC_FILES := \ + $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) \ + $(call all-proto-files-under, core/proto) # EventLogTags files. LOCAL_SRC_FILES += \ @@ -234,6 +236,9 @@ LOCAL_SRC_FILES += \ core/java/android/os/IDeviceIdentifiersPolicyService.aidl \ core/java/android/os/IDeviceIdleController.aidl \ core/java/android/os/IHardwarePropertiesManager.aidl \ + core/java/android/os/IIncidentManager.aidl \ + core/java/android/os/IIncidentReportCompletedListener.aidl \ + core/java/android/os/IIncidentReportStatusListener.aidl \ core/java/android/os/IMaintenanceActivityListener.aidl \ core/java/android/os/IMessenger.aidl \ core/java/android/os/INetworkActivityListener.aidl \ @@ -528,6 +533,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ android.hardware.thermal@1.0-java-constants \ android.hardware.health@1.0-java-constants \ +LOCAL_PROTOC_OPTIMIZE_TYPE := stream +LOCAL_PROTOC_FLAGS := \ + -Iexternal/protobuf/src + LOCAL_MODULE := framework LOCAL_JACK_FLAGS := --multi-dex native @@ -1385,6 +1394,35 @@ endif include $(BUILD_JAVA_LIBRARY) +# ==== c++ proto host library ============================== +include $(CLEAR_VARS) +LOCAL_MODULE := libplatformprotos +LOCAL_PROTOC_OPTIMIZE_TYPE := full +LOCAL_PROTOC_FLAGS := \ + --include_source_info \ + -Iexternal/protobuf/src +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto) +LOCAL_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +LOCAL_EXPORT_C_INCLUDES := \ + $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto +include $(BUILD_HOST_SHARED_LIBRARY) + + +# ==== java proto host library ============================== +include $(CLEAR_VARS) +LOCAL_MODULE := platformprotos +LOCAL_PROTOC_OPTIMIZE_TYPE := full +LOCAL_PROTOC_FLAGS := \ + -Iexternal/protobuf/src +LOCAL_SOURCE_FILES_ALL_GENERATED := true +LOCAL_SRC_FILES := \ + $(call all-proto-files-under, core/proto) \ + $(call all-proto-files-under, libs/incident/proto) +include $(BUILD_HOST_JAVA_LIBRARY) + # Include subdirectory makefiles # ============================================================ diff --git a/api/system-current.txt b/api/system-current.txt index 64d0f266921b5..eb6710c0ad031 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -31713,6 +31713,27 @@ package android.os { method public abstract android.os.IBinder asBinder(); } + public class IncidentManager { + method public void reportIncident(android.os.IncidentReportArgs); + method public void reportIncident(java.lang.String, byte[]); + } + + public final class IncidentReportArgs implements android.os.Parcelable { + ctor public IncidentReportArgs(); + ctor public IncidentReportArgs(android.os.Parcel); + method public void addHeader(byte[]); + method public void addSection(int); + method public boolean containsSection(int); + method public int describeContents(); + method public boolean isAll(); + method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException; + method public void readFromParcel(android.os.Parcel); + method public int sectionCount(); + method public void setAll(boolean); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class LocaleList implements android.os.Parcelable { ctor public LocaleList(java.util.Locale...); method public int describeContents(); diff --git a/api/test-current.txt b/api/test-current.txt index 7e5de6ad5d697..e11dcd1e9a738 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -29286,6 +29286,27 @@ package android.os { method public abstract android.os.IBinder asBinder(); } + public class IncidentManager { + method public void reportIncident(android.os.IncidentReportArgs); + method public void reportIncident(java.lang.String, byte[]); + } + + public final class IncidentReportArgs implements android.os.Parcelable { + ctor public IncidentReportArgs(); + ctor public IncidentReportArgs(android.os.Parcel); + method public void addHeader(byte[]); + method public void addSection(int); + method public boolean containsSection(int); + method public int describeContents(); + method public boolean isAll(); + method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException; + method public void readFromParcel(android.os.Parcel); + method public int sectionCount(); + method public void setAll(boolean); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class LocaleList implements android.os.Parcelable { ctor public LocaleList(java.util.Locale...); method public int describeContents(); diff --git a/cmds/incident/Android.mk b/cmds/incident/Android.mk new file mode 100644 index 0000000000000..e1c9b93a8e6d4 --- /dev/null +++ b/cmds/incident/Android.mk @@ -0,0 +1,48 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + main.cpp + +LOCAL_MODULE := incident + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + liblog \ + libutils \ + libincident + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +LOCAL_MODULE_CLASS := EXECUTABLES +gen_src_dir := $(local-generated-sources-dir) + +gen := $(gen_src_dir)/incident_sections.cpp +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen +$(gen): PRIVATE_CUSTOM_TOOL = \ + $(HOST_OUT_EXECUTABLES)/incident-section-gen > $@ +$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen + $(transform-generated-source) +LOCAL_GENERATED_SOURCES += $(gen) + +gen_src_dir:= +gen:= + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incident/incident_sections.h b/cmds/incident/incident_sections.h new file mode 100644 index 0000000000000..1972088fc82cd --- /dev/null +++ b/cmds/incident/incident_sections.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef INCIDENT_SECTIONS_H +#define INCIDENT_SECTIONS_H + +struct IncidentSection +{ + int id; + char const* name; +}; + +extern IncidentSection const INCIDENT_SECTIONS[]; +extern const int INCIDENT_SECTION_COUNT; + +#endif // INCIDENT_SECTIONS_H diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp new file mode 100644 index 0000000000000..91b7c22b2038a --- /dev/null +++ b/cmds/incident/main.cpp @@ -0,0 +1,236 @@ +/* + * 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 "incident" + +#include "incident_sections.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; + +// ================================================================================ +class StatusListener : public BnIncidentReportStatusListener { +public: + StatusListener(); + virtual ~StatusListener(); + + virtual Status onReportStarted(); + virtual Status onReportSectionStatus(int32_t section, int32_t status); + virtual Status onReportServiceStatus(const String16& service, int32_t status); + virtual Status onReportFinished(); + virtual Status onReportFailed(); +}; + +StatusListener::StatusListener() +{ +} + +StatusListener::~StatusListener() +{ +} + +Status +StatusListener::onReportStarted() +{ + return Status::ok(); +} + +Status +StatusListener::onReportSectionStatus(int32_t section, int32_t status) +{ + fprintf(stderr, "section %d status %d\n", section, status); + return Status::ok(); +} + +Status +StatusListener::onReportServiceStatus(const String16& service, int32_t status) +{ + fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status); + return Status::ok(); +} + +Status +StatusListener::onReportFinished() +{ + fprintf(stderr, "done\n"); + exit(0); + return Status::ok(); +} + +Status +StatusListener::onReportFailed() +{ + fprintf(stderr, "failed\n"); + exit(1); + return Status::ok(); +} + +// ================================================================================ +static IncidentSection const* +find_section(const char* name) +{ + size_t low = 0; + size_t high = INCIDENT_SECTION_COUNT - 1; + + while (low <= high) { + size_t mid = (low + high) >> 1; + IncidentSection const* section = INCIDENT_SECTIONS + mid; + + int cmp = strcmp(section->name, name); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return section; + } + } + return NULL; +} + +// ================================================================================ +static void +usage(FILE* out) +{ + fprintf(out, "usage: incident OPTIONS [SECTION...]\n"); + fprintf(out, "\n"); + fprintf(out, "Takes an incident report.\n"); + fprintf(out, "\n"); + fprintf(out, "OPTIONS\n"); + fprintf(out, " -b (default) print the report to stdout (in proto format)\n"); + fprintf(out, " -d send the report into dropbox\n"); + fprintf(out, "\n"); + fprintf(out, " SECTION the field numbers of the incident report fields to include\n"); + fprintf(out, "\n"); +} + +int +main(int argc, char** argv) +{ + Status status; + IncidentReportArgs args; + enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT; + + // Parse the args + int opt; + while ((opt = getopt(argc, argv, "bhd")) != -1) { + switch (opt) { + case 'b': + destination = DEST_STDOUT; + break; + case 'h': + usage(stdout); + return 0; + case 'd': + destination = DEST_DROPBOX; + break; + default: + usage(stderr); + return 1; + } + } + + if (optind == argc) { + args.setAll(true); + } else { + for (int i=optind; iid); + } + } + } + } + + + + // Start the thread pool. + sp ps(ProcessState::self()); + ps->startThreadPool(); + ps->giveThreadPoolName(); + + // Look up the service + sp service = interface_cast( + defaultServiceManager()->getService(android::String16("incident"))); + if (service == NULL) { + fprintf(stderr, "Couldn't look up the incident service\n"); + return 1; + } + + // Construct the stream + int fds[2]; + pipe(fds); + + unique_fd readEnd(fds[0]); + unique_fd writeEnd(fds[1]); + + if (destination == DEST_STDOUT) { + // Call into the service + sp listener(new StatusListener()); + status = service->reportIncidentToStream(args, listener, writeEnd); + + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + } + + // Wait for the result and print out the data they send. + //IPCThreadState::self()->joinThreadPool(); + + while (true) { + int amt = splice(fds[0], NULL, STDOUT_FILENO, NULL, 4096, 0); + fprintf(stderr, "spliced %d bytes\n", amt); + if (amt < 0) { + return errno; + } else if (amt == 0) { + return 0; + } + } + } else { + status = service->reportIncident(args); + if (!status.isOk()) { + fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string()); + return 1; + } else { + return 0; + } + } + +} diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk new file mode 100644 index 0000000000000..bacf672e81b59 --- /dev/null +++ b/cmds/incidentd/Android.mk @@ -0,0 +1,56 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := incidentd + +LOCAL_SRC_FILES := \ + src/FdBuffer.cpp \ + src/IncidentService.cpp \ + src/Reporter.cpp \ + src/Section.cpp \ + src/main.cpp \ + src/protobuf.cpp \ + src/report_directory.cpp \ + src/section_list.cpp + +LOCAL_CFLAGS += \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +ifeq (debug,) + LOCAL_CFLAGS += \ + -g -O0 +else + # optimize for size (protobuf glop can get big) + LOCAL_CFLAGS += \ + -Os +endif + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libbinder \ + libcutils \ + libincident \ + liblog \ + libselinux \ + libservices \ + libutils + +ifeq (BUILD_WITH_INCIDENTD_RC,true) +LOCAL_INIT_RC := incidentd.rc +endif + +include $(BUILD_EXECUTABLE) diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc new file mode 100644 index 0000000000000..d11e3cf70567c --- /dev/null +++ b/cmds/incidentd/incidentd.rc @@ -0,0 +1,16 @@ +# 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. + +#service incidentd /system/bin/incidentd +# class main diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp new file mode 100644 index 0000000000000..527d7eef3a96e --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -0,0 +1,129 @@ +/* + * 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 "FdBuffer.h" + +#include +#include + +#include +#include +#include + +const ssize_t BUFFER_SIZE = 16 * 1024; +const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max + + +FdBuffer::FdBuffer() + :mBuffers(), + mStartTime(-1), + mFinishTime(-1), + mCurrentWritten(-1), + mTimedOut(false), + mTruncated(false) +{ +} + +FdBuffer::~FdBuffer() +{ + const int N = mBuffers.size(); + for (int i=0; i= BUFFER_SIZE || mCurrentWritten < 0) { + if (mBuffers.size() == MAX_BUFFER_COUNT) { + mTruncated = true; + break; + } + buf = (uint8_t*)malloc(BUFFER_SIZE); + if (buf == NULL) { + return NO_MEMORY; + } + mBuffers.push_back(buf); + mCurrentWritten = 0; + } + + int64_t remainingTime = (mStartTime + timeout) - uptimeMillis(); + if (remainingTime <= 0) { + mTimedOut = true; + break; + } + + int count = poll(&pfds, 1, remainingTime); + if (count == 0) { + mTimedOut = true; + break; + } else if (count < 0) { + return -errno; + } else { + if ((pfds.revents & POLLERR) != 0) { + return errno != 0 ? -errno : UNKNOWN_ERROR; + } else { + ssize_t amt = ::read(fd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten); + if (amt < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; + } else { + return -errno; + } + } else if (amt == 0) { + break; + } + mCurrentWritten += amt; + } + } + } + + mFinishTime = uptimeMillis(); + return NO_ERROR; +} + +size_t +FdBuffer::size() +{ + return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten; +} + +status_t +FdBuffer::write(ReportRequestSet* reporter) +{ + const int N = mBuffers.size() - 1; + for (int i=0; iwrite(mBuffers[i], BUFFER_SIZE); + } + reporter->write(mBuffers[N], mCurrentWritten); + return NO_ERROR; +} + + diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h new file mode 100644 index 0000000000000..e12374f215582 --- /dev/null +++ b/cmds/incidentd/src/FdBuffer.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef FD_BUFFER_H +#define FD_BUFFER_H + +#include "Reporter.h" + +#include + +#include +#include + +using namespace android; +using namespace std; + +/** + * Reads a file into a buffer, and then writes that data to an FdSet. + */ +class FdBuffer +{ +public: + FdBuffer(); + ~FdBuffer(); + + /** + * Read the data until the timeout is hit or we hit eof. + * Returns NO_ERROR if there were no errors or if we timed out. + * Will mark the file O_NONBLOCK. + */ + status_t read(int fd, int64_t timeoutMs); + + /** + * Whether we timed out. + */ + bool timedOut() { return mTimedOut; } + + /** + * If more than 4 MB is read, we truncate the data and return success. + * Downstream tools must handle truncated incident reports as best as possible + * anyway because they could be cut off for a lot of reasons and it's best + * to get as much useful information out of the system as possible. If this + * happens, truncated() will return true so it can be marked. If the data is + * exactly 4 MB, truncated is still set. Sorry. + */ + bool truncated() { return mTruncated; } + + /** + * How much data was read. + */ + size_t size(); + + /** + * Write the data that we recorded to the fd given. + */ + status_t write(ReportRequestSet* requests); + + /** + * How long the read took in milliseconds. + */ + int64_t durationMs() { return mFinishTime - mStartTime; } + +private: + vector mBuffers; + int64_t mStartTime; + int64_t mFinishTime; + ssize_t mCurrentWritten; + bool mTimedOut; + bool mTruncated; +}; + + +#endif // FD_BUFFER_H diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp new file mode 100644 index 0000000000000..7c6789e6e5ba9 --- /dev/null +++ b/cmds/incidentd/src/IncidentService.cpp @@ -0,0 +1,244 @@ +/* + * 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 "IncidentService.h" + +#include "Reporter.h" + +#include +#include +#include +#include +#include + +#include + +using namespace android; + +enum { + WHAT_RUN_REPORT = 1, + WHAT_SEND_BACKLOG_TO_DROPBOX = 2 +}; + +//#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL * 60 * 5) +#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL) + +// ================================================================================ +String16 const DUMP_PERMISSION("android.permission.DUMP"); +String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); + +static Status +checkIncidentPermissions() +{ + if (!checkCallingPermission(DUMP_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.DUMP"); + } + if (!checkCallingPermission(USAGE_STATS_PERMISSION)) { + ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS", + IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission: android.permission.USAGE_STATS"); + } + return Status::ok(); +} + + +// ================================================================================ +ReportRequestQueue::ReportRequestQueue() +{ +} + +ReportRequestQueue::~ReportRequestQueue() +{ +} + +void +ReportRequestQueue::addRequest(const sp& request) +{ + unique_lock lock(mLock); + mQueue.push_back(request); +} + +sp +ReportRequestQueue::getNextRequest() +{ + unique_lock lock(mLock); + if (mQueue.empty()) { + return NULL; + } else { + sp front(mQueue.front()); + mQueue.pop_front(); + return front; + } +} + + +// ================================================================================ +ReportHandler::ReportHandler(const sp& handlerLooper, const sp& queue) + :mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS), + mHandlerLooper(handlerLooper), + mQueue(queue) +{ +} + +ReportHandler::~ReportHandler() +{ +} + +void +ReportHandler::handleMessage(const Message& message) +{ + switch (message.what) { + case WHAT_RUN_REPORT: + run_report(); + break; + case WHAT_SEND_BACKLOG_TO_DROPBOX: + send_backlog_to_dropbox(); + break; + } +} + +void +ReportHandler::scheduleRunReport(const sp& request) +{ + mQueue->addRequest(request); + mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT); + mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT)); +} + +void +ReportHandler::scheduleSendBacklogToDropbox() +{ + unique_lock lock(mLock); + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + schedule_send_backlog_to_dropbox_locked(); +} + +void +ReportHandler::schedule_send_backlog_to_dropbox_locked() +{ + mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX); + mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, + Message(WHAT_SEND_BACKLOG_TO_DROPBOX)); +} + +void +ReportHandler::run_report() +{ + sp reporter = new Reporter(); + + // Merge all of the requests into one that has all of the + // requested fields. + while (true) { + sp request = mQueue->getNextRequest(); + if (request == NULL) { + break; + } + reporter->batch.add(request); + reporter->args.merge(request->args); + } + + // 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. + // TODO: We should further rate-limit the reports to no more than N per time-period. + Reporter::run_report_status_t reportStatus = reporter->runReport(); + if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) { + unique_lock lock(mLock); + schedule_send_backlog_to_dropbox_locked(); + } +} + +void +ReportHandler::send_backlog_to_dropbox() +{ + if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) { + // There was a failure. Exponential backoff. + unique_lock lock(mLock); + mBacklogDelay *= 2; + ALOGI("Error sending to dropbox. Trying again in %lld minutes", + (mBacklogDelay / (1000000000LL * 60))); + schedule_send_backlog_to_dropbox_locked(); + } else { + mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; + } +} + +// ================================================================================ +IncidentService::IncidentService(const sp& handlerLooper) + :mQueue(new ReportRequestQueue()) +{ + mHandler = new ReportHandler(handlerLooper, mQueue); +} + +IncidentService::~IncidentService() +{ +} + +Status +IncidentService::reportIncident(const IncidentReportArgs& args) +{ + ALOGI("reportIncident"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1)); + + return Status::ok(); +} + +Status +IncidentService::reportIncidentToStream(const IncidentReportArgs& args, + const sp& listener, const unique_fd& stream) +{ + ALOGI("reportIncidentToStream"); + + Status status = checkIncidentPermissions(); + if (!status.isOk()) { + return status; + } + + int fd = dup(stream.get()); + if (fd < 0) { + return Status::fromStatusT(-errno); + } + + mHandler->scheduleRunReport(new ReportRequest(args, listener, fd)); + + return Status::ok(); +} + +Status +IncidentService::systemRunning() +{ + if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) { + return Status::fromExceptionCode(Status::EX_SECURITY, + "Only system uid can call systemRunning"); + } + + // When system_server is up and running, schedule the dropbox task to run. + mHandler->scheduleSendBacklogToDropbox(); + + return Status::ok(); +} + diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h new file mode 100644 index 0000000000000..d6f33dfb1a865 --- /dev/null +++ b/cmds/incidentd/src/IncidentService.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#ifndef INCIDENT_SERVICE_H +#define INCIDENT_SERVICE_H + +#include "Reporter.h" + +#include +#include + +#include +#include + +using namespace android; +using namespace android::base; +using namespace android::binder; +using namespace android::os; +using namespace std; + +// ================================================================================ +class ReportRequestQueue : public virtual RefBase +{ +public: + ReportRequestQueue(); + virtual ~ReportRequestQueue(); + + void addRequest(const sp& request); + sp getNextRequest(); + +private: + mutex mLock; + deque > mQueue; +}; + + +// ================================================================================ +class ReportHandler : public MessageHandler +{ +public: + ReportHandler(const sp& handlerLooper, const sp& queue); + virtual ~ReportHandler(); + + virtual void handleMessage(const Message& message); + + /** + * Adds a ReportRequest to the queue. + */ + void scheduleRunReport(const sp& request); + + /** + * Resets mBacklogDelay to the default and schedules sending + * the messages to dropbox. + */ + void scheduleSendBacklogToDropbox(); + +private: + mutex mLock; + nsecs_t mBacklogDelay; + sp mHandlerLooper; + sp mQueue; + + /** + * Runs all of the reports that have been queued. + */ + void run_report(); + + /** + * Schedules a dropbox task mBacklogDelay nanoseconds from now. + */ + void schedule_send_backlog_to_dropbox_locked(); + + /** + * Sends the backlog to the dropbox service. + */ + void send_backlog_to_dropbox(); +}; + + +// ================================================================================ +class IncidentService : public BnIncidentManager { +public: + IncidentService(const sp& handlerLooper); + virtual ~IncidentService(); + + virtual Status reportIncident(const IncidentReportArgs& args); + + virtual Status reportIncidentToStream(const IncidentReportArgs& args, + const sp& listener, const unique_fd& stream); + + virtual Status systemRunning(); + +private: + sp mQueue; + sp mHandler; +}; + + +#endif // INCIDENT_SERVICE_H diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp new file mode 100644 index 0000000000000..1ecb291c84a19 --- /dev/null +++ b/cmds/incidentd/src/Reporter.cpp @@ -0,0 +1,352 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include + +/** + * 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 &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& 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 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 request = (*it); + const vector>& headers = request->args.headers(); + + for (vector>::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 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 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; +} + diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h new file mode 100644 index 0000000000000..5b86561520f85 --- /dev/null +++ b/cmds/incidentd/src/Reporter.h @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#ifndef REPORTER_H +#define REPORTER_H + +#include +#include + +#include +#include + +#include + +using namespace android; +using namespace android::os; +using namespace std; + +// ================================================================================ +struct ReportRequest : public virtual RefBase +{ + IncidentReportArgs args; + sp listener; + int fd; + status_t err; + + ReportRequest(const IncidentReportArgs& args, + const sp &listener, int fd); + virtual ~ReportRequest(); +}; + +// ================================================================================ +class ReportRequestSet +{ +public: + ReportRequestSet(); + ~ReportRequestSet(); + + void add(const sp& request); + void setMainFd(int fd); + + // Write to all of the fds for the requests. If a write fails, it stops + // writing to that fd and returns NO_ERROR. When we are out of fds to write + // to it returns an error. + status_t write(uint8_t const* buf, size_t size); + + typedef vector>::iterator iterator; + + iterator begin() { return mRequests.begin(); } + iterator end() { return mRequests.end(); } + +private: + vector> mRequests; + int mWritableCount; + int mMainFd; +}; + +// ================================================================================ +class Reporter : public virtual RefBase +{ +public: + enum run_report_status_t { + REPORT_FINISHED = 0, + REPORT_NEEDS_DROPBOX = 1 + }; + + IncidentReportArgs args; + ReportRequestSet batch; + + Reporter(); + virtual ~Reporter(); + + // Run the report as described in the batch and args parameters. + run_report_status_t runReport(); + + static run_report_status_t upload_backlog(); + +private: + string mFilename; + off_t mMaxSize; + size_t mMaxCount; + time_t mStartTime; + + status_t create_file(int* fd); +}; + + +#endif // REPORTER_H diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp new file mode 100644 index 0000000000000..fac299ed0dcd3 --- /dev/null +++ b/cmds/incidentd/src/Section.cpp @@ -0,0 +1,292 @@ +/* + * 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 "Section.h" +#include "protobuf.h" + +#include +#include + +using namespace std; + +const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds + +// ================================================================================ +Section::Section(int i) + :id(i) +{ +} + +Section::~Section() +{ +} + +status_t +Section::WriteHeader(ReportRequestSet* requests, size_t size) const +{ + ssize_t amt; + uint8_t buf[20]; + uint8_t* p = write_length_delimited_tag_header(buf, this->id, size); + return requests->write(buf, p-buf); +} + +// ================================================================================ +struct WorkerThreadData : public virtual RefBase +{ + const WorkerThreadSection* section; + int fds[2]; + + // Lock protects these fields + mutex lock; + bool workerDone; + status_t workerError; + + WorkerThreadData(const WorkerThreadSection* section); + virtual ~WorkerThreadData(); + + int readFd() { return fds[0]; } + int writeFd() { return fds[1]; } +}; + +WorkerThreadData::WorkerThreadData(const WorkerThreadSection* sec) + :section(sec), + workerDone(false), + workerError(NO_ERROR) +{ + fds[0] = -1; + fds[1] = -1; +} + +WorkerThreadData::~WorkerThreadData() +{ +} + +// ================================================================================ +WorkerThreadSection::WorkerThreadSection(int id) + :Section(id) +{ +} + +WorkerThreadSection::~WorkerThreadSection() +{ +} + +static void* +worker_thread_func(void* cookie) +{ + WorkerThreadData* data = (WorkerThreadData*)cookie; + status_t err = data->section->BlockingCall(data->writeFd()); + + { + unique_lock lock(data->lock); + data->workerDone = true; + data->workerError = err; + } + + close(data->writeFd()); + 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(ReportRequestSet* requests) const +{ + status_t err = NO_ERROR; + pthread_t thread; + pthread_attr_t attr; + bool timedOut = false; + FdBuffer buffer; + + // Data shared between this thread and the worker thread. + sp data = new WorkerThreadData(this); + + // Create the pipe + err = pipe(data->fds); + if (err != 0) { + return -errno; + } + + // 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); + + // 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; + } + err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get()); + if (err != 0) { + pthread_attr_destroy(&attr); + return -err; + } + pthread_attr_destroy(&attr); + + // Loop reading until either the timeout or the worker side is done (i.e. eof). + err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS); + if (err != NO_ERROR) { + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%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. + close(data->readFd()); + + // 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 lock(data->lock); + if (!data->workerDone) { + // We timed out + timedOut = true; + } else { + if (data->workerError != NO_ERROR) { + err = data->workerError; + // TODO: Log this error into the incident report. + ALOGW("WorkerThreadSection '%s' worker failed with error '%s'", this->name.string(), + strerror(-err)); + } + } + } + + if (timedOut || buffer.timedOut()) { + ALOGW("WorkerThreadSection '%s' timed out", this->name.string()); + return NO_ERROR; + } + + if (buffer.truncated()) { + // TODO: Log this into the incident report. + } + + // TODO: There was an error with the command or buffering. Report that. For now + // just exit with a log messasge. + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed with error '%s'", this->name.string(), + strerror(-err)); + return NO_ERROR; + } + + // Write the data that was collected + ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(), + (int)buffer.durationMs()); + WriteHeader(requests, buffer.size()); + err = buffer.write(requests); + if (err != NO_ERROR) { + ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err)); + return err; + } + + return NO_ERROR; +} + +// ================================================================================ +CommandSection::CommandSection(int id, const char* first, ...) + :Section(id) +{ + va_list args; + int count = 0; + + va_start(args, first); + while (va_arg(args, const char*) != NULL) { + count++; + } + va_end(args); + + mCommand = (const char**)malloc(sizeof(const char*) * count); + + mCommand[0] = first; + name = first; + name += " "; + va_start(args, first); + for (int i=0; i service = defaultServiceManager()->checkService(mService); + + if (service == NULL) { + // Returning an error interrupts the entire incident report, so just + // log the failure. + // TODO: have a meta record inside the report that would log this + // failure inside the report, because the fact that we can't find + // the service is good data in and of itself. This is running in + // another thread so lock that carefully... + ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).string()); + return NO_ERROR; + } + + service->dump(pipeWriteFd, mArgs); + + return NO_ERROR; +} diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h new file mode 100644 index 0000000000000..35740e9771d56 --- /dev/null +++ b/cmds/incidentd/src/Section.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#ifndef SECTIONS_H +#define SECTIONS_H + +#include "FdBuffer.h" + +#include +#include +#include + +using namespace android; + +/** + * Base class for sections + */ +class Section +{ +public: + int id; + String8 name; + + Section(int id); + virtual ~Section(); + + virtual status_t Execute(ReportRequestSet* requests) const = 0; + + status_t WriteHeader(ReportRequestSet* requests, size_t size) const; +}; + +/** + * Section that reads in a file. + */ +class FileSection : public Section +{ +public: + FileSection(int id, const char* filename); + virtual ~FileSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char* mFilename; +}; + +/** + * Base class for sections that call a command that might need a timeout. + */ +class WorkerThreadSection : public Section +{ +public: + WorkerThreadSection(int id); + virtual ~WorkerThreadSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + + virtual status_t BlockingCall(int pipeWriteFd) const = 0; +}; + +/** + * Section that forks and execs a command, and puts stdout as the section. + */ +class CommandSection : public Section +{ +public: + CommandSection(int id, const char* first, ...); + virtual ~CommandSection(); + + virtual status_t Execute(ReportRequestSet* requests) const; + +private: + const char** mCommand; +}; + +/** + * Section that calls dumpsys on a system service. + */ +class DumpsysSection : public WorkerThreadSection +{ +public: + DumpsysSection(int id, const char* service, ...); + virtual ~DumpsysSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + String16 mService; + Vector mArgs; +}; + +#endif // SECTIONS_H + diff --git a/cmds/incidentd/src/main.cpp b/cmds/incidentd/src/main.cpp new file mode 100644 index 0000000000000..3a7511d430480 --- /dev/null +++ b/cmds/incidentd/src/main.cpp @@ -0,0 +1,64 @@ +/* + * 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 "IncidentService.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace android; + +// ================================================================================ +int +main(int /*argc*/, char** /*argv*/) +{ + // Set up the looper + sp looper(Looper::prepare(0 /* opts */)); + + // Set up the binder + sp ps(ProcessState::self()); + ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram + ps->startThreadPool(); + ps->giveThreadPoolName(); + IPCThreadState::self()->disableBackgroundScheduling(true); + + // Create the service + android::sp service = new IncidentService(looper); + if (defaultServiceManager()->addService(String16("incident"), service) != 0) { + ALOGE("Failed to add service"); + return -1; + } + + // Loop forever -- the reports run on this thread in a handler, and the + // binder calls remain responsive in their pool of one thread. + while (true) { + looper->pollAll(-1 /* timeoutMillis */); + } + ALOGW("incidentd escaped from its loop."); + + return 1; +} diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp new file mode 100644 index 0000000000000..cb864fd3b619e --- /dev/null +++ b/cmds/incidentd/src/protobuf.cpp @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#include "protobuf.h" + +uint8_t* +write_raw_varint(uint8_t* buf, uint32_t val) +{ + uint8_t* p = buf; + while (true) { + if ((val & ~0x7F) == 0) { + *p++ = (uint8_t)val; + return p; + } else { + *p++ = (uint8_t)((val & 0x7F) | 0x80); + val >>= 7; + } + } +} + +uint8_t* +write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size) +{ + buf = write_raw_varint(buf, (fieldId << 3) | 2); + buf = write_raw_varint(buf, size); + return buf; +} + diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h new file mode 100644 index 0000000000000..a24399832b007 --- /dev/null +++ b/cmds/incidentd/src/protobuf.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef PROTOBUF_H +#define PROTOBUF_H + +#include + +/** + * Write a varint into the buffer. Return the next position to write at. + * There must be 10 bytes in the buffer. The same as EncodedBuffer.writeRawVarint32 + */ +uint8_t* write_raw_varint(uint8_t* buf, uint32_t val); + +/** + * Write a protobuf WIRE_TYPE_LENGTH_DELIMITED header. Return the next position to write at. + * There must be 20 bytes in the buffer. + */ +uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size); + +enum { + // IncidentProto.header + FIELD_ID_INCIDENT_HEADER = 1 +}; + +#endif // PROTOBUF_H + diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp new file mode 100644 index 0000000000000..f60b8ac46cca1 --- /dev/null +++ b/cmds/incidentd/src/report_directory.cpp @@ -0,0 +1,173 @@ +/* + * 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 "report_directory.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +using namespace android; +using namespace std; + +status_t +create_directory(const char* directory) +{ + struct stat st; + status_t err = NO_ERROR; + char* dir = strdup(directory); + + // Skip first slash + char* d = dir + 1; + + // Create directories, assigning them to the system user + bool last = false; + while (!last) { + d = strchr(d, '/'); + if (d != NULL) { + *d = '\0'; + } else { + last = true; + } + if (stat(dir, &st) == 0) { + if (!S_ISDIR(st.st_mode)) { + err = ALREADY_EXISTS; + goto done; + } + } else { + if (mkdir(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to create incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chmod(dir, 0770)) { + ALOGE("No incident reports today. " + "Unable to set permissions for incident report dir %s: %s", dir, + strerror(errno)); + err = -errno; + goto done; + } + if (chown(dir, AID_SYSTEM, AID_SYSTEM)) { + ALOGE("No incident reports today. Unable to change ownership of dir %s: %s\n", + dir, strerror(errno)); + err = -errno; + goto done; + } + } + if (!last) { + *d++ = '/'; + } + } + + // Ensure that the final directory is owned by the system with 0770. If it isn't + // we won't write into it. + if (stat(directory, &st) != 0) { + ALOGE("No incident reports today. Can't stat: %s", directory); + err = -errno; + goto done; + } + if ((st.st_mode & 0777) != 0770) { + ALOGE("No incident reports today. Mode is %0o on report directory %s", + st.st_mode, directory); + err = BAD_VALUE; + goto done; + } + if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) { + ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s", + st.st_uid, st.st_gid, directory); + err = BAD_VALUE; + goto done; + } + +done: + free(dir); + return err; +} + +static bool +stat_mtime_cmp(const pair& a, const pair& b) +{ + return a.second.st_mtime < b.second.st_mtime; +} + +void +clean_directory(const char* directory, off_t maxSize, size_t maxCount) +{ + DIR* dir; + struct dirent* entry; + struct stat st; + + vector> files; + + if ((dir = opendir(directory)) == NULL) { + ALOGE("Couldn't open incident directory: %s", directory); + return; + } + + String8 dirbase(String8(directory) + "/"); + + off_t totalSize = 0; + size_t totalCount = 0; + + // 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; + } + files.push_back(pair(filename, st)); + + totalSize += st.st_size; + totalCount++; + } + + closedir(dir); + + // Count or size is less than max, then we're done. + if (totalSize < maxSize && totalCount < maxCount) { + return; + } + + // Oldest files first. + sort(files.begin(), files.end(), stat_mtime_cmp); + + // Remove files until we're under our limits. + for (vector>::iterator it = files.begin(); + it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) { + remove(it->first.string()); + totalSize -= it->second.st_size; + totalCount--; + } +} diff --git a/cmds/incidentd/src/report_directory.h b/cmds/incidentd/src/report_directory.h new file mode 100644 index 0000000000000..bed4f869cfe46 --- /dev/null +++ b/cmds/incidentd/src/report_directory.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef DIRECTORY_CLEANER_H +#define DIRECTORY_CLEANER_H + +#include + +#include + +using namespace android; + +status_t create_directory(const char* directory); +void clean_directory(const char* directory, off_t maxSize, size_t maxCount); + +#endif // DIRECTORY_CLEANER_H diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp new file mode 100644 index 0000000000000..b6112ed0f7df8 --- /dev/null +++ b/cmds/incidentd/src/section_list.cpp @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#include "section_list.h" + +//using namespace android::util; + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +const Section* SECTION_LIST[] = { + new DumpsysSection(3000, + "fingerprint", "--proto", "--incident", NULL), + NULL +}; + diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h new file mode 100644 index 0000000000000..c97751937d6d1 --- /dev/null +++ b/cmds/incidentd/src/section_list.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#ifndef SECTION_LIST_H +#define SECTION_LIST_H + +#include "Section.h" + +/** + * This is the mapping of section IDs to the commands that are run to get those commands. + */ +extern const Section* SECTION_LIST[]; + +#endif // SECTION_LIST_H + diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e1bd93eb3a4ca..6bddfba163d0b 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -96,6 +96,7 @@ import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; import android.os.IUserManager; +import android.os.IncidentManager; import android.os.PowerManager; import android.os.Process; import android.os.RecoverySystem; @@ -774,6 +775,13 @@ final class SystemServiceRegistry { return new ContextHubManager(ctx.getOuterContext(), ctx.mMainThread.getHandler().getLooper()); }}); + + registerService(Context.INCIDENT_SERVICE, IncidentManager.class, + new CachedServiceFetcher() { + @Override + public IncidentManager createService(ContextImpl ctx) throws ServiceNotFoundException { + return new IncidentManager(ctx); + }}); } /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b9783aa1600a1..11da8dd054aee 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2747,7 +2747,8 @@ public abstract class Context { //@hide: SOUND_TRIGGER_SERVICE, SHORTCUT_SERVICE, //@hide: CONTEXTHUB_SERVICE, - SYSTEM_HEALTH_SERVICE + SYSTEM_HEALTH_SERVICE, + //@hide: INCIDENT_SERVICE }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3704,6 +3705,12 @@ public abstract class Context { */ public static final String DEVICE_IDENTIFIERS_SERVICE = "device_identifiers"; + /** + * Service to report a system health "incident" + * @hide + */ + public static final String INCIDENT_SERVICE = "incident"; + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl new file mode 100644 index 0000000000000..1a76648c2565e --- /dev/null +++ b/core/java/android/os/IIncidentManager.aidl @@ -0,0 +1,49 @@ +/** + * 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. + */ + +package android.os; + +import android.os.IIncidentReportCompletedListener; +import android.os.IIncidentReportStatusListener; +import android.os.IncidentReportArgs; + +/** + * Binder interface to report system health incidents. + * {@hide} + */ +oneway interface IIncidentManager { + + /** + * Takes a report with the given args, reporting status to the optional listener. + * + * When the report is completed, the system report listener will be notified. + */ + void reportIncident(in IncidentReportArgs args); + + /** + * Takes a report with the given args, reporting status to the optional listener. + * + * When the report is completed, the system report listener will be notified. + */ + void reportIncidentToStream(in IncidentReportArgs args, + @nullable IIncidentReportStatusListener listener, + FileDescriptor stream); + + /** + * Tell the incident daemon that the android system server is up and running. + */ + void systemRunning(); +} diff --git a/core/java/android/os/IIncidentReportCompletedListener.aidl b/core/java/android/os/IIncidentReportCompletedListener.aidl new file mode 100644 index 0000000000000..2d66bf635a2a7 --- /dev/null +++ b/core/java/android/os/IIncidentReportCompletedListener.aidl @@ -0,0 +1,32 @@ +/** + * 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. + */ + +package android.os; + +/** + * Listener for incident report status + * + * {@hide} + */ +oneway interface IIncidentReportCompletedListener { + /** + * Called when there has been an incident report. + * + * The system service implementing this method should delete or move the file + * after it is finished with it. + */ + void onIncidentReport(String filename); +} diff --git a/core/java/android/os/IIncidentReportStatusListener.aidl b/core/java/android/os/IIncidentReportStatusListener.aidl new file mode 100644 index 0000000000000..7be2ac8eabe2a --- /dev/null +++ b/core/java/android/os/IIncidentReportStatusListener.aidl @@ -0,0 +1,34 @@ +/** + * 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. + */ + +package android.os; + +/** + * Listener for incident report status + * + * {@hide} + */ +oneway interface IIncidentReportStatusListener { + const int STATUS_STARTING = 1; + const int STATUS_FINISHED = 2; + + void onReportStarted(); + + void onReportSectionStatus(int section, int status); + + void onReportFinished(); + void onReportFailed(); +} diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java new file mode 100644 index 0000000000000..976d59472e696 --- /dev/null +++ b/core/java/android/os/IncidentManager.java @@ -0,0 +1,114 @@ +/* + * 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. + */ + +package android.os; + +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Context; +import android.os.IIncidentManager; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Slog; + +/** + * Class to take an incident report. + * + * @hide + */ +@SystemApi +@TestApi +public class IncidentManager { + private static final String TAG = "incident"; + + private Context mContext; + + /** + * @hide + */ + public IncidentManager(Context context) { + mContext = context; + } + + /** + * Take an incident report and put it in dropbox. + */ + public void reportIncident(IncidentReportArgs args) { + final IIncidentManager service = IIncidentManager.Stub.asInterface( + ServiceManager.getService("incident")); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } + + try { + service.reportIncident(args); + } catch (RemoteException ex) { + Slog.e(TAG, "reportIncident failed", ex); + } + } + + /** + * Convenience method to trigger an incident report and put it in dropbox. + *

+ * The fields that are reported will be looked up in the system setting named by + * the settingName parameter. The setting must match one of these patterns: + * The string "disabled": The report will not be taken. + * The string "all": The report will taken with all sections. + * The string "none": The report will taken with no sections, but with the header. + * A comma separated list of field numbers: The report will have these fields. + *

+ * The header parameter will be added as a header for the incident report. Fill in a + * {@link android.util.proto.ProtoOutputStream ProtoOutputStream}, and then call the + * {@link android.util.proto.ProtoOutputStream#bytes bytes()} method to retrieve + * the encoded data for the header. + */ + public void reportIncident(String settingName, byte[] headerProto) { + // Sections + String setting = Settings.System.getString(mContext.getContentResolver(), settingName); + IncidentReportArgs args; + try { + args = IncidentReportArgs.parseSetting(setting); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Bad value for incident report setting '" + settingName + "'", ex); + return; + } + if (args == null) { + Slog.i(TAG, "Incident report requested but disabled: " + settingName); + return; + } + + // Header + args.addHeader(headerProto); + + // Look up the service + final IIncidentManager service = IIncidentManager.Stub.asInterface( + ServiceManager.getService("incident")); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } + + // Call the service + Slog.i(TAG, "Taking incident report: " + settingName); + try { + service.reportIncident(args); + } catch (RemoteException ex) { + Slog.e(TAG, "reportIncident failed", ex); + } + } +} + diff --git a/core/java/android/os/IncidentReportArgs.aidl b/core/java/android/os/IncidentReportArgs.aidl new file mode 100644 index 0000000000000..bbddf59d92067 --- /dev/null +++ b/core/java/android/os/IncidentReportArgs.aidl @@ -0,0 +1,20 @@ +/** + * 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. + */ + +package android.os; + +parcelable IncidentReportArgs cpp_header "android/os/IncidentReportArgs.h"; + diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java new file mode 100644 index 0000000000000..ce2ae1071d01e --- /dev/null +++ b/core/java/android/os/IncidentReportArgs.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2005 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. + */ + +package android.os; + +import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.IntArray; + +import java.util.ArrayList; + +/** + * The arguments for an incident report. + * {@hide} + */ +@SystemApi +@TestApi +public final class IncidentReportArgs implements Parcelable { + + private final IntArray mSections = new IntArray(); + private final ArrayList mHeaders = new ArrayList(); + private boolean mAll; + + /** + * Construct an incident report args with no fields. + */ + public IncidentReportArgs() { + } + + /** + * Construct an incdent report args from the given parcel. + */ + public IncidentReportArgs(Parcel in) { + readFromParcel(in); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mAll ? 1 : 0); + + int N = mSections.size(); + out.writeInt(N); + for (int i=0; i CREATOR + = new Parcelable.Creator() { + public IncidentReportArgs createFromParcel(Parcel in) { + return new IncidentReportArgs(in); + } + + public IncidentReportArgs[] newArray(int size) { + return new IncidentReportArgs[size]; + } + }; + + /** + * Print this report as a string. + */ + public String toString() { + final StringBuilder sb = new StringBuilder("Incident("); + if (mAll) { + sb.append("all"); + } else { + final int N = mSections.size(); + if (N > 0) { + sb.append(mSections.get(0)); + } + for (int i=1; i= 0; + } + + public int sectionCount() { + return mSections.size(); + } + + public void addHeader(byte[] header) { + mHeaders.add(header); + } + + /** + * Parses an incident report config as described in the system setting. + * + * @see IncidentManager#reportIncident + */ + public static IncidentReportArgs parseSetting(String setting) + throws IllegalArgumentException { + if (setting == null || setting.length() == 0) { + return null; + } + setting = setting.trim(); + if (setting.length() == 0 || "disabled".equals(setting)) { + return null; + } + + final IncidentReportArgs args = new IncidentReportArgs(); + + if ("all".equals(setting)) { + args.setAll(true); + return args; + } else if ("none".equals(setting)) { + return args; + } + + final String[] splits = setting.split(","); + final int N = splits.length; + for (int i=0; i= accept+reject due to poor + // image acquisition in some cases (too fast, too slow, dirty sensor, etc.) + optional int32 acquire = 3; + + // Total number of lockouts. + optional int32 lockout = 4; +} diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk new file mode 100644 index 0000000000000..439e86deba5db --- /dev/null +++ b/libs/incident/Android.mk @@ -0,0 +1,41 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := libincident + +LOCAL_CFLAGS := \ + -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter + +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + liblog \ + libutils + +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include + +LOCAL_SRC_FILES := \ + ../../core/java/android/os/IIncidentManager.aidl \ + ../../core/java/android/os/IIncidentReportCompletedListener.aidl \ + ../../core/java/android/os/IIncidentReportStatusListener.aidl \ + src/IncidentReportArgs.cpp + +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include + +include $(BUILD_SHARED_LIBRARY) + diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h new file mode 100644 index 0000000000000..956ef6c39b993 --- /dev/null +++ b/libs/incident/include/android/os/IncidentReportArgs.h @@ -0,0 +1,62 @@ +/** + * 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. + */ + +#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_ +#define ANDROID_OS_DUMPSTATE_ARGS_H_ + +#include +#include +#include + +#include +#include + +namespace android { +namespace os { + +using namespace std; + +class IncidentReportArgs : public Parcelable { +public: + IncidentReportArgs(); + explicit IncidentReportArgs(const IncidentReportArgs& that); + virtual ~IncidentReportArgs(); + + virtual status_t writeToParcel(Parcel* out) const; + virtual status_t readFromParcel(const Parcel* in); + + void setAll(bool all); + void addSection(int section); + void addHeader(const vector& header); + + inline bool all() const { return mAll; }; + bool containsSection(int section) const; + + inline const set& sections() const { return mSections; } + inline const vector>& headers() const { return mHeaders; } + + void merge(const IncidentReportArgs& that); + +private: + set mSections; + vector> mHeaders; + bool mAll; +}; + +} +} + +#endif // ANDROID_OS_DUMPSTATE_ARGS_H_ diff --git a/libs/incident/proto/android/privacy.proto b/libs/incident/proto/android/privacy.proto new file mode 100644 index 0000000000000..ae5af0e4afa72 --- /dev/null +++ b/libs/incident/proto/android/privacy.proto @@ -0,0 +1,57 @@ +/* + * 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. + */ + +syntax = "proto2"; + +option java_package = "android"; +option java_multiple_files = true; + +import "google/protobuf/descriptor.proto"; + +package android; + +// TODO: It's better to track this by semantic types and set policy for those. +// Do this for now to bootstrap the tools. +enum Destination { + // Fields or messages annotated with DEST_LOCAL must never be + // extracted from the device automatically. They can be accessed + // by tools on the developer's workstation, and if they are sent + // to another device that must be by the user, with a PII warning. (TBD) + DEST_LOCAL = 0; + + // Fields or messages annotated with DEST_EXPLICIT can be sent + // off the device with an explicit user action. + DEST_EXPLICIT = 1; + + // Fields or messages annotated with DEST_LOCAL can be sent by + // automatic means, without per-sending user consent. The user + // still must have previously accepted a consent to share this + // information. + DEST_AUTOMATIC = 2; + + // There is no more permissive option than DEST_AUTOMATIC. +} + +message PrivacyFlags { + optional Destination dest = 1 [ + default = DEST_LOCAL + ]; +} + +extend google.protobuf.FieldOptions { + // Flags for automatically filtering statistics + optional PrivacyFlags privacy = 102672883; +} diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp new file mode 100644 index 0000000000000..f60490911aed0 --- /dev/null +++ b/libs/incident/src/IncidentReportArgs.cpp @@ -0,0 +1,172 @@ +/** + * 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 "dumpstate" + +#include + +#include + +namespace android { +namespace os { + +IncidentReportArgs::IncidentReportArgs() + :mSections(), + mAll(false) +{ +} + +IncidentReportArgs::IncidentReportArgs(const IncidentReportArgs& that) + :mSections(that.mSections), + mHeaders(that.mHeaders), + mAll(that.mAll) +{ +} + +IncidentReportArgs::~IncidentReportArgs() +{ +} + +status_t +IncidentReportArgs::writeToParcel(Parcel* out) const +{ + status_t err; + + err = out->writeInt32(mAll); + if (err != NO_ERROR) { + return err; + } + + err = out->writeInt32(mSections.size()); + if (err != NO_ERROR) { + return err; + } + + for (set::const_iterator it=mSections.begin(); it!=mSections.end(); it++) { + err = out->writeInt32(*it); + if (err != NO_ERROR) { + return err; + } + } + + err = out->writeInt32(mHeaders.size()); + if (err != NO_ERROR) { + return err; + } + + for (vector>::const_iterator it = mHeaders.begin(); it != mHeaders.end(); it++) { + err = out->writeByteVector(*it); + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + +status_t +IncidentReportArgs::readFromParcel(const Parcel* in) +{ + status_t err; + + int32_t all; + err = in->readInt32(&all); + if (err != NO_ERROR) { + return err; + } + if (all != 0) { + mAll = all; + } + + mSections.clear(); + int32_t sectionCount; + err = in->readInt32(§ionCount); + if (err != NO_ERROR) { + return err; + } + for (int i=0; ireadInt32(§ion); + if (err != NO_ERROR) { + return err; + } + + mSections.insert(section); + } + + int32_t headerCount; + err = in->readInt32(&headerCount); + if (err != NO_ERROR) { + return err; + } + mHeaders.resize(headerCount); + for (int i=0; ireadByteVector(&mHeaders[i]); + if (err != NO_ERROR) { + return err; + } + } + + return OK; +} + +void +IncidentReportArgs::setAll(bool all) +{ + mAll = all; + if (all) { + mSections.clear(); + } +} + +void +IncidentReportArgs::addSection(int section) +{ + if (!mAll) { + mSections.insert(section); + } +} + +void +IncidentReportArgs::addHeader(const vector& header) +{ + mHeaders.push_back(header); +} + +bool +IncidentReportArgs::containsSection(int section) const +{ + return mAll || mSections.find(section) != mSections.end(); +} + +void +IncidentReportArgs::merge(const IncidentReportArgs& that) +{ + if (mAll) { + return; + } else if (that.mAll) { + mAll = true; + mSections.clear(); + } else { + for (set::const_iterator it=that.mSections.begin(); + it!=that.mSections.end(); it++) { + mSections.insert(*it); + } + } +} + +} +} diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 132967c6b26a9..3f0ebf28bbfdf 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -46,7 +46,11 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.service.fingerprint.FingerprintActionStatsProto; +import android.service.fingerprint.FingerprintServiceDumpProto; +import android.service.fingerprint.FingerprintUserStatsProto; import android.util.Slog; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -968,7 +972,11 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe final long ident = Binder.clearCallingIdentity(); try { - dumpInternal(pw); + if (args.length > 0 && "--proto".equals(args[0])) { + dumpProto(fd); + } else { + dumpInternal(pw); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -1027,6 +1035,45 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe pw.println(dump); } + private void dumpProto(FileDescriptor fd) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + for (UserInfo user : UserManager.get(getContext()).getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + final long userToken = proto.start(FingerprintServiceDumpProto.USERS); + + proto.write(FingerprintUserStatsProto.USER_ID, userId); + proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS, + mFingerprintUtils.getFingerprintsForUser(mContext, userId).size()); + + // Normal fingerprint authentications (e.g. lockscreen) + final PerformanceStats normal = mPerformanceMap.get(userId); + if (normal != null) { + final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL); + proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept); + proto.write(FingerprintActionStatsProto.REJECT, normal.reject); + proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire); + proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout); + proto.end(countsToken); + } + + // Statistics about secure fingerprint transactions (e.g. to unlock password + // storage, make secure purchases, etc.) + final PerformanceStats crypto = mPerformanceMap.get(userId); + if (crypto != null) { + final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO); + proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept); + proto.write(FingerprintActionStatsProto.REJECT, crypto.reject); + proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire); + proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout); + proto.end(countsToken); + } + + proto.end(userToken); + } + proto.flush(); + } + @Override public void onStart() { publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6ec25c53726b9..be13499e2c18c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -32,6 +32,7 @@ import android.os.Build; import android.os.Environment; import android.os.FactoryTest; import android.os.FileUtils; +import android.os.IIncidentManager; import android.os.Looper; import android.os.PowerManager; import android.os.RemoteException; @@ -1635,6 +1636,19 @@ public final class SystemServer { } traceEnd(); + traceBeginAndSlog("IncidentDaemonReady"); + try { + // TODO: Switch from checkService to getService once it's always + // in the build and should reliably be there. + final IIncidentManager incident = IIncidentManager.Stub.asInterface( + ServiceManager.checkService("incident")); + if (incident != null) incident.systemRunning(); + } catch (Throwable e) { + reportWtf("Notifying incident daemon running", e); + } + traceEnd(); + + traceEnd(); // PhaseActivityManagerReady } }); diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk new file mode 100644 index 0000000000000..ed89bd6251e34 --- /dev/null +++ b/tools/incident_report/Android.mk @@ -0,0 +1,39 @@ +# +# Copyright (C) 2015 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. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := incident_report + +LOCAL_C_INCLUDES := \ + external/protobuf/src + +LOCAL_SRC_FILES := \ + generic_message.cpp \ + main.cpp \ + printer.cpp + +LOCAL_SHARED_LIBRARIES := \ + libplatformprotos \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + + diff --git a/tools/incident_report/formatter.cpp b/tools/incident_report/formatter.cpp new file mode 100644 index 0000000000000..944348f30022f --- /dev/null +++ b/tools/incident_report/formatter.cpp @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#include "proto_format.h" + +#include + +extern int const PROTO_FORMAT_STRING_POOL_SIZE; +extern int const PROTO_FORMAT_ENUM_LABELS_LENGTH; +extern int const PROTO_FORMAT_MESSAGES_LENGTH; +extern int const PROTO_FORMAT_FIELDS_LENGTH; + +extern char const PROTO_FORMAT_STRING_POOL[]; +extern ProtoFieldFormat const PROTO_FORMAT_FIELDS[]; +extern ProtoEnumLabel const PROTO_FORMAT_ENUM_LABELS[]; +extern ProtoMessageFormat const PROTO_FORMAT_MESSAGES[]; + +static const char* +get_string(int index) +{ + if (index >= 0 && index < PROTO_FORMAT_STRING_POOL_SIZE) { + return PROTO_FORMAT_STRING_POOL + index; + } else { + // These indices all come from within the generated table, so just crash now. + *(int*)NULL = 42; + return NULL; + } +} + +static ProtoMessageFormat const* +get_message(int index) +{ + if (index >= 0 && index < PROTO_FORMAT_MESSAGES_LENGTH) { + return PROTO_FORMAT_MESSAGES + index; + } else { + // These indices all come from within the generated table, so just crash now. + *(int*)NULL = 42; + return NULL; + } +} + +static int +compare_name(const char* full, const char* package, const char* clazz) +{ + int const packageLen = strlen(package); + int cmp = strncmp(full, package, packageLen); + if (cmp == 0) { + cmp = full[packageLen] - '.'; + if (cmp == 0) { + return strcmp(full + packageLen, clazz); + } + } + return cmp; +} + +int +find_message_index(const char* name) +{ + size_t low = 0; + size_t high = PROTO_FORMAT_FIELDS_LENGTH - 1; + + while (low <= high) { + size_t mid = (low + high) >> 1; + ProtoMessageFormat const* msg = get_message(mid); + + int cmp = compare_name(name, get_string(msg->package_name), get_string(msg->package_name)); + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -1; +} diff --git a/tools/incident_report/generic_message.cpp b/tools/incident_report/generic_message.cpp new file mode 100644 index 0000000000000..84d9d7cfe7c27 --- /dev/null +++ b/tools/incident_report/generic_message.cpp @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#include "generic_message.h" + +GenericMessage::GenericMessage() +{ +} + +GenericMessage::~GenericMessage() +{ +} + +void +GenericMessage::addInt32(int32_t fieldId, uint32_t value) +{ + Node node; + node.type = TYPE_VALUE32; + node.value32 = value; + mNodes.insert(pair(fieldId, node)); +} + +void +GenericMessage::addInt64(int32_t fieldId, uint64_t value) +{ + Node node; + node.type = TYPE_VALUE64; + node.value64 = value; + mNodes.insert(pair(fieldId, node)); +} + +GenericMessage* +GenericMessage::addMessage(int32_t fieldId) +{ + GenericMessage* result = new GenericMessage(); + Node node; + node.type = TYPE_MESSAGE; + node.message = result; + mNodes.insert(pair(fieldId, node)); + return result; +} + +void +GenericMessage::addString(int32_t fieldId, const string& value) +{ + Node node; + node.type = TYPE_STRING; + node.str = new string(value); + mNodes.insert(pair(fieldId, node)); +} + +GenericMessage::const_iterator_pair +GenericMessage::find(int fieldId) const +{ + return mNodes.equal_range(fieldId); +} + diff --git a/tools/incident_report/generic_message.h b/tools/incident_report/generic_message.h new file mode 100644 index 0000000000000..df3f7b22dfc77 --- /dev/null +++ b/tools/incident_report/generic_message.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef GENERIC_MESSAGE_H +#define GENERIC_MESSAGE_H + +#include +#include + +using namespace std; + +/** + * Class to represent a protobuf Message, where we don't actually + * know what any of the fields are, just their type codes. In other + * words, this loslessly stores a parsed protobuf object without + * having the .proto file that generated it. + */ +class GenericMessage +{ +public: + GenericMessage(); + ~GenericMessage(); + + enum { + TYPE_VALUE32, + TYPE_VALUE64, + TYPE_MESSAGE, + TYPE_STRING, + TYPE_DATA + }; + + struct Node { + uint32_t type; + union { + uint32_t value32; + uint64_t value64; + GenericMessage* message; + string* str; + string* data; + }; + }; + + void addInt32(int32_t fieldId, uint32_t value); + void addInt64(int32_t fieldId, uint64_t value); + GenericMessage* addMessage(int32_t fieldId); + void addString(int32_t fieldId, const string& value); + + typedef multimap::const_iterator const_iterator; + typedef pair const_iterator_pair; + + const_iterator_pair find(int fieldId) const; + +private: + multimap mNodes; +}; + +#endif // GENERIC_MESSAGE_H + diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp new file mode 100644 index 0000000000000..a814847e41f7f --- /dev/null +++ b/tools/incident_report/main.cpp @@ -0,0 +1,576 @@ +/* + * 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. + */ + +#include "generic_message.h" +#include "printer.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace android::os; +using namespace google::protobuf; +using namespace google::protobuf::io; +using namespace google::protobuf::internal; + +static bool read_message(CodedInputStream* in, Descriptor const* descriptor, + GenericMessage* message); +static void print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message); + +// ================================================================================ +static bool +read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* descriptor, + GenericMessage* message) +{ + uint32 size; + if (!in->ReadVarint32(&size)) { + return false; + } + + FieldDescriptor const* field = descriptor->FindFieldByNumber(fieldId); + if (field != NULL) { + int type = field->type(); + if (type == FieldDescriptor::TYPE_MESSAGE) { + GenericMessage* child = message->addMessage(fieldId); + + CodedInputStream::Limit limit = in->PushLimit(size); + bool rv = read_message(in, field->message_type(), child); + in->PopLimit(limit); + return rv; + } else if (type == FieldDescriptor::TYPE_STRING) { + // TODO: do a version of readstring that just pumps the data + // rather than allocating a string which we don't care about. + string str; + if (in->ReadString(&str, size)) { + message->addString(fieldId, str); + return true; + } else { + return false; + } + } else if (type == FieldDescriptor::TYPE_BYTES) { + // TODO: Save bytes field. + return in->Skip(size); + } + } + return in->Skip(size); +} + +// ================================================================================ +static bool +read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message) +{ + uint32 value32; + uint64 value64; + + while (true) { + uint32 tag = in->ReadTag(); + if (tag == 0) { + return true; + } + int fieldId = WireFormatLite::GetTagFieldNumber(tag); + switch (WireFormatLite::GetTagWireType(tag)) { + case WireFormatLite::WIRETYPE_VARINT: + if (in->ReadVarint64(&value64)) { + message->addInt64(fieldId, value64); + break; + } else { + return false; + } + case WireFormatLite::WIRETYPE_FIXED64: + if (in->ReadLittleEndian64(&value64)) { + message->addInt64(fieldId, value64); + break; + } else { + return false; + } + case WireFormatLite::WIRETYPE_LENGTH_DELIMITED: + if (!read_length_delimited(in, fieldId, descriptor, message)) { + return false; + } + break; + case WireFormatLite::WIRETYPE_FIXED32: + if (in->ReadLittleEndian32(&value32)) { + message->addInt32(fieldId, value32); + break; + } else { + return false; + } + default: + fprintf(stderr, "bad tag: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); + return false; + } + } +} + +// ================================================================================ +static void +print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node) +{ + uint32_t val32; + FieldDescriptor::Type type = field->type(); + + switch (node.type) { + case GenericMessage::TYPE_VALUE32: + switch (type) { + case FieldDescriptor::TYPE_FIXED32: + out->printf("%u", node.value32); + break; + case FieldDescriptor::TYPE_SFIXED32: + out->printf("%d", node.value32); + break; + case FieldDescriptor::TYPE_FLOAT: + out->printf("%f", *(float*)&node.value32); + break; + default: + out->printf("(unexpected value %d (0x%x)", node.value32, node.value32); + break; + } + break; + case GenericMessage::TYPE_VALUE64: + switch (type) { + case FieldDescriptor::TYPE_FIXED64: + case FieldDescriptor::TYPE_SFIXED64: + case FieldDescriptor::TYPE_DOUBLE: + out->printf("%f", *(double*)&node.value64); + break; + case FieldDescriptor::TYPE_SINT32: + case FieldDescriptor::TYPE_INT32: + val32 = (uint32_t)node.value32; + out->printf("%d", val32); + break; + case FieldDescriptor::TYPE_INT64: + case FieldDescriptor::TYPE_UINT32: + val32 = (uint32_t)node.value32; + out->printf("%u", val32); + break; + case FieldDescriptor::TYPE_UINT64: + case FieldDescriptor::TYPE_SINT64: + case FieldDescriptor::TYPE_BOOL: + if (node.value64) { + out->printf("true"); + } else { + out->printf("false"); + } + break; + case FieldDescriptor::TYPE_ENUM: + default: + out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64); + break; + } + break; + case GenericMessage::TYPE_MESSAGE: + print_message(out, field->message_type(), node.message); + break; + case GenericMessage::TYPE_STRING: + // TODO: custom format for multi-line strings. + out->printf("%s", node.str->c_str()); + break; + case GenericMessage::TYPE_DATA: + out->printf(""); + break; + } +} + +static void +print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message) +{ + out->printf("%s {\n", descriptor->name().c_str()); + out->indent(); + + int const N = descriptor->field_count(); + for (int i=0; ifield(i); + + int fieldId = field->number(); + bool repeated = field->label() == FieldDescriptor::LABEL_REPEATED; + FieldDescriptor::Type type = field->type(); + GenericMessage::const_iterator_pair it = message->find(fieldId); + + out->printf("%s=", field->name().c_str()); + if (repeated) { + if (it.first != it.second) { + out->printf("["); + if (type == FieldDescriptor::TYPE_MESSAGE + || type == FieldDescriptor::TYPE_STRING + || type == FieldDescriptor::TYPE_BYTES) { + out->printf("\n"); + } + out->indent(); + + for (GenericMessage::const_iterator_pair it = message->find(fieldId); + it.first != it.second; it.first++) { + print_value(out, field, it.first->second); + if (type == FieldDescriptor::TYPE_MESSAGE + || type == FieldDescriptor::TYPE_STRING + || type == FieldDescriptor::TYPE_BYTES) { + out->printf("\n"); + } + } + + out->dedent(); + out->printf("]"); + } else { + out->printf("[]"); + } + } else { + if (it.first != it.second) { + print_value(out, field, it.first->second); + } else { + switch (type) { + case FieldDescriptor::TYPE_BOOL: + out->printf("false"); + break; + case FieldDescriptor::TYPE_STRING: + case FieldDescriptor::TYPE_MESSAGE: + out->printf(""); + break; + case FieldDescriptor::TYPE_ENUM: + out->printf("%s", field->default_value_enum()->name().c_str()); + break; + default: + out->printf("0"); + break; + } + } + } + out->printf("\n"); + } + out->dedent(); + out->printf("}"); +} + +// ================================================================================ +static uint8_t* +write_raw_varint(uint8_t* buf, uint32_t val) +{ + uint8_t* p = buf; + while (true) { + if ((val & ~0x7F) == 0) { + *p++ = (uint8_t)val; + return p; + } else { + *p++ = (uint8_t)((val & 0x7F) | 0x80); + val >>= 7; + } + } +} + +static int +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 0; +} + +static int +adb_incident_workaround(const char* adbSerial, const vector& sections) +{ + const int maxAllowedSize = 20 * 1024 * 1024; // 20MB + uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize); + + for (vector::const_iterator it=sections.begin(); it!=sections.end(); it++) { + Descriptor const* descriptor = IncidentProto::descriptor(); + FieldDescriptor const* field; + + // Get the name and field id. + string name = *it; + char* end; + int id = strtol(name.c_str(), &end, 0); + if (*end == '\0') { + // If it's an id, find out the string. + field = descriptor->FindFieldByNumber(id); + if (field == NULL) { + fprintf(stderr, "Unable to find field number: %d\n", id); + return 1; + } + name = field->name(); + } else { + // If it's a string, find out the id. + field = descriptor->FindFieldByName(name); + if (field == NULL) { + fprintf(stderr, "Unable to find field: %s\n", name.c_str()); + return 1; + } + id = field->number(); + } + + int pfd[2]; + if (pipe(pfd) != 0) { + fprintf(stderr, "pipe failed: %s\n", strerror(errno)); + return 1; + } + + pid_t pid = fork(); + if (pid == -1) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + return 1; + } else if (pid == 0) { + // child + dup2(pfd[1], STDOUT_FILENO); + close(pfd[0]); + close(pfd[1]); + + char const** args = (char const**)malloc(sizeof(char*) * 8); + int argpos = 0; + args[argpos++] = "adb"; + if (adbSerial != NULL) { + args[argpos++] = "-s"; + args[argpos++] = adbSerial; + } + args[argpos++] = "shell"; + args[argpos++] = "dumpsys"; + args[argpos++] = name.c_str(); + args[argpos++] = "--proto"; + args[argpos++] = NULL; + execvp(args[0], (char*const*)args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + return 1; + } else { + // parent + close(pfd[1]); + + size_t size = 0; + while (size < maxAllowedSize) { + ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size); + if (amt == 0) { + break; + } else if (amt == -1) { + fprintf(stderr, "read error: %s\n", strerror(errno)); + return 1; + } + size += amt; + } + + int status; + do { + waitpid(pid, &status, 0); + } while (!WIFEXITED(status)); + if (WEXITSTATUS(status) != 0) { + return WEXITSTATUS(status); + } + + if (size > 0) { + uint8_t header[20]; + uint8_t* p = write_raw_varint(header, (id << 3) | 2); + p = write_raw_varint(p, size); + int err = write_all(STDOUT_FILENO, header, p-header); + if (err != 0) { + fprintf(stderr, "write error: %s\n", strerror(err)); + return 1; + } + err = write_all(STDOUT_FILENO, buffer, size); + if (err != 0) { + fprintf(stderr, "write error: %s\n", strerror(err)); + return 1; + } + } + + close(pfd[0]); + } + } + + return 0; +} + +// ================================================================================ +static void +usage(FILE* out) +{ + fprintf(out, "usage: incident_report -i INPUT [-o OUTPUT]\n"); + fprintf(out, "\n"); + fprintf(out, "Pretty-prints an incident report protobuf file.\n"); + fprintf(out, " -i INPUT the input file. INPUT may be '-' to use stdin\n"); + fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: incident_report [-o OUTPUT] [-t|b] [-s SERIAL] [SECTION...]\n"); + fprintf(out, "\n"); + fprintf(out, "Take an incident report over adb (which must be in the PATH).\n"); + fprintf(out, " -b output the incident report raw protobuf format\n"); + fprintf(out, " -o OUTPUT the output file. OUTPUT may be '-' or omitted to use stdout\n"); + fprintf(out, " -s SERIAL sent to adb to choose which device, instead of $ANDROID_SERIAL\n"); + fprintf(out, " -t output the incident report in pretty-printed text format\n"); + fprintf(out, "\n"); + fprintf(out, " SECTION which bugreport sections to print, either the int code of the\n"); + fprintf(out, " section in the Incident proto or the field name. If ommited,\n"); + fprintf(out, " the report will contain all fields\n"); + fprintf(out, "\n"); +} + +int +main(int argc, char** argv) +{ + enum { OUTPUT_TEXT, OUTPUT_PROTO } outputFormat = OUTPUT_TEXT; + const char* inFilename = NULL; + const char* outFilename = NULL; + const char* adbSerial = NULL; + bool adbIncidentWorkaround = true; + pid_t childPid = -1; + vector sections; + + int opt; + while ((opt = getopt(argc, argv, "bhi:o:s:tw")) != -1) { + switch (opt) { + case 'b': + outputFormat = OUTPUT_PROTO; + break; + case 'i': + inFilename = optarg; + break; + case 'o': + outFilename = optarg; + break; + case 's': + adbSerial = optarg; + break; + case 't': + outputFormat = OUTPUT_TEXT; + break; + case 'h': + usage(stdout); + return 0; + case 'w': + adbIncidentWorkaround = false; + break; + default: + usage(stderr); + return 1; + } + } + + while (optind < argc) { + sections.push_back(argv[optind++]); + } + + int inFd; + if (inFilename != NULL) { + // translate-only mode - oepn the file or use stdin. + if (strcmp("-", inFilename) == 0) { + inFd = STDIN_FILENO; + } else { + inFd = open(inFilename, O_RDONLY | O_CLOEXEC); + if (inFd < 0) { + fprintf(stderr, "unable to open file for read (%s): %s\n", strerror(errno), + inFilename); + return 1; + } + } + } else { + // pipe mode - run adb shell incident ... + int pfd[2]; + if (pipe(pfd) != 0) { + fprintf(stderr, "pipe failed: %s\n", strerror(errno)); + return 1; + } + + childPid = fork(); + if (childPid == -1) { + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + return 1; + } else if (childPid == 0) { + dup2(pfd[1], STDOUT_FILENO); + close(pfd[0]); + close(pfd[1]); + // child + if (adbIncidentWorkaround) { + // TODO: Until the device side incident command is checked in, + // the incident_report builds the outer Incident proto by hand + // from individual adb shell dumpsys --proto calls, + // with a maximum allowed output size. + return adb_incident_workaround(adbSerial, sections); + } + + // TODO: This is what the real implementation will be... + char const** args = (char const**)malloc(sizeof(char*) * (6 + sections.size())); + int argpos = 0; + args[argpos++] = "adb"; + if (adbSerial != NULL) { + args[argpos++] = "-s"; + args[argpos++] = adbSerial; + } + args[argpos++] = "shell"; + args[argpos++] = "incident"; + for (vector::const_iterator it=sections.begin(); it!=sections.end(); it++) { + args[argpos++] = it->c_str(); + } + args[argpos++] = NULL; + execvp(args[0], (char*const*)args); + fprintf(stderr, "execvp failed: %s\n", strerror(errno)); + return 0; + } else { + // parent + inFd = pfd[0]; + close(pfd[1]); + } + } + + int outFd; + if (outFilename == NULL || strcmp("-", outFilename) == 0) { + outFd = STDOUT_FILENO; + } else { + outFd = open(outFilename, O_CREAT | O_RDWR, 0666); + if (outFd < 0) { + fprintf(stderr, "unable to open file for write: %s\n", outFilename); + return 1; + } + } + + GenericMessage message; + + Descriptor const* descriptor = IncidentProto::descriptor(); + FileInputStream infile(inFd); + CodedInputStream in(&infile); + + if (!read_message(&in, descriptor, &message)) { + fprintf(stderr, "unable to read incident\n"); + return 1; + } + + Out out(outFd); + + print_message(&out, descriptor, &message); + out.printf("\n"); + + if (childPid != -1) { + int status; + do { + waitpid(childPid, &status, 0); + } while (!WIFEXITED(status)); + if (WEXITSTATUS(status) != 0) { + return WEXITSTATUS(status); + } + } + + return 0; +} diff --git a/tools/incident_report/printer.cpp b/tools/incident_report/printer.cpp new file mode 100644 index 0000000000000..1ab6bd87913ec --- /dev/null +++ b/tools/incident_report/printer.cpp @@ -0,0 +1,127 @@ +/* + * 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. + */ + +#include "printer.h" + +#include +#include +#include + +#define INITIAL_BUF_SIZE (16*1024) + +char const* SPACES = " "; +const int SPACE_COUNT = strlen(SPACES); + +Out::Out(int fd) + :mOut(fd == STDOUT_FILENO ? stdout : fdopen(fd, "w")), + mBufSize(INITIAL_BUF_SIZE), + mBuf((char*)malloc(INITIAL_BUF_SIZE)), + mIndent(0), + mPendingIndent(false) +{ +} + +Out::~Out() +{ + fclose(mOut); +} + +int +Out::reallocate(int size) +{ + if (size > mBufSize) { + char* p = (char*)malloc(size); + if (p != NULL) { + free(mBuf); + mBufSize = size; + mBuf = p; + return size; + } + } + return mBufSize; +} + +void +Out::printf(const char* format, ...) +{ + if (mPendingIndent) { + print_indent(); + mPendingIndent = false; + } + + int len; + + va_list args; + va_start(args, format); + + len = vsnprintf(mBuf, mBufSize, format, args); + bool truncated = (len >= mBufSize) && (reallocate(len) < len); + + len = vsnprintf(mBuf, mBufSize, format, args); + va_end(args); + + if (len > 0) { + if (mIndent == 0) { + fwrite(mBuf, len, 1, mOut); + } else { + char* last = mBuf; + char* p; + do { + p = strchr(last, '\n'); + int size = p != NULL ? p - last + 1 : strlen(last); + fwrite(last, size, 1, mOut); + if (p != NULL) { + if (p[1] == '\0') { + mPendingIndent = true; + } else { + print_indent(); + } + } + last = p+1; + } while (p != NULL); + } + } +} + +void +Out::indent() +{ + mPendingIndent = true; + mIndent += 2; +} + +void +Out::dedent() +{ + if (mIndent > 0) { + mIndent -= 2; + } +} + +void +Out::print_indent() +{ +#if 0 + fprintf(mOut, "[%d]", mIndent); +#else + int indent = mIndent; + while (indent > SPACE_COUNT) { + fwrite(SPACES, SPACE_COUNT, 1, mOut); + indent -= SPACE_COUNT; + } + fwrite(SPACES + SPACE_COUNT - indent, indent, 1, mOut); +#endif +} diff --git a/tools/incident_report/printer.h b/tools/incident_report/printer.h new file mode 100644 index 0000000000000..ed93fa19542c9 --- /dev/null +++ b/tools/incident_report/printer.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef PRINTER_H +#define PRINTER_H + +#include + +class Out +{ +public: + Out(int fd); + ~Out(); + + void printf(const char* format, ...); + + void indent(); + void dedent(); + +private: + FILE* mOut; + int mBufSize; + char* mBuf; + int mIndent; + bool mPendingIndent; + + int reallocate(int size); + void print_indent(); +}; + +#endif // PRINTER_H diff --git a/tools/incident_section_gen/Android.mk b/tools/incident_section_gen/Android.mk new file mode 100644 index 0000000000000..acf3f8327b5c8 --- /dev/null +++ b/tools/incident_section_gen/Android.mk @@ -0,0 +1,35 @@ +# +# Copyright (C) 2015 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. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := incident-section-gen +LOCAL_CFLAGS += -g -O0 +LOCAL_C_INCLUDES := \ + external/protobuf/src +LOCAL_SRC_FILES := \ + main.cpp +LOCAL_LDFLAGS := -ldl +LOCAL_SHARED_LIBRARIES := \ + libplatformprotos \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp new file mode 100644 index 0000000000000..d0048105d9fe9 --- /dev/null +++ b/tools/incident_section_gen/main.cpp @@ -0,0 +1,67 @@ +/* + * 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. + */ + + +#include + + +#include + +using namespace android::os; +using namespace google::protobuf; +using namespace google::protobuf::io; +using namespace google::protobuf::internal; +using namespace std; + +int +main(int, const char**) +{ + map sections; + int N; + + printf("// Auto generated file. Do not modify\n"); + printf("\n"); + printf("#include \"incident_sections.h\"\n"); + printf("\n"); + + Descriptor const* descriptor = IncidentProto::descriptor(); + N = descriptor->field_count(); + for (int i=0; ifield(i); + if (field->type() == FieldDescriptor::TYPE_MESSAGE) { + sections[field->name()] = field; + } + } + + printf("IncidentSection const INCIDENT_SECTIONS[] = {\n"); + N = sections.size(); + int i = 0; + for (map::const_iterator it = sections.begin(); + it != sections.end(); it++, i++) { + const FieldDescriptor* field = it->second; + printf(" { %d, \"%s\" }", field->number(), field->name().c_str()); + if (i != N-1) { + printf(",\n"); + } else { + printf("\n"); + } + } + printf("};\n"); + + printf("const int INCIDENT_SECTION_COUNT = %d;\n", N); + + return 0; +}