This cl does the following things:
0) Implements a skeleton of incident_helper
1) Implements FileSection class which calls incident_helper to parse
file content to protobuf
2) Adds Kernel Wake Sources to incident.proto and makes it parsed by
FileSection
3) Adds basic gtests to test FdBuffer, io_utils, FileSection
implementation
Bug: 62923266
Bug: 62926061
Test: manual - push incidentd, incident_helper and incident to my device
and verify kernel wakeup sources file is able to be parsed.
Change-Id: I2aa6b6158d962ce70e6fa6c8a9c42213a45ff41c
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index 527d7ee..7743301 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -24,11 +24,11 @@
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
+#include <wait.h>
-const ssize_t BUFFER_SIZE = 16 * 1024;
+const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB
const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max
-
FdBuffer::FdBuffer()
:mBuffers(),
mStartTime(-1),
@@ -109,6 +109,135 @@
return NO_ERROR;
}
+status_t
+FdBuffer::readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs)
+{
+ 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
+ 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;
+ }
+
+ int64_t remainingTime = (mStartTime + timeoutMs) - uptimeMillis();
+ if (remainingTime <= 0) {
+ mTimedOut = true;
+ break;
+ }
+
+ // wait for any pfds to be ready to perform IO
+ int count = poll(pfds, 3, remainingTime);
+ if (count == 0) {
+ mTimedOut = true;
+ break;
+ } else if (count < 0) {
+ return -errno;
+ }
+
+ // make sure no errors occur on any fds
+ for (int i = 0; i < 3; ++i) {
+ if ((pfds[i].revents & POLLERR) != 0) {
+ 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)) {
+ 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)) {
+ 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, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten);
+ if (amt < 0) {
+ if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
+ return -errno;
+ } // otherwise just continue
+ } else if (amt == 0) {
+ break;
+ } else {
+ mCurrentWritten += amt;
+ }
+ }
+
+ mFinishTime = uptimeMillis();
+ return NO_ERROR;
+}
+
size_t
FdBuffer::size()
{
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index e12374f..7888442 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -44,6 +44,16 @@
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.
+ */
+ status_t readProcessedDataInStream(int fd, int toFd, int fromFd, int64_t timeoutMs);
+
+ /**
* Whether we timed out.
*/
bool timedOut() { return mTimedOut; }
@@ -82,5 +92,19 @@
bool mTruncated;
};
+class Fpipe {
+public:
+ Fpipe() {}
+ bool close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
+ ~Fpipe() { close(); }
+
+ inline status_t init() { return pipe(mFds); }
+ inline int readFd() const { return mFds[0]; }
+ inline int writeFd() const { return mFds[1]; }
+
+private:
+ int mFds[2];
+};
+
#endif // FD_BUFFER_H
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 1ecb291..ba157de6 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -22,8 +22,8 @@
#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>
@@ -37,8 +37,8 @@
*/
static const String8 INCIDENT_DIRECTORY("/data/incidents");
-static status_t
-write_all(int fd, uint8_t const* buf, size_t size)
+// ================================================================================
+static status_t write_all(int fd, uint8_t const* buf, size_t size)
{
while (size > 0) {
ssize_t amt = ::write(fd, buf, size);
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index fac299e..8494f98 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -21,10 +21,19 @@
#include <binder/IServiceManager.h>
#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wait.h>
+#include <unistd.h>
using namespace std;
const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
+const int64_t INCIDENT_HELPER_TIMEOUT_MS = 5 * 1000; // 5 seconds
+const char* INCIDENT_HELPER = "/system/bin/incident_helper";
+const uid_t IH_UID = 9999; // run incident_helper as nobody
+const gid_t IH_GID = 9999;
// ================================================================================
Section::Section(int i)
@@ -46,6 +55,115 @@
}
// ================================================================================
+FileSection::FileSection(int id, const char* filename)
+ : Section(id), mFilename(filename) {
+ name = "cat ";
+ name += filename;
+}
+
+FileSection::~FileSection() {}
+
+status_t FileSection::Execute(ReportRequestSet* requests) const {
+ Fpipe p2cPipe;
+ Fpipe c2pPipe;
+ FdBuffer buffer;
+
+ // initiate pipes to pass data to/from incident_helper
+ if (p2cPipe.init() == -1) {
+ return -errno;
+ }
+ if (c2pPipe.init() == -1) {
+ return -errno;
+ }
+
+ // fork a child process
+ pid_t pid = fork();
+
+ if (pid == -1) {
+ ALOGW("FileSection '%s' failed to fork", this->name.string());
+ return -errno;
+ }
+
+ // child process
+ if (pid == 0) {
+ if (setgid(IH_GID) == -1) {
+ ALOGW("FileSection '%s' can't change gid: %s", this->name.string(), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (setuid(IH_UID) == -1) {
+ ALOGW("FileSection '%s' can't change uid: %s", this->name.string(), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ if (dup2(p2cPipe.readFd(), STDIN_FILENO) != 0 || !p2cPipe.close()) {
+ ALOGW("FileSection '%s' failed to set up stdin: %s", this->name.string(), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) {
+ ALOGW("FileSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ // execute incident_helper to parse raw file data and generate protobuf
+ char sectionID[8]; // section id is expected to be smaller than 8 digits
+ sprintf(sectionID, "%d", this->id);
+ const char* args[]{INCIDENT_HELPER, "-s", sectionID, NULL};
+ execv(INCIDENT_HELPER, const_cast<char**>(args));
+
+ ALOGW("FileSection '%s' failed in child process: %s", this->name.string(), strerror(errno));
+ return -1;
+ }
+
+ // parent process
+
+ // close fds used in child process
+ close(p2cPipe.readFd());
+ close(c2pPipe.writeFd());
+
+ // read from mFilename and pump buffer to incident_helper
+ status_t err = NO_ERROR;
+ int fd = open(mFilename, O_RDONLY, 0444);
+ if (fd == -1) {
+ ALOGW("FileSection '%s' failed to open file", this->name.string());
+ return -errno;
+ }
+
+ err = buffer.readProcessedDataInStream(fd,
+ p2cPipe.writeFd(), c2pPipe.readFd(), INCIDENT_HELPER_TIMEOUT_MS);
+ if (err != NO_ERROR) {
+ ALOGW("FileSection '%s' failed to read data from incident helper: %s",
+ this->name.string(), strerror(-err));
+ kill(pid, SIGKILL); // kill child process if meets error
+ return err;
+ }
+
+ if (buffer.timedOut()) {
+ ALOGW("FileSection '%s' timed out reading from incident helper!", this->name.string());
+ kill(pid, SIGKILL); // kill the child process if timed out
+ }
+
+ // has to block here to reap child process
+ int status;
+ int w = waitpid(pid, &status, 0);
+ if (w < 0 || status == -1) {
+ ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-err));
+ return -errno;
+ }
+
+ // write parsed data to reporter
+ ALOGD("section '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
+ (int)buffer.durationMs());
+ WriteHeader(requests, buffer.size());
+ err = buffer.write(requests);
+ if (err != NO_ERROR) {
+ ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err));
+ return err;
+ }
+
+ return NO_ERROR;
+}
+
+// ================================================================================
struct WorkerThreadData : public virtual RefBase
{
const WorkerThreadSection* section;
@@ -223,7 +341,7 @@
name += " ";
va_start(args, first);
for (int i=0; i<count; i++) {
- const char* arg = va_arg(args, const char*);
+ const char* arg = va_arg(args, const char*);
mCommand[i+1] = arg;
if (arg != NULL) {
name += va_arg(args, const char*);
@@ -254,7 +372,7 @@
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 +392,7 @@
{
// 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.
diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp
index cb864fd..a703ef9 100644
--- a/cmds/incidentd/src/protobuf.cpp
+++ b/cmds/incidentd/src/protobuf.cpp
@@ -16,7 +16,7 @@
#include "protobuf.h"
-uint8_t*
+uint8_t*
write_raw_varint(uint8_t* buf, uint32_t val)
{
uint8_t* p = buf;
@@ -31,7 +31,7 @@
}
}
-uint8_t*
+uint8_t*
write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size)
{
buf = write_raw_varint(buf, (fieldId << 3) | 2);
diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h
index a243998..f196ddc1 100644
--- a/cmds/incidentd/src/protobuf.h
+++ b/cmds/incidentd/src/protobuf.h
@@ -21,13 +21,14 @@
/**
* 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
+ * 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.
+ * 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);
@@ -36,5 +37,4 @@
FIELD_ID_INCIDENT_HEADER = 1
};
-#endif // PROTOBUF_H
-
+#endif // PROTOBUF_H
diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp
index b6112ed..72c54d2 100644
--- a/cmds/incidentd/src/section_list.cpp
+++ b/cmds/incidentd/src/section_list.cpp
@@ -16,14 +16,14 @@
#include "section_list.h"
-//using namespace android::util;
-
/**
* This is the mapping of section IDs to the commands that are run to get those commands.
*/
const Section* SECTION_LIST[] = {
- new DumpsysSection(3000,
- "fingerprint", "--proto", "--incident", NULL),
+ // Linux Services
+ new FileSection(2002, "/d/wakeup_sources"),
+
+ // System Services
+ new DumpsysSection(3000, "fingerprint", "--proto", "--incident", NULL),
NULL
};
-