diff options
Diffstat (limited to 'cmds/incidentd/src')
-rw-r--r-- | cmds/incidentd/src/FdBuffer.cpp | 186 | ||||
-rw-r--r-- | cmds/incidentd/src/FdBuffer.h | 37 | ||||
-rw-r--r-- | cmds/incidentd/src/IncidentService.cpp | 44 | ||||
-rw-r--r-- | cmds/incidentd/src/Privacy.cpp | 83 | ||||
-rw-r--r-- | cmds/incidentd/src/Privacy.h | 83 | ||||
-rw-r--r-- | cmds/incidentd/src/PrivacyBuffer.cpp | 162 | ||||
-rw-r--r-- | cmds/incidentd/src/PrivacyBuffer.h | 71 | ||||
-rw-r--r-- | cmds/incidentd/src/Reporter.cpp | 159 | ||||
-rw-r--r-- | cmds/incidentd/src/Reporter.h | 20 | ||||
-rw-r--r-- | cmds/incidentd/src/Section.cpp | 567 | ||||
-rw-r--r-- | cmds/incidentd/src/Section.h | 53 | ||||
-rw-r--r-- | cmds/incidentd/src/io_util.cpp | 46 | ||||
-rw-r--r-- | cmds/incidentd/src/io_util.h (renamed from cmds/incidentd/src/section_list.cpp) | 32 | ||||
-rw-r--r-- | cmds/incidentd/src/protobuf.cpp | 41 | ||||
-rw-r--r-- | cmds/incidentd/src/protobuf.h | 40 | ||||
-rw-r--r-- | cmds/incidentd/src/report_directory.cpp | 28 | ||||
-rw-r--r-- | cmds/incidentd/src/section_list.h | 12 |
17 files changed, 1350 insertions, 314 deletions
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index 527d7eef3a96..0fff4e6dc4a0 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -24,16 +24,16 @@ #include <fcntl.h> #include <poll.h> #include <unistd.h> +#include <wait.h> -const ssize_t BUFFER_SIZE = 16 * 1024; +const bool DEBUG = false; +const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max - FdBuffer::FdBuffer() - :mBuffers(), + :mBuffer(BUFFER_SIZE), mStartTime(-1), mFinishTime(-1), - mCurrentWritten(-1), mTimedOut(false), mTruncated(false) { @@ -41,11 +41,6 @@ FdBuffer::FdBuffer() FdBuffer::~FdBuffer() { - const int N = mBuffers.size(); - for (int i=0; i<N; i++) { - uint8_t* buf = mBuffers[i]; - free(buf); - } } status_t @@ -59,71 +54,192 @@ FdBuffer::read(int fd, int64_t timeout) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - uint8_t* buf = NULL; while (true) { - if (mCurrentWritten >= 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; + if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + mTruncated = true; + break; } + if (mBuffer.writeBuffer() == NULL) return NO_MEMORY; int64_t remainingTime = (mStartTime + timeout) - uptimeMillis(); if (remainingTime <= 0) { + if (DEBUG) ALOGD("timed out due to long read"); mTimedOut = true; break; } int count = poll(&pfds, 1, remainingTime); if (count == 0) { + if (DEBUG) ALOGD("timed out due to block calling poll"); mTimedOut = true; break; } else if (count < 0) { + if (DEBUG) ALOGD("poll failed: %s", strerror(errno)); return -errno; } else { if ((pfds.revents & POLLERR) != 0) { + if (DEBUG) ALOGD("return event has error %s", strerror(errno)); return errno != 0 ? -errno : UNKNOWN_ERROR; } else { - ssize_t amt = ::read(fd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten); + ssize_t amt = ::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite()); if (amt < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { continue; } else { + if (DEBUG) ALOGD("Fail to read %d: %s", fd, strerror(errno)); return -errno; } } else if (amt == 0) { break; } - mCurrentWritten += amt; + mBuffer.wp()->move(amt); } } } - mFinishTime = uptimeMillis(); return NO_ERROR; } -size_t -FdBuffer::size() -{ - return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten; -} - status_t -FdBuffer::write(ReportRequestSet* reporter) +FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs, const bool isSysfs) { - const int N = mBuffers.size() - 1; - for (int i=0; i<N; i++) { - reporter->write(mBuffers[i], BUFFER_SIZE); + struct pollfd pfds[] = { + { .fd = fd, .events = POLLIN }, + { .fd = toFd, .events = POLLOUT }, + { .fd = fromFd, .events = POLLIN }, + }; + + mStartTime = uptimeMillis(); + + // mark all fds non blocking + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + fcntl(toFd, F_SETFL, fcntl(toFd, F_GETFL, 0) | O_NONBLOCK); + fcntl(fromFd, F_SETFL, fcntl(fromFd, F_GETFL, 0) | O_NONBLOCK); + + // A circular buffer holds data read from fd and writes to parsing process + uint8_t cirBuf[BUFFER_SIZE]; + size_t cirSize = 0; + int rpos = 0, wpos = 0; + + // This is the buffer used to store processed data + while (true) { + if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + mTruncated = true; + break; + } + if (mBuffer.writeBuffer() == NULL) return NO_MEMORY; + + int64_t remainingTime = (mStartTime + timeoutMs) - uptimeMillis(); + if (remainingTime <= 0) { + if (DEBUG) ALOGD("timed out due to long read"); + mTimedOut = true; + break; + } + + // wait for any pfds to be ready to perform IO + int count = poll(pfds, 3, remainingTime); + if (count == 0) { + if (DEBUG) ALOGD("timed out due to block calling poll"); + mTimedOut = true; + break; + } else if (count < 0) { + if (DEBUG) ALOGD("Fail to poll: %s", strerror(errno)); + return -errno; + } + + // make sure no errors occur on any fds + for (int i = 0; i < 3; ++i) { + if ((pfds[i].revents & POLLERR) != 0) { + if (i == 0 && isSysfs) { + if (DEBUG) ALOGD("fd %d is sysfs, ignore its POLLERR return value", fd); + continue; + } + if (DEBUG) ALOGD("fd[%d]=%d returns error events: %s", i, fd, strerror(errno)); + return errno != 0 ? -errno : UNKNOWN_ERROR; + } + } + + // read from fd + if (cirSize != BUFFER_SIZE && pfds[0].fd != -1) { + ssize_t amt; + if (rpos >= wpos) { + amt = ::read(fd, cirBuf + rpos, BUFFER_SIZE - rpos); + } else { + amt = ::read(fd, cirBuf + rpos, wpos - rpos); + } + if (amt < 0) { + if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { + if (DEBUG) ALOGD("Fail to read fd %d: %s", fd, strerror(errno)); + return -errno; + } // otherwise just continue + } else if (amt == 0) { // reach EOF so don't have to poll pfds[0]. + ::close(pfds[0].fd); + pfds[0].fd = -1; + } else { + rpos += amt; + cirSize += amt; + } + } + + // write to parsing process + if (cirSize > 0 && pfds[1].fd != -1) { + ssize_t amt; + if (rpos > wpos) { + amt = ::write(toFd, cirBuf + wpos, rpos - wpos); + } else { + amt = ::write(toFd, cirBuf + wpos, BUFFER_SIZE - wpos); + } + if (amt < 0) { + if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { + if (DEBUG) ALOGD("Fail to write toFd %d: %s", toFd, strerror(errno)); + return -errno; + } // otherwise just continue + } else { + wpos += amt; + cirSize -= amt; + } + } + + // if buffer is empty and fd is closed, close write fd. + if (cirSize == 0 && pfds[0].fd == -1 && pfds[1].fd != -1) { + ::close(pfds[1].fd); + pfds[1].fd = -1; + } + + // circular buffer, reset rpos and wpos + if (rpos >= BUFFER_SIZE) { + rpos = 0; + } + if (wpos >= BUFFER_SIZE) { + wpos = 0; + } + + // read from parsing process + ssize_t amt = ::read(fromFd, mBuffer.writeBuffer(), mBuffer.currentToWrite()); + if (amt < 0) { + if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { + if (DEBUG) ALOGD("Fail to read fromFd %d: %s", fromFd, strerror(errno)); + return -errno; + } // otherwise just continue + } else if (amt == 0) { + break; + } else { + mBuffer.wp()->move(amt); + } } - reporter->write(mBuffers[N], mCurrentWritten); + + mFinishTime = uptimeMillis(); return NO_ERROR; } +size_t +FdBuffer::size() const +{ + return mBuffer.size(); +} +EncodedBuffer::iterator +FdBuffer::data() const +{ + return mBuffer.begin(); +} diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h index e12374f21558..48dc855e71b2 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -17,14 +17,11 @@ #ifndef FD_BUFFER_H #define FD_BUFFER_H -#include "Reporter.h" - +#include <android/util/EncodedBuffer.h> #include <utils/Errors.h> -#include <set> -#include <vector> - using namespace android; +using namespace android::util; using namespace std; /** @@ -44,9 +41,21 @@ public: status_t read(int fd, int64_t timeoutMs); /** + * Read processed results by streaming data to a parsing process, e.g. incident helper. + * The parsing process provides IO fds which are 'toFd' and 'fromFd'. The function + * reads original data in 'fd' and writes to parsing process through 'toFd', then it reads + * and stores the processed data from 'fromFd' in memory for later usage. + * This function behaves in a streaming fashion in order to save memory usage. + * Returns NO_ERROR if there were no errors or if we timed out. + * + * Poll will return POLLERR if fd is from sysfs, handle this edge case. + */ + status_t readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs, const bool isSysfs=false); + + /** * Whether we timed out. */ - bool timedOut() { return mTimedOut; } + bool timedOut() const { return mTimedOut; } /** * If more than 4 MB is read, we truncate the data and return success. @@ -56,31 +65,29 @@ public: * 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; } + bool truncated() const { return mTruncated; } /** * How much data was read. */ - size_t size(); + size_t size() const; /** - * Write the data that we recorded to the fd given. + * How long the read took in milliseconds. */ - status_t write(ReportRequestSet* requests); + int64_t durationMs() const { return mFinishTime - mStartTime; } /** - * How long the read took in milliseconds. + * Reader API for data stored in FdBuffer */ - int64_t durationMs() { return mFinishTime - mStartTime; } + EncodedBuffer::iterator data() const; private: - vector<uint8_t*> mBuffers; + EncodedBuffer mBuffer; 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 index 7c6789e6e5ba..654036ec6ab7 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -43,24 +43,49 @@ String16 const DUMP_PERMISSION("android.permission.DUMP"); String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); static Status -checkIncidentPermissions() +checkIncidentPermissions(const IncidentReportArgs& args) { + uid_t callingUid = IPCThreadState::self()->getCallingUid(); + pid_t callingPid = IPCThreadState::self()->getCallingPid(); + if (callingUid == AID_ROOT || callingUid == AID_SHELL) { + // root doesn't have permission.DUMP if don't do this! + return Status::ok(); + } + + // checking calling permission. if (!checkCallingPermission(DUMP_PERMISSION)) { ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP", - IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid()); + callingPid, callingUid); 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()); + callingPid, callingUid); return Status::fromExceptionCode(Status::EX_SECURITY, "Calling process does not have permission: android.permission.USAGE_STATS"); } + + // checking calling request uid permission. + switch (args.dest()) { + case DEST_LOCAL: + if (callingUid != AID_SHELL && callingUid != AID_ROOT) { + ALOGW("Calling pid %d and uid %d does not have permission to get local data.", + callingPid, callingUid); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission to get local data."); + } + case DEST_EXPLICIT: + if (callingUid != AID_SHELL && callingUid != AID_ROOT && + callingUid != AID_STATSD && callingUid != AID_SYSTEM) { + ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.", + callingPid, callingUid); + return Status::fromExceptionCode(Status::EX_SECURITY, + "Calling process does not have permission to get explicit data."); + } + } return Status::ok(); } - - // ================================================================================ ReportRequestQueue::ReportRequestQueue() { @@ -71,7 +96,7 @@ ReportRequestQueue::~ReportRequestQueue() } void -ReportRequestQueue::addRequest(const sp<ReportRequest>& request) +ReportRequestQueue::addRequest(const sp<ReportRequest>& request) { unique_lock<mutex> lock(mLock); mQueue.push_back(request); @@ -153,7 +178,6 @@ ReportHandler::run_report() break; } reporter->batch.add(request); - reporter->args.merge(request->args); } // Take the report, which might take a while. More requests might queue @@ -197,7 +221,7 @@ IncidentService::reportIncident(const IncidentReportArgs& args) { ALOGI("reportIncident"); - Status status = checkIncidentPermissions(); + Status status = checkIncidentPermissions(args); if (!status.isOk()) { return status; } @@ -213,7 +237,7 @@ IncidentService::reportIncidentToStream(const IncidentReportArgs& args, { ALOGI("reportIncidentToStream"); - Status status = checkIncidentPermissions(); + Status status = checkIncidentPermissions(args); if (!status.isOk()) { return status; } @@ -235,7 +259,7 @@ IncidentService::systemRunning() 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(); diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp new file mode 100644 index 000000000000..3f0e331c8b55 --- /dev/null +++ b/cmds/incidentd/src/Privacy.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 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 "Privacy.h" + +#include <android/os/IncidentReportArgs.h> +#include <stdlib.h> + +uint64_t encode_field_id(const Privacy* p) { return (uint64_t)p->type << 32 | p->field_id; } + +const Privacy* lookup(const Privacy* p, uint32_t fieldId) +{ + if (p->children == NULL) return NULL; + for (int i=0; p->children[i] != NULL; i++) { // NULL-terminated. + if (p->children[i]->field_id == fieldId) return p->children[i]; + // Incident section gen tool guarantees field ids in ascending order. + if (p->children[i]->field_id > fieldId) return NULL; + } + return NULL; +} + +static bool allowDest(const uint8_t dest, const uint8_t policy) +{ + switch (policy) { + case android::os::DEST_LOCAL: + return dest == android::os::DEST_LOCAL; + case android::os::DEST_EXPLICIT: + case DEST_UNSET: + return dest == android::os::DEST_LOCAL || + dest == android::os::DEST_EXPLICIT || + dest == DEST_UNSET; + case android::os::DEST_AUTOMATIC: + return true; + default: + return false; + } +} + +bool +PrivacySpec::operator<(const PrivacySpec& other) const +{ + return dest < other.dest; +} + +bool +PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) const +{ + uint8_t policy = privacy != NULL ? privacy->dest : defaultDest; + return allowDest(dest, policy); +} + +bool +PrivacySpec::RequireAll() const { return dest == android::os::DEST_LOCAL; } + +PrivacySpec PrivacySpec::new_spec(int dest) +{ + switch (dest) { + case android::os::DEST_AUTOMATIC: + case android::os::DEST_EXPLICIT: + case android::os::DEST_LOCAL: + return PrivacySpec(dest); + default: + return PrivacySpec(); + } +} + +PrivacySpec PrivacySpec::get_default_dropbox_spec() +{ + return PrivacySpec(android::os::DEST_AUTOMATIC); +} diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h new file mode 100644 index 000000000000..4f3db678f765 --- /dev/null +++ b/cmds/incidentd/src/Privacy.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 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 PRIVACY_H +#define PRIVACY_H + +#include <stdint.h> + +// This is the default value of DEST enum, sync with privacy.proto +const uint8_t DEST_UNSET = 255; // DEST_UNSET is not exposed to libincident +const uint8_t DEST_DEFAULT_VALUE = DEST_UNSET; + +/* + * In order to NOT auto-generate large chuck of code by proto compiler in incidentd, + * privacy options's data structure are explicitly redefined here and + * the values are populated by incident_section_gen tool. + * + * Each proto field will have a Privacy when it is different from its parent, otherwise + * it uses its parent's tag. A message type will have an array of Privacy. + */ +struct Privacy { + // The field number + uint32_t field_id; + + // The field type, see external/protobuf/src/google/protobuf/descriptor.h + uint8_t type; + + // If children is null, it is a primitive field, + // otherwise it is a message field which could have overridden privacy tags here. + // This array is NULL-terminated. + Privacy** children; + + // DESTINATION Enum in frameworks/base/libs/incident/proto/android/privacy.proto. + uint8_t dest; + // A list of regexp rules for stripping string fields in proto. + const char** patterns; +}; + +// Encode field id used by ProtoOutputStream. +uint64_t encode_field_id(const Privacy* p); + +// Look up the child with given fieldId, if not found, return NULL. +const Privacy* lookup(const Privacy* p, uint32_t fieldId); + +/** + * PrivacySpec defines the request has what level of privacy authorization. + * For example, a device without user consent should only be able to upload AUTOMATIC fields. + * DEST_UNSET are treated as DEST_EXPLICIT. + */ +class PrivacySpec { +public: + const uint8_t dest; + + PrivacySpec() : dest(DEST_DEFAULT_VALUE) {} + bool operator<(const PrivacySpec& other) const; + + // check permission of a policy, if returns true, don't strip the data. + bool CheckPremission(const Privacy* privacy, const uint8_t defaultDest = DEST_DEFAULT_VALUE) const; + + // if returns true, no data need to be stripped. + bool RequireAll() const; + + // Constructs spec using static methods below. + static PrivacySpec new_spec(int dest); + static PrivacySpec get_default_dropbox_spec(); +private: + PrivacySpec(uint8_t dest) : dest(dest) {} +}; + +#endif // PRIVACY_H diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp new file mode 100644 index 000000000000..f53befefab93 --- /dev/null +++ b/cmds/incidentd/src/PrivacyBuffer.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 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 "PrivacyBuffer.h" +#include "io_util.h" + +#include <android/util/protobuf.h> +#include <cutils/log.h> + +using namespace android::util; + +const bool DEBUG = false; + +/** + * Write the field to buf based on the wire type, iterator will point to next field. + * If skip is set to true, no data will be written to buf. Return number of bytes written. + */ +void +PrivacyBuffer::writeFieldOrSkip(uint32_t fieldTag, bool skip) +{ + if (DEBUG) ALOGD("%s field %d (wiretype = %d)", skip ? "skip" : "write", + read_field_id(fieldTag), read_wire_type(fieldTag)); + + uint8_t wireType = read_wire_type(fieldTag); + size_t bytesToWrite = 0; + uint32_t varint = 0; + + switch (wireType) { + case WIRE_TYPE_VARINT: + varint = mData.readRawVarint(); + if (!skip) { + mProto.writeRawVarint(fieldTag); + mProto.writeRawVarint(varint); + } + return; + case WIRE_TYPE_FIXED64: + if (!skip) mProto.writeRawVarint(fieldTag); + bytesToWrite = 8; + break; + case WIRE_TYPE_LENGTH_DELIMITED: + bytesToWrite = mData.readRawVarint(); + if(!skip) mProto.writeLengthDelimitedHeader(read_field_id(fieldTag), bytesToWrite); + break; + case WIRE_TYPE_FIXED32: + if (!skip) mProto.writeRawVarint(fieldTag); + bytesToWrite = 4; + break; + } + if (DEBUG) ALOGD("%s %d bytes of data", skip ? "skip" : "write", (int)bytesToWrite); + if (skip) { + mData.rp()->move(bytesToWrite); + } else { + for (size_t i=0; i<bytesToWrite; i++) { + mProto.writeRawByte(mData.next()); + } + } +} + +/** + * Strip next field based on its private policy and request spec, then stores data in buf. + * Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in FdBuffer. + * + * The iterator must point to the head of a protobuf formatted field for successful operation. + * After exit with NO_ERROR, iterator points to the next protobuf field's head. + */ +status_t +PrivacyBuffer::stripField(const Privacy* parentPolicy, const PrivacySpec& spec) +{ + if (!mData.hasNext() || parentPolicy == NULL) return BAD_VALUE; + uint32_t fieldTag = mData.readRawVarint(); + const Privacy* policy = lookup(parentPolicy, read_field_id(fieldTag)); + + if (policy == NULL || policy->children == NULL) { + if (DEBUG) ALOGD("Not a message field %d: dest(%d)", read_field_id(fieldTag), + policy != NULL ? policy->dest : parentPolicy->dest); + + bool skip = !spec.CheckPremission(policy, parentPolicy->dest); + // iterator will point to head of next field + writeFieldOrSkip(fieldTag, skip); + return NO_ERROR; + } + // current field is message type and its sub-fields have extra privacy policies + uint32_t msgSize = mData.readRawVarint(); + EncodedBuffer::Pointer start = mData.rp()->copy(); + long long token = mProto.start(encode_field_id(policy)); + while (mData.rp()->pos() - start.pos() != msgSize) { + status_t err = stripField(policy, spec); + if (err != NO_ERROR) return err; + } + mProto.end(token); + return NO_ERROR; +} + +// ================================================================================ +PrivacyBuffer::PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator& data) + :mPolicy(policy), + mData(data), + mProto(), + mSize(0) +{ +} + +PrivacyBuffer::~PrivacyBuffer() +{ +} + +status_t +PrivacyBuffer::strip(const PrivacySpec& spec) +{ + if (DEBUG) ALOGD("Strip with spec %d", spec.dest); + // optimization when no strip happens + if (mPolicy == NULL || mPolicy->children == NULL || spec.RequireAll()) { + if (spec.CheckPremission(mPolicy)) mSize = mData.size(); + return NO_ERROR; + } + while (mData.hasNext()) { + status_t err = stripField(mPolicy, spec); + if (err != NO_ERROR) return err; + } + if (mData.bytesRead() != mData.size()) return BAD_VALUE; + mSize = mProto.size(); + mData.rp()->rewind(); // rewind the read pointer back to beginning after the strip. + return NO_ERROR; +} + +void +PrivacyBuffer::clear() +{ + mSize = 0; + mProto.clear(); +} + +size_t +PrivacyBuffer::size() const { return mSize; } + +status_t +PrivacyBuffer::flush(int fd) +{ + status_t err = NO_ERROR; + EncodedBuffer::iterator iter = size() == mData.size() ? mData : mProto.data(); + while (iter.readBuffer() != NULL) { + err = write_all(fd, iter.readBuffer(), iter.currentToRead()); + iter.rp()->move(iter.currentToRead()); + if (err != NO_ERROR) return err; + } + return NO_ERROR; +} diff --git a/cmds/incidentd/src/PrivacyBuffer.h b/cmds/incidentd/src/PrivacyBuffer.h new file mode 100644 index 000000000000..c9ca9a752c9c --- /dev/null +++ b/cmds/incidentd/src/PrivacyBuffer.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 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 PRIVACY_BUFFER_H +#define PRIVACY_BUFFER_H + +#include "Privacy.h" + +#include <android/util/EncodedBuffer.h> +#include <android/util/ProtoOutputStream.h> +#include <stdint.h> +#include <utils/Errors.h> + +using namespace android; +using namespace android::util; + +/** + * PrivacyBuffer holds the original protobuf data and strips PII-sensitive fields + * based on the request and holds stripped data in its own buffer for output. + */ +class PrivacyBuffer +{ +public: + PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator& data); + ~PrivacyBuffer(); + + /** + * Strip based on the request and hold data in its own buffer. Return NO_ERROR if strip succeeds. + */ + status_t strip(const PrivacySpec& spec); + + /** + * Clear encoded buffer so it can be reused by another request. + */ + void clear(); + + /** + * Return the size of the stripped data. + */ + size_t size() const; + + /** + * Flush buffer to the given fd. NO_ERROR is returned if the flush succeeds. + */ + status_t flush(int fd); + +private: + const Privacy* mPolicy; + EncodedBuffer::iterator& mData; + + ProtoOutputStream mProto; + size_t mSize; + + status_t stripField(const Privacy* parentPolicy, const PrivacySpec& spec); + void writeFieldOrSkip(uint32_t fieldTag, bool skip); +}; + +#endif // PRIVACY_BUFFER_H
\ No newline at end of file diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 1ecb291c84a1..b9f479bd683f 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -17,13 +17,12 @@ #define LOG_TAG "incidentd" #include "Reporter.h" -#include "protobuf.h" #include "report_directory.h" #include "section_list.h" -#include <private/android_filesystem_config.h> #include <android/os/DropBoxManager.h> +#include <private/android_filesystem_config.h> #include <utils/SystemClock.h> #include <sys/types.h> @@ -35,21 +34,7 @@ /** * The directory where the incident reports are stored. */ -static const String8 INCIDENT_DIRECTORY("/data/incidents"); - -static status_t -write_all(int fd, uint8_t const* buf, size_t size) -{ - while (size > 0) { - ssize_t amt = ::write(fd, buf, size); - if (amt < 0) { - return -errno; - } - size -= amt; - buf += amt; - } - return NO_ERROR; -} +static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/"; // ================================================================================ ReportRequest::ReportRequest(const IncidentReportArgs& a, @@ -63,13 +48,24 @@ ReportRequest::ReportRequest(const IncidentReportArgs& a, ReportRequest::~ReportRequest() { + if (fd >= 0) { + // clean up the opened file descriptor + close(fd); + } +} + +bool +ReportRequest::ok() +{ + return fd >= 0 && err == NO_ERROR; } // ================================================================================ ReportRequestSet::ReportRequestSet() :mRequests(), - mWritableCount(0), - mMainFd(-1) + mSections(), + mMainFd(-1), + mMainDest(-1) { } @@ -77,56 +73,36 @@ ReportRequestSet::~ReportRequestSet() { } +// TODO: dedup on exact same args and fd, report the status back to listener! void ReportRequestSet::add(const sp<ReportRequest>& request) { mRequests.push_back(request); - mWritableCount++; + mSections.merge(request->args); } void ReportRequestSet::setMainFd(int fd) { mMainFd = fd; - mWritableCount++; } -status_t -ReportRequestSet::write(uint8_t const* buf, size_t size) +void +ReportRequestSet::setMainDest(int dest) { - status_t err = EBADF; - - // The streaming ones - int const N = mRequests.size(); - for (int i=N-1; i>=0; i--) { - sp<ReportRequest> request = mRequests[i]; - if (request->fd >= 0 && request->err == NO_ERROR) { - err = write_all(request->fd, buf, size); - if (err != NO_ERROR) { - request->err = err; - mWritableCount--; - } - } - } - - // The dropbox file - if (mMainFd >= 0) { - err = write_all(mMainFd, buf, size); - if (err != NO_ERROR) { - mMainFd = -1; - mWritableCount--; - } - } - - // Return an error only when there are no FDs to write. - return mWritableCount > 0 ? NO_ERROR : err; + mMainDest = dest; } +bool +ReportRequestSet::containsSection(int id) { + return mSections.containsSection(id); +} // ================================================================================ -Reporter::Reporter() - :args(), - batch() +Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; }; + +Reporter::Reporter(const char* directory) + :batch() { char buf[100]; @@ -134,10 +110,15 @@ Reporter::Reporter() mMaxSize = 100 * 1024 * 1024; mMaxCount = 100; + // string ends up with '/' is a directory + String8 dir = String8(directory); + if (directory[dir.size() - 1] != '/') dir += "/"; + mIncidentDirectory = dir.string(); + // There can't be two at the same time because it's on one thread. mStartTime = time(NULL); - strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); - mFilename = INCIDENT_DIRECTORY + buf; + strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); + mFilename = mIncidentDirectory + buf; } Reporter::~Reporter() @@ -151,34 +132,38 @@ Reporter::runReport() status_t err = NO_ERROR; bool needMainFd = false; int mainFd = -1; + int mainDest = -1; + HeaderSection headers; // 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; + mainDest = (*it)->args.dest(); break; } } if (needMainFd) { // Create the directory - err = create_directory(INCIDENT_DIRECTORY); + if (!isTest) err = create_directory(mIncidentDirectory); if (err != NO_ERROR) { - goto done; + goto DONE; } // If there are too many files in the directory (for whatever reason), // delete the oldest ones until it's under the limit. Doing this first // does mean that we can go over, so the max size is not a hard limit. - clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount); + if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount); // Open the file. err = create_file(&mainFd); if (err != NO_ERROR) { - goto done; + goto DONE; } // Add to the set batch.setMainFd(mainFd); + batch.setMainDest(mainDest); } // Tell everyone that we're starting. @@ -189,32 +174,14 @@ Reporter::runReport() } // Write the incident headers - for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { - const sp<ReportRequest> request = (*it); - const vector<vector<int8_t>>& headers = request->args.headers(); - - for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); - buf++) { - int fd = request->fd >= 0 ? request->fd : mainFd; - - uint8_t buffer[20]; - uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER, - buf->size()); - write_all(fd, buffer, p-buffer); - - write_all(fd, (uint8_t const*)buf->data(), buf->size()); - // If there was an error now, there will be an error later and we will remove - // it from the list then. - } - } + headers.Execute(&batch); // For each of the report fields, see if we need it, and if so, execute the command // and report to those that care that we're doing it. for (const Section** section=SECTION_LIST; *section; section++) { const int id = (*section)->id; - ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); - - if (this->args.containsSection(id)) { + if (this->batch.containsSection(id)) { + ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); // Notify listener of starting for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { @@ -226,9 +193,9 @@ Reporter::runReport() // 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; + ALOGW("Incident section %s (%d) failed: %s. Stopping report.", + (*section)->name.string(), id, strerror(-err)); + goto DONE; } // Notify listener of starting @@ -238,10 +205,11 @@ Reporter::runReport() IIncidentReportStatusListener::STATUS_FINISHED); } } + ALOGD("Finish incident report section %d '%s'", id, (*section)->name.string()); } } -done: +DONE: // Close the file. if (mainFd >= 0) { close(mainFd); @@ -270,7 +238,7 @@ done: // If the status was ok, delete the file. If not, leave it around until the next // boot or the next checkin. If the directory gets too big older files will // be rotated out. - unlink(mFilename.c_str()); + if(!isTest) unlink(mFilename.c_str()); } return REPORT_FINISHED; @@ -284,7 +252,7 @@ Reporter::create_file(int* fd) { const char* filename = mFilename.c_str(); - *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660); + *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660); if (*fd < 0) { ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); return -errno; @@ -293,7 +261,7 @@ Reporter::create_file(int* fd) // Override umask. Not super critical. If it fails go on with life. chmod(filename, 0660); - if (chown(filename, AID_SYSTEM, AID_SYSTEM)) { + if (chown(filename, AID_INCIDENTD, AID_INCIDENTD)) { ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); status_t err = -errno; unlink(mFilename.c_str()); @@ -303,28 +271,34 @@ Reporter::create_file(int* fd) return NO_ERROR; } -// ================================================================================ Reporter::run_report_status_t Reporter::upload_backlog() { DIR* dir; struct dirent* entry; struct stat st; + status_t err; + + ALOGD("Start uploading backlogs in %s", INCIDENT_DIRECTORY); + if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) { + ALOGE("directory doesn't exist: %s", strerror(-err)); + return REPORT_FINISHED; + } - if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) { - ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string()); + if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) { + ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY); return REPORT_NEEDS_DROPBOX; } - String8 dirbase(INCIDENT_DIRECTORY + "/"); sp<DropBoxManager> dropbox = new DropBoxManager(); // Enumerate, count and add up size + int count = 0; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') { continue; } - String8 filename = dirbase + entry->d_name; + String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name; if (stat(filename.string(), &st) != 0) { ALOGE("Unable to stat file %s", filename.string()); continue; @@ -343,8 +317,9 @@ Reporter::upload_backlog() // boot or the next checkin. If the directory gets too big older files will // be rotated out. unlink(filename.string()); + count++; } - + ALOGD("Successfully uploaded %d files to Dropbox.", count); closedir(dir); return REPORT_FINISHED; diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h index 5b86561520f8..f30ecf0dd648 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -40,6 +40,8 @@ struct ReportRequest : public virtual RefBase ReportRequest(const IncidentReportArgs& args, const sp<IIncidentReportStatusListener> &listener, int fd); virtual ~ReportRequest(); + + bool ok(); // returns true if the request is ok for write. }; // ================================================================================ @@ -51,21 +53,21 @@ public: void add(const sp<ReportRequest>& 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); + void setMainDest(int dest); typedef vector<sp<ReportRequest>>::iterator iterator; iterator begin() { return mRequests.begin(); } iterator end() { return mRequests.end(); } + int mainFd() { return mMainFd; } + bool containsSection(int id); + int mainDest() { return mMainDest; } private: vector<sp<ReportRequest>> mRequests; - int mWritableCount; + IncidentReportArgs mSections; int mMainFd; + int mMainDest; }; // ================================================================================ @@ -77,10 +79,10 @@ public: REPORT_NEEDS_DROPBOX = 1 }; - IncidentReportArgs args; ReportRequestSet batch; Reporter(); + Reporter(const char* directory); virtual ~Reporter(); // Run the report as described in the batch and args parameters. @@ -89,12 +91,16 @@ public: static run_report_status_t upload_backlog(); private: + String8 mIncidentDirectory; + string mFilename; off_t mMaxSize; size_t mMaxCount; time_t mStartTime; status_t create_file(int* fd); + + bool isTest = true; // default to true for testing }; diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index fac299ed0dcd..faeab87f8178 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -17,18 +17,196 @@ #define LOG_TAG "incidentd" #include "Section.h" -#include "protobuf.h" -#include <binder/IServiceManager.h> +#include <errno.h> +#include <sys/prctl.h> +#include <unistd.h> +#include <wait.h> + +#include <memory> #include <mutex> +#include <android/util/protobuf.h> +#include <binder/IServiceManager.h> +#include <log/log_event_list.h> +#include <log/logprint.h> +#include <log/log_read.h> +#include <private/android_logger.h> + +#include "FdBuffer.h" +#include "frameworks/base/core/proto/android/util/log.proto.h" +#include "io_util.h" +#include "Privacy.h" +#include "PrivacyBuffer.h" +#include "section_list.h" + +using namespace android::util; using namespace std; -const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds +// special section ids +const int FIELD_ID_INCIDENT_HEADER = 1; + +// incident section parameters +const int WAIT_MAX = 5; +const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000}; +const char INCIDENT_HELPER[] = "/system/bin/incident_helper"; + +static pid_t +fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe) +{ + const char* ihArgs[] { INCIDENT_HELPER, "-s", String8::format("%d", id).string(), NULL }; + // fork used in multithreaded environment, avoid adding unnecessary code in child process + pid_t pid = fork(); + if (pid == 0) { + if (TEMP_FAILURE_RETRY(dup2(p2cPipe.readFd(), STDIN_FILENO)) != 0 + || !p2cPipe.close() + || TEMP_FAILURE_RETRY(dup2(c2pPipe.writeFd(), STDOUT_FILENO)) != 1 + || !c2pPipe.close()) { + ALOGW("%s can't setup stdin and stdout for incident helper", name); + _exit(EXIT_FAILURE); + } + + /* make sure the child dies when incidentd dies */ + prctl(PR_SET_PDEATHSIG, SIGKILL); + + execv(INCIDENT_HELPER, const_cast<char**>(ihArgs)); + + ALOGW("%s failed in incident helper process: %s", name, strerror(errno)); + _exit(EXIT_FAILURE); // always exits with failure if any + } + // close the fds used in incident helper + close(p2cPipe.readFd()); + close(c2pPipe.writeFd()); + return pid; +} + +// ================================================================================ +static status_t statusCode(int status) { + if (WIFSIGNALED(status)) { + ALOGD("return by signal: %s", strerror(WTERMSIG(status))); + return -WTERMSIG(status); + } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) { + ALOGD("return by exit: %s", strerror(WEXITSTATUS(status))); + return -WEXITSTATUS(status); + } + return NO_ERROR; +} + +static status_t kill_child(pid_t pid) { + int status; + ALOGD("try to kill child process %d", pid); + kill(pid, SIGKILL); + if (waitpid(pid, &status, 0) == -1) return -1; + return statusCode(status); +} + +static status_t wait_child(pid_t pid) { + int status; + bool died = false; + // wait for child to report status up to 1 seconds + for(int loop = 0; !died && loop < WAIT_MAX; loop++) { + if (waitpid(pid, &status, WNOHANG) == pid) died = true; + // sleep for 0.2 second + nanosleep(&WAIT_INTERVAL_NS, NULL); + } + if (!died) return kill_child(pid); + return statusCode(status); +} +// ================================================================================ +static const Privacy* +get_privacy_of_section(int id) +{ + int l = 0; + int r = PRIVACY_POLICY_COUNT - 1; + while (l <= r) { + int mid = (l + r) >> 1; + const Privacy* p = PRIVACY_POLICY_LIST[mid]; + + if (p->field_id < (uint32_t)id) { + l = mid + 1; + } else if (p->field_id > (uint32_t)id) { + r = mid - 1; + } else { + return p; + } + } + return NULL; +} + +// ================================================================================ +static status_t +write_section_header(int fd, int sectionId, size_t size) +{ + uint8_t buf[20]; + uint8_t *p = write_length_delimited_tag_header(buf, sectionId, size); + return write_all(fd, buf, p-buf); +} + +static status_t +write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* requests) +{ + status_t err = -EBADF; + EncodedBuffer::iterator data = buffer.data(); + PrivacyBuffer privacyBuffer(get_privacy_of_section(id), data); + int writeable = 0; + + // The streaming ones, group requests by spec in order to save unnecessary strip operations + map<PrivacySpec, vector<sp<ReportRequest>>> requestsBySpec; + for (auto it = requests->begin(); it != requests->end(); it++) { + sp<ReportRequest> request = *it; + if (!request->ok() || !request->args.containsSection(id)) { + continue; // skip invalid request + } + PrivacySpec spec = PrivacySpec::new_spec(request->args.dest()); + requestsBySpec[spec].push_back(request); + } + + for (auto mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) { + PrivacySpec spec = mit->first; + err = privacyBuffer.strip(spec); + if (err != NO_ERROR) return err; // it means the privacyBuffer data is corrupted. + if (privacyBuffer.size() == 0) continue; + + for (auto it = mit->second.begin(); it != mit->second.end(); it++) { + sp<ReportRequest> request = *it; + err = write_section_header(request->fd, id, privacyBuffer.size()); + if (err != NO_ERROR) { request->err = err; continue; } + err = privacyBuffer.flush(request->fd); + if (err != NO_ERROR) { request->err = err; continue; } + writeable++; + ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, + privacyBuffer.size(), request->fd, spec.dest); + } + privacyBuffer.clear(); + } + + // The dropbox file + if (requests->mainFd() >= 0) { + PrivacySpec spec = requests->mainDest() < 0 ? + PrivacySpec::get_default_dropbox_spec() : + PrivacySpec::new_spec(requests->mainDest()); + err = privacyBuffer.strip(spec); + if (err != NO_ERROR) return err; // the buffer data is corrupted. + if (privacyBuffer.size() == 0) goto DONE; + + err = write_section_header(requests->mainFd(), id, privacyBuffer.size()); + if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; } + err = privacyBuffer.flush(requests->mainFd()); + if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; } + writeable++; + ALOGD("Section %d flushed %zu bytes to dropbox %d with spec %d", id, + privacyBuffer.size(), requests->mainFd(), spec.dest); + } + +DONE: + // only returns error if there is no fd to write to. + return writeable > 0 ? NO_ERROR : err; +} // ================================================================================ -Section::Section(int i) - :id(i) +Section::Section(int i, const int64_t timeoutMs) + :id(i), + timeoutMs(timeoutMs) { } @@ -36,13 +214,99 @@ Section::~Section() { } +// ================================================================================ +HeaderSection::HeaderSection() + :Section(FIELD_ID_INCIDENT_HEADER, 0) +{ +} + +HeaderSection::~HeaderSection() +{ +} + status_t -Section::WriteHeader(ReportRequestSet* requests, size_t size) const +HeaderSection::Execute(ReportRequestSet* requests) 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); + for (ReportRequestSet::iterator it=requests->begin(); it!=requests->end(); it++) { + const sp<ReportRequest> request = *it; + const vector<vector<uint8_t>>& headers = request->args.headers(); + + for (vector<vector<uint8_t>>::const_iterator buf=headers.begin(); buf!=headers.end(); buf++) { + if (buf->empty()) continue; + + // So the idea is only requests with negative fd are written to dropbox file. + int fd = request->fd >= 0 ? request->fd : requests->mainFd(); + write_section_header(fd, FIELD_ID_INCIDENT_HEADER, buf->size()); + 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. + } + } + return NO_ERROR; +} + +// ================================================================================ +FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs) + :Section(id, timeoutMs), + mFilename(filename) +{ + name = filename; + mIsSysfs = strncmp(filename, "/sys/", 5) == 0; +} + +FileSection::~FileSection() {} + +status_t +FileSection::Execute(ReportRequestSet* requests) const +{ + // read from mFilename first, make sure the file is available + // add O_CLOEXEC to make sure it is closed when exec incident helper + int fd = open(mFilename, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + ALOGW("FileSection '%s' failed to open file", this->name.string()); + return -errno; + } + + FdBuffer buffer; + Fpipe p2cPipe; + Fpipe c2pPipe; + // initiate pipes to pass data to/from incident_helper + if (!p2cPipe.init() || !c2pPipe.init()) { + ALOGW("FileSection '%s' failed to setup pipes", this->name.string()); + return -errno; + } + + pid_t pid = fork_execute_incident_helper(this->id, this->name.string(), p2cPipe, c2pPipe); + if (pid == -1) { + ALOGW("FileSection '%s' failed to fork", this->name.string()); + return -errno; + } + + // parent process + status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(), + this->timeoutMs, mIsSysfs); + if (readStatus != NO_ERROR || buffer.timedOut()) { + ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s", + this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); + kill_child(pid); + return readStatus; + } + + status_t ihStatus = wait_child(pid); + if (ihStatus != NO_ERROR) { + ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-ihStatus)); + return ihStatus; + } + + ALOGD("FileSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(), + (int)buffer.durationMs()); + status_t err = write_report_requests(this->id, buffer, requests); + if (err != NO_ERROR) { + ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err)); + return err; + } + + return NO_ERROR; } // ================================================================================ @@ -145,7 +409,7 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const 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); + err = buffer.read(data->readFd(), this->timeoutMs); if (err != NO_ERROR) { // TODO: Log this error into the incident report. ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(), @@ -191,10 +455,9 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const } // Write the data that was collected - ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(), + ALOGD("WorkerThreadSection '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(), (int)buffer.durationMs()); - WriteHeader(requests, buffer.size()); - err = buffer.write(requests); + err = write_report_requests(this->id, buffer, requests); if (err != NO_ERROR) { ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err)); return err; @@ -204,42 +467,117 @@ WorkerThreadSection::Execute(ReportRequestSet* requests) const } // ================================================================================ -CommandSection::CommandSection(int id, const char* first, ...) - :Section(id) +void +CommandSection::init(const char* command, va_list args) { - va_list args; - int count = 0; + va_list copied_args; + int numOfArgs = 0; - va_start(args, first); - while (va_arg(args, const char*) != NULL) { - count++; + va_copy(copied_args, args); + while(va_arg(copied_args, const char*) != NULL) { + numOfArgs++; } - va_end(args); + va_end(copied_args); - mCommand = (const char**)malloc(sizeof(const char*) * count); + // allocate extra 1 for command and 1 for NULL terminator + mCommand = (const char**)malloc(sizeof(const char*) * (numOfArgs + 2)); - mCommand[0] = first; - name = first; - name += " "; - va_start(args, first); - for (int i=0; i<count; i++) { - const char* arg = va_arg(args, const char*); + mCommand[0] = command; + name = command; + for (int i=0; i<numOfArgs; i++) { + const char* arg = va_arg(args, const char*); mCommand[i+1] = arg; - if (arg != NULL) { - name += va_arg(args, const char*); - name += " "; - } + name += " "; + name += arg; } + mCommand[numOfArgs+1] = NULL; +} + +CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...) + :Section(id, timeoutMs) +{ + va_list args; + va_start(args, command); + init(command, args); + va_end(args); +} + +CommandSection::CommandSection(int id, const char* command, ...) + :Section(id) +{ + va_list args; + va_start(args, command); + init(command, args); va_end(args); } CommandSection::~CommandSection() { + free(mCommand); } status_t -CommandSection::Execute(ReportRequestSet* /*requests*/) const +CommandSection::Execute(ReportRequestSet* requests) const { + FdBuffer buffer; + Fpipe cmdPipe; + Fpipe ihPipe; + + if (!cmdPipe.init() || !ihPipe.init()) { + ALOGW("CommandSection '%s' failed to setup pipes", this->name.string()); + return -errno; + } + + pid_t cmdPid = fork(); + if (cmdPid == -1) { + ALOGW("CommandSection '%s' failed to fork", this->name.string()); + return -errno; + } + // child process to execute the command as root + if (cmdPid == 0) { + // replace command's stdout with ihPipe's write Fd + if (dup2(cmdPipe.writeFd(), STDOUT_FILENO) != 1 || !ihPipe.close() || !cmdPipe.close()) { + ALOGW("CommandSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno)); + _exit(EXIT_FAILURE); + } + execvp(this->mCommand[0], (char *const *) this->mCommand); + int err = errno; // record command error code + ALOGW("CommandSection '%s' failed in executing command: %s", this->name.string(), strerror(errno)); + _exit(err); // exit with command error code + } + pid_t ihPid = fork_execute_incident_helper(this->id, this->name.string(), cmdPipe, ihPipe); + if (ihPid == -1) { + ALOGW("CommandSection '%s' failed to fork", this->name.string()); + return -errno; + } + + close(cmdPipe.writeFd()); + status_t readStatus = buffer.read(ihPipe.readFd(), this->timeoutMs); + if (readStatus != NO_ERROR || buffer.timedOut()) { + ALOGW("CommandSection '%s' failed to read data from incident helper: %s, timedout: %s", + this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); + kill_child(cmdPid); + kill_child(ihPid); + return readStatus; + } + + // TODO: wait for command here has one trade-off: the failed status of command won't be detected until + // buffer timeout, but it has advatage on starting the data stream earlier. + status_t cmdStatus = wait_child(cmdPid); + status_t ihStatus = wait_child(ihPid); + if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) { + ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incident helper: %s", + this->name.string(), strerror(-cmdStatus), strerror(-ihStatus)); + return cmdStatus != NO_ERROR ? cmdStatus : ihStatus; + } + + ALOGD("CommandSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(), + (int)buffer.durationMs()); + status_t err = write_report_requests(this->id, buffer, requests); + if (err != NO_ERROR) { + ALOGW("CommandSection '%s' failed writing: %s", this->name.string(), strerror(-err)); + return err; + } return NO_ERROR; } @@ -254,7 +592,7 @@ DumpsysSection::DumpsysSection(int id, const char* service, ...) va_list args; va_start(args, service); while (true) { - const char* arg = va_arg(args, const char*); + const char* arg = va_arg(args, const char*); if (arg == NULL) { break; } @@ -274,7 +612,7 @@ DumpsysSection::BlockingCall(int pipeWriteFd) const { // checkService won't wait for the service to show up like getService will. sp<IBinder> service = defaultServiceManager()->checkService(mService); - + if (service == NULL) { // Returning an error interrupts the entire incident report, so just // log the failure. @@ -290,3 +628,160 @@ DumpsysSection::BlockingCall(int pipeWriteFd) const return NO_ERROR; } + +// ================================================================================ +// initialization only once in Section.cpp. +map<log_id_t, log_time> LogSection::gLastLogsRetrieved; + +LogSection::LogSection(int id, log_id_t logID) + :WorkerThreadSection(id), + mLogID(logID) +{ + name += "logcat "; + name += android_log_id_to_name(logID); + switch (logID) { + case LOG_ID_EVENTS: + case LOG_ID_STATS: + case LOG_ID_SECURITY: + mBinary = true; + break; + default: + mBinary = false; + } +} + +LogSection::~LogSection() +{ +} + +static size_t +trimTail(char const* buf, size_t len) +{ + while (len > 0) { + char c = buf[len - 1]; + if (c == '\0' || c == ' ' || c == '\n' || c == '\r' || c == ':') { + len--; + } else { + break; + } + } + return len; +} + +static inline int32_t get4LE(uint8_t const* src) { + return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); +} + +status_t +LogSection::BlockingCall(int pipeWriteFd) const +{ + status_t err = NO_ERROR; + // Open log buffer and getting logs since last retrieved time if any. + unique_ptr<logger_list, void (*)(logger_list*)> loggers( + gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end() ? + android_logger_list_alloc(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0) : + android_logger_list_alloc_time(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, + gLastLogsRetrieved[mLogID], 0), + android_logger_list_free); + + if (android_logger_open(loggers.get(), mLogID) == NULL) { + ALOGW("LogSection %s: Can't get logger.", this->name.string()); + return err; + } + + log_msg msg; + log_time lastTimestamp(0); + + ProtoOutputStream proto; + while (true) { // keeps reading until logd buffer is fully read. + status_t err = android_logger_list_read(loggers.get(), &msg); + // err = 0 - no content, unexpected connection drop or EOF. + // err = +ive number - size of retrieved data from logger + // err = -ive number, OS supplied error _except_ for -EAGAIN + // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data. + if (err <= 0) { + if (err != -EAGAIN) { + ALOGE("LogSection %s: fails to read a log_msg.\n", this->name.string()); + } + break; + } + if (mBinary) { + // remove the first uint32 which is tag's index in event log tags + android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t), + msg.len() - sizeof(uint32_t));; + android_log_list_element elem; + + lastTimestamp.tv_sec = msg.entry_v1.sec; + lastTimestamp.tv_nsec = msg.entry_v1.nsec; + + // format a BinaryLogEntry + long long token = proto.start(LogProto::BINARY_LOGS); + proto.write(BinaryLogEntry::SEC, msg.entry_v1.sec); + proto.write(BinaryLogEntry::NANOSEC, msg.entry_v1.nsec); + proto.write(BinaryLogEntry::UID, (int) msg.entry_v4.uid); + proto.write(BinaryLogEntry::PID, msg.entry_v1.pid); + proto.write(BinaryLogEntry::TID, msg.entry_v1.tid); + proto.write(BinaryLogEntry::TAG_INDEX, get4LE(reinterpret_cast<uint8_t const*>(msg.msg()))); + do { + elem = android_log_read_next(context); + long long elemToken = proto.start(BinaryLogEntry::ELEMS); + switch (elem.type) { + case EVENT_TYPE_INT: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_INT); + proto.write(BinaryLogEntry::Elem::VAL_INT32, (int) elem.data.int32); + break; + case EVENT_TYPE_LONG: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LONG); + proto.write(BinaryLogEntry::Elem::VAL_INT64, (long long) elem.data.int64); + break; + case EVENT_TYPE_STRING: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_STRING); + proto.write(BinaryLogEntry::Elem::VAL_STRING, elem.data.string, elem.len); + break; + case EVENT_TYPE_FLOAT: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_FLOAT); + proto.write(BinaryLogEntry::Elem::VAL_FLOAT, elem.data.float32); + break; + case EVENT_TYPE_LIST: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST); + break; + case EVENT_TYPE_LIST_STOP: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_LIST_STOP); + break; + case EVENT_TYPE_UNKNOWN: + proto.write(BinaryLogEntry::Elem::TYPE, BinaryLogEntry::Elem::EVENT_TYPE_UNKNOWN); + break; + } + proto.end(elemToken); + } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete); + proto.end(token); + if (context) { + android_log_destroy(&context); + } + } else { + AndroidLogEntry entry; + err = android_log_processLogBuffer(&msg.entry_v1, &entry); + if (err != NO_ERROR) { + ALOGE("LogSection %s: fails to process to an entry.\n", this->name.string()); + break; + } + lastTimestamp.tv_sec = entry.tv_sec; + lastTimestamp.tv_nsec = entry.tv_nsec; + + // format a TextLogEntry + long long token = proto.start(LogProto::TEXT_LOGS); + proto.write(TextLogEntry::SEC, (long long)entry.tv_sec); + proto.write(TextLogEntry::NANOSEC, (long long)entry.tv_nsec); + proto.write(TextLogEntry::PRIORITY, (int)entry.priority); + proto.write(TextLogEntry::UID, entry.uid); + proto.write(TextLogEntry::PID, entry.pid); + proto.write(TextLogEntry::TID, entry.tid); + proto.write(TextLogEntry::TAG, entry.tag, trimTail(entry.tag, entry.tagLen)); + proto.write(TextLogEntry::LOG, entry.message, trimTail(entry.message, entry.messageLen)); + proto.end(token); + } + } + gLastLogsRetrieved[mLogID] = lastTimestamp; + proto.flush(pipeWriteFd); + return err; +} diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index 35740e9771d5..d440ee92601c 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -17,7 +17,10 @@ #ifndef SECTIONS_H #define SECTIONS_H -#include "FdBuffer.h" +#include "Reporter.h" + +#include <map> +#include <stdarg.h> #include <utils/String8.h> #include <utils/String16.h> @@ -25,21 +28,34 @@ using namespace android; +const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds + /** * Base class for sections */ class Section { public: - int id; + const int id; + const int64_t timeoutMs; // each section must have a timeout String8 name; - Section(int id); + Section(int id, const int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS); virtual ~Section(); virtual status_t Execute(ReportRequestSet* requests) const = 0; +}; + +/** + * Section that generates incident headers. + */ +class HeaderSection : public Section +{ +public: + HeaderSection(); + virtual ~HeaderSection(); - status_t WriteHeader(ReportRequestSet* requests, size_t size) const; + virtual status_t Execute(ReportRequestSet* requests) const; }; /** @@ -48,13 +64,14 @@ public: class FileSection : public Section { public: - FileSection(int id, const char* filename); + FileSection(int id, const char* filename, const int64_t timeoutMs = 5000 /* 5 seconds */); virtual ~FileSection(); virtual status_t Execute(ReportRequestSet* requests) const; private: const char* mFilename; + bool mIsSysfs; // sysfs files are pollable but return POLLERR by default, handle it separately }; /** @@ -77,13 +94,18 @@ public: class CommandSection : public Section { public: - CommandSection(int id, const char* first, ...); + CommandSection(int id, const int64_t timeoutMs, const char* command, ...); + + CommandSection(int id, const char* command, ...); + virtual ~CommandSection(); virtual status_t Execute(ReportRequestSet* requests) const; private: const char** mCommand; + + void init(const char* command, va_list args); }; /** @@ -102,5 +124,24 @@ private: Vector<String16> mArgs; }; +/** + * Section that reads from logd. + */ +class LogSection : public WorkerThreadSection +{ + // global last log retrieved timestamp for each log_id_t. + static map<log_id_t, log_time> gLastLogsRetrieved; + +public: + LogSection(int id, log_id_t logID); + virtual ~LogSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + log_id_t mLogID; + bool mBinary; +}; + #endif // SECTIONS_H diff --git a/cmds/incidentd/src/io_util.cpp b/cmds/incidentd/src/io_util.cpp new file mode 100644 index 000000000000..90f543e30ff7 --- /dev/null +++ b/cmds/incidentd/src/io_util.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 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 "io_util.h" + +#include <unistd.h> + +status_t write_all(int fd, uint8_t const* buf, size_t size) +{ + while (size > 0) { + ssize_t amt = TEMP_FAILURE_RETRY(::write(fd, buf, size)); + if (amt < 0) { + return -errno; + } + size -= amt; + buf += amt; + } + return NO_ERROR; +} + +Fpipe::Fpipe() {} + +Fpipe::~Fpipe() { close(); } + +bool Fpipe::close() { return !(::close(mFds[0]) || ::close(mFds[1])); } + +bool Fpipe::init() { return pipe(mFds) != -1; } + +int Fpipe::readFd() const { return mFds[0]; } + +int Fpipe::writeFd() const { return mFds[1]; } diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/io_util.h index b6112ed0f7df..320dd6c386d2 100644 --- a/cmds/incidentd/src/section_list.cpp +++ b/cmds/incidentd/src/io_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 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. @@ -14,16 +14,28 @@ * limitations under the License. */ -#include "section_list.h" +#ifndef IO_UTIL_H +#define IO_UTIL_H -//using namespace android::util; +#include <stdint.h> +#include <utils/Errors.h> -/** - * 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 +using namespace android; + +status_t write_all(int fd, uint8_t const* buf, size_t size); + +class Fpipe { +public: + Fpipe(); + ~Fpipe(); + + bool init(); + bool close(); + int readFd() const; + int writeFd() const; + +private: + int mFds[2]; }; +#endif // IO_UTIL_H
\ No newline at end of file diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp deleted file mode 100644 index cb864fd3b619..000000000000 --- a/cmds/incidentd/src/protobuf.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 deleted file mode 100644 index a24399832b00..000000000000 --- a/cmds/incidentd/src/protobuf.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 <stdint.h> - -/** - * 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 index f60b8ac46cca..20111d8ae89a 100644 --- a/cmds/incidentd/src/report_directory.cpp +++ b/cmds/incidentd/src/report_directory.cpp @@ -58,26 +58,9 @@ create_directory(const char* directory) 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; - } + ALOGE("No such directory %s, something wrong.", dir); + err = -1; + goto done; } if (!last) { *d++ = '/'; @@ -97,7 +80,7 @@ create_directory(const char* directory) err = BAD_VALUE; goto done; } - if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) { + if (st.st_uid != AID_INCIDENTD || st.st_gid != AID_INCIDENTD) { 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; @@ -129,7 +112,8 @@ clean_directory(const char* directory, off_t maxSize, size_t maxCount) return; } - String8 dirbase(String8(directory) + "/"); + String8 dirbase(directory); + if (directory[dirbase.size() - 1] != '/') dirbase += "/"; off_t totalSize = 0; size_t totalCount = 0; diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h index c97751937d6d..ddc0505df331 100644 --- a/cmds/incidentd/src/section_list.h +++ b/cmds/incidentd/src/section_list.h @@ -17,12 +17,24 @@ #ifndef SECTION_LIST_H #define SECTION_LIST_H +#include <log/log_event_list.h> // include log_id_t enums. + +#include "Privacy.h" #include "Section.h" /** * This is the mapping of section IDs to the commands that are run to get those commands. + * The section IDs are guaranteed in ascending order, NULL-terminated. */ extern const Section* SECTION_LIST[]; +/** + * This is the mapping of section IDs to each section's privacy policy. + * The section IDs are guaranteed in ascending order, not NULL-terminated since size is provided. + */ +extern const Privacy** PRIVACY_POLICY_LIST; + +extern const int PRIVACY_POLICY_COUNT; + #endif // SECTION_LIST_H |