summaryrefslogtreecommitdiff
path: root/cmds/incidentd/src
diff options
context:
space:
mode:
Diffstat (limited to 'cmds/incidentd/src')
-rw-r--r--cmds/incidentd/src/FdBuffer.cpp186
-rw-r--r--cmds/incidentd/src/FdBuffer.h37
-rw-r--r--cmds/incidentd/src/IncidentService.cpp44
-rw-r--r--cmds/incidentd/src/Privacy.cpp83
-rw-r--r--cmds/incidentd/src/Privacy.h83
-rw-r--r--cmds/incidentd/src/PrivacyBuffer.cpp162
-rw-r--r--cmds/incidentd/src/PrivacyBuffer.h71
-rw-r--r--cmds/incidentd/src/Reporter.cpp159
-rw-r--r--cmds/incidentd/src/Reporter.h20
-rw-r--r--cmds/incidentd/src/Section.cpp567
-rw-r--r--cmds/incidentd/src/Section.h53
-rw-r--r--cmds/incidentd/src/io_util.cpp46
-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.cpp41
-rw-r--r--cmds/incidentd/src/protobuf.h40
-rw-r--r--cmds/incidentd/src/report_directory.cpp28
-rw-r--r--cmds/incidentd/src/section_list.h12
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