Merge "Implement a new section to attach LAST_KMSG to incident report This section simply gzip a large file and stores result in GZippedFileProto"
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index 6bdd9be..3a47fe1 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -15,7 +15,8 @@
LOCAL_PATH:= $(call my-dir)
# proto files used in incidentd to generate cppstream proto headers.
-PROTO_FILES:= frameworks/base/core/proto/android/util/log.proto
+PROTO_FILES:= frameworks/base/core/proto/android/util/log.proto \
+ frameworks/base/core/proto/android/os/data.proto
# ========= #
# incidentd #
@@ -131,7 +132,7 @@
LOCAL_MODULE_CLASS := NATIVE_TESTS
gen_src_dir := $(local-generated-sources-dir)
# generate cppstream proto for testing
-GEN_PROTO := $(gen_src_dir)/log.proto.timestamp
+GEN_PROTO := $(gen_src_dir)/test.proto.timestamp
$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES)
$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir)
$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index db60794..64da677 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -76,6 +76,7 @@
return -errno;
}
} else if (amt == 0) {
+ VLOG("Reached EOF of fd=%d", fd);
break;
}
mBuffer.wp()->move(amt);
@@ -156,10 +157,10 @@
if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
VLOG("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;
+ } // otherwise just continue
+ } else if (amt == 0) {
+ VLOG("Reached EOF of input file %d", fd);
+ pfds[0].fd = -1; // reach EOF so don't have to poll pfds[0].
} else {
rpos += amt;
cirSize += amt;
@@ -187,6 +188,7 @@
// if buffer is empty and fd is closed, close write fd.
if (cirSize == 0 && pfds[0].fd == -1 && pfds[1].fd != -1) {
+ VLOG("Close write pipe %d", toFd);
::close(pfds[1].fd);
pfds[1].fd = -1;
}
@@ -207,6 +209,7 @@
return -errno;
} // otherwise just continue
} else if (amt == 0) {
+ VLOG("Reached EOF of fromFd %d", fromFd);
break;
} else {
mBuffer.wp()->move(amt);
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index 5bfa093..66a3de1 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -26,7 +26,7 @@
using namespace std;
/**
- * Reads a file into a buffer, and then writes that data to an FdSet.
+ * Reads data from fd into a buffer, fd must be closed explicitly.
*/
class FdBuffer {
public:
@@ -83,6 +83,11 @@
*/
EncodedBuffer::iterator data() const;
+ /**
+ * Return the internal buffer, don't call unless you are familiar with EncodedBuffer.
+ */
+ EncodedBuffer* getInternalBuffer() { return &mBuffer; }
+
private:
EncodedBuffer mBuffer;
int64_t mStartTime;
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 64eae3a..334d77c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -18,12 +18,8 @@
#include "Section.h"
-#include <errno.h>
-#include <sys/prctl.h>
-#include <unistd.h>
#include <wait.h>
-#include <memory>
#include <mutex>
#include <android-base/file.h>
@@ -37,6 +33,7 @@
#include "FdBuffer.h"
#include "Privacy.h"
#include "PrivacyBuffer.h"
+#include "frameworks/base/core/proto/android/os/data.proto.h"
#include "frameworks/base/core/proto/android/util/log.proto.h"
#include "incidentd_util.h"
@@ -52,31 +49,11 @@
const int WAIT_MAX = 5;
const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
const char INCIDENT_HELPER[] = "/system/bin/incident_helper";
+const char GZIP[] = "/system/bin/gzip";
-static pid_t fork_execute_incident_helper(const int id, const char* name, Fpipe& p2cPipe,
- Fpipe& c2pPipe) {
+static pid_t fork_execute_incident_helper(const int id, 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;
+ return fork_execute_cmd(INCIDENT_HELPER, const_cast<char**>(ihArgs), p2cPipe, c2pPipe);
}
// ================================================================================
@@ -254,10 +231,12 @@
return NO_ERROR;
}
// ================================================================================
+static inline bool isSysfs(const char* filename) { return strncmp(filename, "/sys/", 5) == 0; }
+
FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
: Section(id, timeoutMs), mFilename(filename) {
name = filename;
- mIsSysfs = strncmp(filename, "/sys/", 5) == 0;
+ mIsSysfs = isSysfs(filename);
}
FileSection::~FileSection() {}
@@ -280,7 +259,7 @@
return -errno;
}
- pid_t pid = fork_execute_incident_helper(this->id, this->name.string(), p2cPipe, c2pPipe);
+ pid_t pid = fork_execute_incident_helper(this->id, &p2cPipe, &c2pPipe);
if (pid == -1) {
ALOGW("FileSection '%s' failed to fork", this->name.string());
return -errno;
@@ -289,6 +268,8 @@
// parent process
status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(),
this->timeoutMs, mIsSysfs);
+ close(fd); // close the fd anyway.
+
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");
@@ -313,7 +294,99 @@
return NO_ERROR;
}
+// ================================================================================
+GZipSection::GZipSection(int id, const char* filename, ...) : Section(id) {
+ name = "gzip ";
+ name += filename;
+ va_list args;
+ va_start(args, filename);
+ mFilenames = varargs(filename, args);
+ va_end(args);
+}
+GZipSection::~GZipSection() {}
+
+status_t GZipSection::Execute(ReportRequestSet* requests) const {
+ // Reads the files in order, use the first available one.
+ int index = 0;
+ int fd = -1;
+ while (mFilenames[index] != NULL) {
+ fd = open(mFilenames[index], O_RDONLY | O_CLOEXEC);
+ if (fd != -1) {
+ break;
+ }
+ ALOGW("GZipSection failed to open file %s", mFilenames[index]);
+ index++; // look at the next file.
+ }
+ VLOG("GZipSection is using file %s, fd=%d", mFilenames[index], fd);
+ if (fd == -1) return -1;
+
+ FdBuffer buffer;
+ Fpipe p2cPipe;
+ Fpipe c2pPipe;
+ // initiate pipes to pass data to/from gzip
+ if (!p2cPipe.init() || !c2pPipe.init()) {
+ ALOGW("GZipSection '%s' failed to setup pipes", this->name.string());
+ return -errno;
+ }
+
+ const char* gzipArgs[]{GZIP, NULL};
+ pid_t pid = fork_execute_cmd(GZIP, const_cast<char**>(gzipArgs), &p2cPipe, &c2pPipe);
+ if (pid == -1) {
+ ALOGW("GZipSection '%s' failed to fork", this->name.string());
+ return -errno;
+ }
+ // parent process
+
+ // construct Fdbuffer to output GZippedfileProto, the reason to do this instead of using
+ // ProtoOutputStream is to avoid allocation of another buffer inside ProtoOutputStream.
+ EncodedBuffer* internalBuffer = buffer.getInternalBuffer();
+ internalBuffer->writeHeader((uint32_t)GZippedFileProto::FILENAME, WIRE_TYPE_LENGTH_DELIMITED);
+ String8 usedFile(mFilenames[index]);
+ internalBuffer->writeRawVarint32(usedFile.size());
+ for (size_t i = 0; i < usedFile.size(); i++) {
+ internalBuffer->writeRawByte(mFilenames[index][i]);
+ }
+ internalBuffer->writeHeader((uint32_t)GZippedFileProto::GZIPPED_DATA,
+ WIRE_TYPE_LENGTH_DELIMITED);
+ size_t editPos = internalBuffer->wp()->pos();
+ internalBuffer->wp()->move(8); // reserve 8 bytes for the varint of the data size.
+ size_t dataBeginAt = internalBuffer->wp()->pos();
+ VLOG("GZipSection '%s' editPos=%zd, dataBeginAt=%zd", this->name.string(), editPos,
+ dataBeginAt);
+
+ status_t readStatus = buffer.readProcessedDataInStream(
+ fd, p2cPipe.writeFd(), c2pPipe.readFd(), this->timeoutMs, isSysfs(mFilenames[index]));
+ close(fd); // close the fd anyway.
+
+ if (readStatus != NO_ERROR || buffer.timedOut()) {
+ ALOGW("GZipSection '%s' failed to read data from gzip: %s, timedout: %s",
+ this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+ kill_child(pid);
+ return readStatus;
+ }
+
+ status_t gzipStatus = wait_child(pid);
+ if (gzipStatus != NO_ERROR) {
+ ALOGW("GZipSection '%s' abnormal child process: %s", this->name.string(),
+ strerror(-gzipStatus));
+ return gzipStatus;
+ }
+ // Revisit the actual size from gzip result and edit the internal buffer accordingly.
+ size_t dataSize = buffer.size() - dataBeginAt;
+ internalBuffer->wp()->rewind()->move(editPos);
+ internalBuffer->writeRawVarint32(dataSize);
+ internalBuffer->copy(dataBeginAt, dataSize);
+ VLOG("GZipSection '%s' wrote %zd bytes in %d ms, dataSize=%zd", this->name.string(),
+ buffer.size(), (int)buffer.durationMs(), dataSize);
+ status_t err = write_report_requests(this->id, buffer, requests);
+ if (err != NO_ERROR) {
+ ALOGW("GZipSection '%s' failed writing: %s", this->name.string(), strerror(-err));
+ return err;
+ }
+
+ return NO_ERROR;
+}
// ================================================================================
struct WorkerThreadData : public virtual RefBase {
const WorkerThreadSection* section;
@@ -457,42 +530,20 @@
}
// ================================================================================
-void CommandSection::init(const char* command, va_list args) {
- va_list copied_args;
- int numOfArgs = 0;
-
- va_copy(copied_args, args);
- while (va_arg(copied_args, const char*) != NULL) {
- numOfArgs++;
- }
- va_end(copied_args);
-
- // allocate extra 1 for command and 1 for NULL terminator
- mCommand = (const char**)malloc(sizeof(const char*) * (numOfArgs + 2));
-
- mCommand[0] = command;
- name = command;
- for (int i = 0; i < numOfArgs; i++) {
- const char* arg = va_arg(args, const char*);
- mCommand[i + 1] = arg;
- name += " ";
- name += arg;
- }
- mCommand[numOfArgs + 1] = NULL;
-}
-
CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...)
: Section(id, timeoutMs) {
+ name = command;
va_list args;
va_start(args, command);
- init(command, args);
+ mCommand = varargs(command, args);
va_end(args);
}
CommandSection::CommandSection(int id, const char* command, ...) : Section(id) {
+ name = command;
va_list args;
va_start(args, command);
- init(command, args);
+ mCommand = varargs(command, args);
va_end(args);
}
@@ -527,7 +578,7 @@
strerror(errno));
_exit(err); // exit with command error code
}
- pid_t ihPid = fork_execute_incident_helper(this->id, this->name.string(), cmdPipe, ihPipe);
+ pid_t ihPid = fork_execute_incident_helper(this->id, &cmdPipe, &ihPipe);
if (ihPid == -1) {
ALOGW("CommandSection '%s' failed to fork", this->name.string());
return -errno;
@@ -544,8 +595,7 @@
}
// 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.
+ // 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) {
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index d644681..8294be1 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -84,6 +84,21 @@
};
/**
+ * Section that reads in a file and gzips the content.
+ */
+class GZipSection : public Section {
+public:
+ GZipSection(int id, const char* filename, ...);
+ virtual ~GZipSection();
+
+ virtual status_t Execute(ReportRequestSet* requests) const;
+
+private:
+ // It looks up the content from multiple files and stops when the first one is available.
+ const char** mFilenames;
+};
+
+/**
* Base class for sections that call a command that might need a timeout.
*/
class WorkerThreadSection : public Section {
@@ -111,8 +126,6 @@
private:
const char** mCommand;
-
- void init(const char* command, va_list args);
};
/**
diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp
index 2415860..fc7cec9 100644
--- a/cmds/incidentd/src/incidentd_util.cpp
+++ b/cmds/incidentd/src/incidentd_util.cpp
@@ -13,8 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define DEBUG false
+#include "Log.h"
+
#include "incidentd_util.h"
+#include <sys/prctl.h>
+
#include "section_list.h"
const Privacy* get_privacy_of_section(int id) {
@@ -50,4 +55,49 @@
int Fpipe::readFd() const { return mRead.get(); }
-int Fpipe::writeFd() const { return mWrite.get(); }
\ No newline at end of file
+int Fpipe::writeFd() const { return mWrite.get(); }
+
+pid_t fork_execute_cmd(const char* cmd, char* const argv[], Fpipe* input, Fpipe* output) {
+ // fork used in multithreaded environment, avoid adding unnecessary code in child process
+ pid_t pid = fork();
+ if (pid == 0) {
+ if (TEMP_FAILURE_RETRY(dup2(input->readFd(), STDIN_FILENO)) < 0 || !input->close() ||
+ TEMP_FAILURE_RETRY(dup2(output->writeFd(), STDOUT_FILENO)) < 0 || !output->close()) {
+ ALOGW("Can't setup stdin and stdout for command %s", cmd);
+ _exit(EXIT_FAILURE);
+ }
+
+ /* make sure the child dies when incidentd dies */
+ prctl(PR_SET_PDEATHSIG, SIGKILL);
+
+ execv(cmd, argv);
+
+ ALOGW("%s failed in the child process: %s", cmd, strerror(errno));
+ _exit(EXIT_FAILURE); // always exits with failure if any
+ }
+ // close the fds used in child process.
+ close(input->readFd());
+ close(output->writeFd());
+ return pid;
+}
+// ================================================================================
+const char** varargs(const char* first, va_list rest) {
+ va_list copied_rest;
+ int numOfArgs = 1; // first is already count.
+
+ va_copy(copied_rest, rest);
+ while (va_arg(copied_rest, const char*) != NULL) {
+ numOfArgs++;
+ }
+ va_end(copied_rest);
+
+ // allocate extra 1 for NULL terminator
+ const char** ret = (const char**)malloc(sizeof(const char*) * (numOfArgs + 1));
+ ret[0] = first;
+ for (int i = 0; i < numOfArgs; i++) {
+ const char* arg = va_arg(rest, const char*);
+ ret[i + 1] = arg;
+ }
+ ret[numOfArgs + 1] = NULL;
+ return ret;
+}
diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h
index 09aa040..db7ec82 100644
--- a/cmds/incidentd/src/incidentd_util.h
+++ b/cmds/incidentd/src/incidentd_util.h
@@ -20,12 +20,20 @@
#include <android-base/unique_fd.h>
+#include <stdarg.h>
+
#include "Privacy.h"
using namespace android::base;
+/**
+ * Looks up Privacy of a section in the auto-gen PRIVACY_POLICY_LIST;
+ */
const Privacy* get_privacy_of_section(int id);
+/**
+ * This class wraps android::base::Pipe.
+ */
class Fpipe {
public:
Fpipe();
@@ -41,4 +49,15 @@
unique_fd mWrite;
};
+/**
+ * Forks and exec a command with two pipes, one connects stdin for input,
+ * one connects stdout for output. It returns the pid of the child.
+ */
+pid_t fork_execute_cmd(const char* cmd, char* const argv[], Fpipe* input, Fpipe* output);
+
+/**
+ * Grabs varargs from stack and stores them in heap with NULL-terminated array.
+ */
+const char** varargs(const char* first, va_list rest);
+
#endif // INCIDENTD_UTIL_H
\ No newline at end of file
diff --git a/cmds/incidentd/testdata/kmsg.txt b/cmds/incidentd/testdata/kmsg.txt
new file mode 100644
index 0000000..a8e3c02
--- /dev/null
+++ b/cmds/incidentd/testdata/kmsg.txt
@@ -0,0 +1,47 @@
+[0] bldr_log_init: bldr_log_base=0x83600000, bldr_log_size=458752
+B - 626409 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 729255 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 729285 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 9:0
+D - 104829 - APPSBL Image Loaded, Delta - (2498816 Bytes)
+B - 729468 - SBL1, End
+D - 643611 - SBL1, Delta
+S - Flash Throughput, 129000 KB/s (4729638 Bytes, 36613 us)
+S - DDR Frequency, 1017 MHz
+0x400, 0x400
+B - 482296 - Basic DDR tests done
+B - 544638 - clock_init, Start
+D - 244 - clock_init, Delta
+B - 544913 - HTC RPM DATARAM UPDATE info: Done
+B - 545004 - Image Load, Start
+B - 548359 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 5:0
+D - 3386 - QSEE Dev Config Image Loaded, Delta - (46232 Bytes)
+B - 548725 - Image Load, Start
+B - 550860 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 512:0
+D - 2166 - APDP Image Loaded, Delta - (7696 Bytes)
+B - 550891 - Image Load, Start
+B - 601612 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 7:0
+D - 50782 - QSEE Image Loaded, Delta - (1648640 Bytes)
+B - 601704 - Image Load, Start
+D - 244 - SEC Image Loaded, Delta - (4096 Bytes)
+B - 602344 - 0x1310 = 0x24
+B - 602375 - is_above_vbat_weak = 1, pon_reasons (with usb_in checked) = 0x31
+B - 602466 - sbl1_efs_handle_cookies, Start
+D - 91 - sbl1_efs_handle_cookies, Delta
+B - 602558 - Image Load, Start
+B - 613446 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 21:0
+D - 11010 - QHEE Image Loaded, Delta - (258280 Bytes)
+B - 613568 - Image Load, Start
+B - 624274 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 10:0
+D - 10736 - RPM Image Loaded, Delta - (224104 Bytes)
+B - 624335 - Image Load, Start
+D - 0 - STI Image Loaded, Delta - (0 Bytes)
+B - 624548 - Image Load, Start
+m_driver_init, Delta
+B - 471804 - pm_sbl_chg
+ ******************** [ START SECOND] ********************
+^@B - 736605 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 839451 - [INFO][XBL]: Bypass appsbl verification on DEVELOPMENT device
+B - 839482 - boot_elf_load_and_verify_image: boot_auth_compute_verify_hash for 9:0
+D - 104828 - APPSBL Image Loaded, Delta - (2498816 Bytes)
+B - 839665 - SBL1, End
+D - 753838 - SBL1, Delta
diff --git a/cmds/incidentd/testdata/kmsg.txt.gz b/cmds/incidentd/testdata/kmsg.txt.gz
new file mode 100644
index 0000000..fba449f
--- /dev/null
+++ b/cmds/incidentd/testdata/kmsg.txt.gz
Binary files differ
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 026bf74..1528224 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -39,10 +39,22 @@
using namespace android::os;
using namespace std;
using ::testing::StrEq;
+using ::testing::Test;
using ::testing::internal::CaptureStdout;
using ::testing::internal::GetCapturedStdout;
// NOTICE: this test requires /system/bin/incident_helper is installed.
+class SectionTest : public Test {
+public:
+ virtual void SetUp() override { ASSERT_NE(tf.fd, -1); }
+
+protected:
+ TemporaryFile tf;
+ ReportRequestSet requests;
+
+ const std::string kTestPath = GetExecutableDirectory();
+ const std::string kTestDataPath = kTestPath + "/testdata/";
+};
class SimpleListener : public IIncidentReportStatusListener {
public:
@@ -58,10 +70,8 @@
virtual IBinder* onAsBinder() override { return nullptr; };
};
-TEST(SectionTest, HeaderSection) {
- TemporaryFile output2;
+TEST_F(SectionTest, HeaderSection) {
HeaderSection hs;
- ReportRequestSet requests;
IncidentReportArgs args1, args2;
args1.addSection(1);
@@ -77,7 +87,7 @@
args2.addHeader(head2);
requests.add(new ReportRequest(args1, new SimpleListener(), -1));
- requests.add(new ReportRequest(args2, new SimpleListener(), output2.fd));
+ requests.add(new ReportRequest(args2, new SimpleListener(), tf.fd));
requests.setMainFd(STDOUT_FILENO);
string content;
@@ -87,28 +97,25 @@
"\x12\x3"
"axe\n\x05\x12\x03pup"));
- EXPECT_TRUE(ReadFileToString(output2.path, &content));
+ EXPECT_TRUE(ReadFileToString(tf.path, &content));
EXPECT_THAT(content, StrEq("\n\x05\x12\x03pup"));
}
-TEST(SectionTest, MetadataSection) {
+TEST_F(SectionTest, MetadataSection) {
MetadataSection ms;
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
requests.sectionStats(1)->set_success(true);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ms.Execute(&requests));
- EXPECT_THAT(GetCapturedStdout(), StrEq("\x12\b\x18\x1\"\x4\b\x1\x10\x1"));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x12\b(\x1"
+ "2\x4\b\x1\x10\x1"));
}
-TEST(SectionTest, FileSection) {
- TemporaryFile tf;
+TEST_F(SectionTest, FileSection) {
FileSection fs(REVERSE_PARSER, tf.path);
- ReportRequestSet requests;
- ASSERT_TRUE(tf.fd != -1);
ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path));
requests.setMainFd(STDOUT_FILENO);
@@ -120,66 +127,79 @@
EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\vatadtsetmai"));
}
-TEST(SectionTest, FileSectionTimeout) {
- TemporaryFile tf;
- // id -1 is timeout parser
+TEST_F(SectionTest, FileSectionTimeout) {
FileSection fs(TIMEOUT_PARSER, tf.path, QUICK_TIMEOUT_MS);
- ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionConstructor) {
+TEST_F(SectionTest, GZipSection) {
+ const std::string testFile = kTestDataPath + "kmsg.txt";
+ const std::string testGzFile = testFile + ".gz";
+ GZipSection gs(NOOP_PARSER, "/tmp/nonexist", testFile.c_str(), NULL);
+
+ requests.setMainFd(tf.fd);
+ requests.setMainDest(android::os::DEST_LOCAL);
+
+ ASSERT_EQ(NO_ERROR, gs.Execute(&requests));
+ std::string expect, gzFile, actual;
+ ASSERT_TRUE(ReadFileToString(testGzFile, &gzFile));
+ ASSERT_TRUE(ReadFileToString(tf.path, &actual));
+ expect = "\x2\xC6\x6\n\"" + testFile + "\x12\x9F\x6" + gzFile;
+ EXPECT_THAT(actual, StrEq(expect));
+}
+
+TEST_F(SectionTest, GZipSectionNoFileFound) {
+ GZipSection gs(NOOP_PARSER, "/tmp/nonexist1", "/tmp/nonexist2", NULL);
+ requests.setMainFd(STDOUT_FILENO);
+ ASSERT_EQ(-1, gs.Execute(&requests));
+}
+
+TEST_F(SectionTest, CommandSectionConstructor) {
CommandSection cs1(1, "echo", "\"this is a test\"", "ooo", NULL);
CommandSection cs2(2, "single_command", NULL);
CommandSection cs3(1, 3123, "echo", "\"this is a test\"", "ooo", NULL);
CommandSection cs4(2, 43214, "single_command", NULL);
- EXPECT_THAT(cs1.name.string(), StrEq("echo \"this is a test\" ooo"));
+ EXPECT_THAT(cs1.name.string(), StrEq("echo"));
EXPECT_THAT(cs2.name.string(), StrEq("single_command"));
EXPECT_EQ(3123, cs3.timeoutMs);
EXPECT_EQ(43214, cs4.timeoutMs);
- EXPECT_THAT(cs3.name.string(), StrEq("echo \"this is a test\" ooo"));
+ EXPECT_THAT(cs3.name.string(), StrEq("echo"));
EXPECT_THAT(cs4.name.string(), StrEq("single_command"));
}
-TEST(SectionTest, CommandSectionEcho) {
+TEST_F(SectionTest, CommandSectionEcho) {
CommandSection cs(REVERSE_PARSER, "/system/bin/echo", "about", NULL);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
EXPECT_THAT(GetCapturedStdout(), StrEq("\xa\x06\ntuoba"));
}
-TEST(SectionTest, CommandSectionCommandTimeout) {
+TEST_F(SectionTest, CommandSectionCommandTimeout) {
CommandSection cs(NOOP_PARSER, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
- ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionIncidentHelperTimeout) {
+TEST_F(SectionTest, CommandSectionIncidentHelperTimeout) {
CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionBadCommand) {
+TEST_F(SectionTest, CommandSectionBadCommand) {
CommandSection cs(NOOP_PARSER, "echoo", "about", NULL);
- ReportRequestSet requests;
ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
}
-TEST(SectionTest, CommandSectionBadCommandAndTimeout) {
+TEST_F(SectionTest, CommandSectionBadCommandAndTimeout) {
CommandSection cs(TIMEOUT_PARSER, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
- ReportRequestSet requests;
// timeout will return first
ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
-TEST(SectionTest, LogSectionBinary) {
+TEST_F(SectionTest, LogSectionBinary) {
LogSection ls(1, LOG_ID_EVENTS);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
@@ -187,9 +207,8 @@
EXPECT_FALSE(results.empty());
}
-TEST(SectionTest, LogSectionSystem) {
+TEST_F(SectionTest, LogSectionSystem) {
LogSection ls(1, LOG_ID_SYSTEM);
- ReportRequestSet requests;
requests.setMainFd(STDOUT_FILENO);
CaptureStdout();
ASSERT_EQ(NO_ERROR, ls.Execute(&requests));
@@ -197,12 +216,9 @@
EXPECT_FALSE(results.empty());
}
-TEST(SectionTest, TestFilterPiiTaggedFields) {
- TemporaryFile tf;
+TEST_F(SectionTest, TestFilterPiiTaggedFields) {
FileSection fs(NOOP_PARSER, tf.path);
- ReportRequestSet requests;
- ASSERT_TRUE(tf.fd != -1);
ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
requests.setMainFd(STDOUT_FILENO);
@@ -212,11 +228,9 @@
EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\r" + STRING_FIELD_2));
}
-TEST(SectionTest, TestBadFdRequest) {
- TemporaryFile input;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+TEST_F(SectionTest, TestBadFdRequest) {
+ FileSection fs(NOOP_PARSER, tf.path);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args;
args.setAll(true);
@@ -231,11 +245,9 @@
EXPECT_EQ(badFdRequest->err, -EBADF);
}
-TEST(SectionTest, TestBadRequests) {
- TemporaryFile input;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+TEST_F(SectionTest, TestBadRequests) {
+ FileSection fs(NOOP_PARSER, tf.path);
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args;
args.setAll(true);
@@ -244,16 +256,14 @@
EXPECT_EQ(fs.Execute(&requests), -EBADF);
}
-TEST(SectionTest, TestMultipleRequests) {
- TemporaryFile input, output1, output2, output3;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
+TEST_F(SectionTest, TestMultipleRequests) {
+ TemporaryFile output1, output2, output3;
+ FileSection fs(NOOP_PARSER, tf.path);
- ASSERT_TRUE(input.fd != -1);
ASSERT_TRUE(output1.fd != -1);
ASSERT_TRUE(output2.fd != -1);
ASSERT_TRUE(output3.fd != -1);
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
@@ -286,17 +296,15 @@
EXPECT_THAT(content, StrEq(""));
}
-TEST(SectionTest, TestMultipleRequestsBySpec) {
- TemporaryFile input, output1, output2, output3;
- FileSection fs(NOOP_PARSER, input.path);
- ReportRequestSet requests;
+TEST_F(SectionTest, TestMultipleRequestsBySpec) {
+ TemporaryFile output1, output2, output3;
+ FileSection fs(NOOP_PARSER, tf.path);
- ASSERT_TRUE(input.fd != -1);
ASSERT_TRUE(output1.fd != -1);
ASSERT_TRUE(output2.fd != -1);
ASSERT_TRUE(output3.fd != -1);
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
@@ -328,4 +336,4 @@
c = (char)STRING_FIELD_2.size();
EXPECT_TRUE(ReadFileToString(output3.path, &content));
EXPECT_THAT(content, StrEq(string("\x02") + c + STRING_FIELD_2));
-}
\ No newline at end of file
+}
diff --git a/core/proto/android/os/data.proto b/core/proto/android/os/data.proto
new file mode 100644
index 0000000..c06f318
--- /dev/null
+++ b/core/proto/android/os/data.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.os;
+
+// This file contains protobuf definitions used in incidentd directly.
+// The top level proto message must be used as a new SectionType in
+// incidentd.
+
+// Output of SECTION_GZIP section type, which reads a file, gzip it and attached
+// in incident report as a proto field, example is LAST_KMSG.
+// NOTE the content in the file must not contain sensitive PII otherwise
+// implement it with fine-grained proto definition.
+message GZippedFileProto {
+ optional string filename = 1;
+
+ optional bytes gzipped_data = 2;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 9a53b89..be15597 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -20,6 +20,7 @@
import "frameworks/base/core/proto/android/os/batterytype.proto";
import "frameworks/base/core/proto/android/os/cpufreq.proto";
import "frameworks/base/core/proto/android/os/cpuinfo.proto";
+import "frameworks/base/core/proto/android/os/data.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
import "frameworks/base/core/proto/android/os/procrank.proto";
@@ -52,9 +53,8 @@
package android.os;
-// privacy field options must not be set at this level because all
-// the sections are able to be controlled and configured by section ids.
-// Instead privacy field options need to be configured in each section proto message.
+// Privacy tag can be marked to override UNSET messages so generic
+// message type can be handled case by case, e.g. GZippedFileProto.
message IncidentProto {
reserved 1001;
@@ -151,6 +151,12 @@
(section).args = "/sys/class/power_supply/bms/battery_type"
];
+ optional GZippedFileProto last_kmsg = 2007 [
+ (section).type = SECTION_GZIP,
+ (section).args = "/sys/fs/pstore/console-ramoops /sys/fs/pstore/console-ramoops-0 /proc/last_kmsg",
+ (privacy).dest = DEST_AUTOMATIC
+ ];
+
// System Services
optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
(section).type = SECTION_DUMPSYS,
diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto
index 49bfe1e..ef6a8ff 100644
--- a/libs/incident/proto/android/section.proto
+++ b/libs/incident/proto/android/section.proto
@@ -40,6 +40,9 @@
// incidentd calls logs for annotated field
SECTION_LOG = 4;
+
+ // incidentd read file and gzip the data in bytes field
+ SECTION_GZIP = 5;
}
message SectionFlags {
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
index 0a8a5aa..bf698d4 100644
--- a/libs/protoutil/include/android/util/EncodedBuffer.h
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -154,7 +154,7 @@
void editRawFixed32(size_t pos, uint32_t val);
/**
- * Copy _size_ bytes of data starting at __srcPos__ to wp.
+ * Copy _size_ bytes of data starting at __srcPos__ to wp, srcPos must be larger than wp.pos().
*/
void copy(size_t srcPos, size_t size);
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index e7b269a..9183918 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -411,6 +411,11 @@
case SECTION_LOG:
printf(" new LogSection(%d, %s),\n", field->number(), s.args().c_str());
break;
+ case SECTION_GZIP:
+ printf(" new GZipSection(%d,", field->number());
+ splitAndPrint(s.args());
+ printf(" NULL),\n");
+ break;
}
}
printf(" NULL };\n");