logcat: add --id=<id> for -f option
Clear file rotate content if id signature changes. This allows
logcatd to detect change of property ro.build.id to reset the
recorded data. The id is stored in the the -f filespec as
<filespec>.id.
If the id file is missing or unreadable, do not clear. Allow an
API where chmod 0 to the id file blocks this feature. Does not
work for root.
Add logcat.logrotate_id gTest.
Bug: 30591615
Change-Id: I895749ca7c6d4af57ec57f058a29d8e3af2f8b27
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 04efd68..49943fe 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -283,6 +283,8 @@
" Rotate log every kbytes. Requires -f option\n"
" -n <count>, --rotate-count=<count>\n"
" Sets max number of rotated logs to <count>, default 4\n"
+ " --id=<id> If the signature id for logging to file changes, then clear\n"
+ " the fileset and continue\n"
" -v <format>, --format=<format>\n"
" Sets log print format verb and adverbs, where <format> is:\n"
" brief long process raw tag thread threadtime time\n"
@@ -546,6 +548,7 @@
unsigned long setLogSize = 0;
int getPruneList = 0;
char *setPruneList = NULL;
+ char *setId = NULL;
int printStatistics = 0;
int mode = ANDROID_LOG_RDONLY;
const char *forceFilters = NULL;
@@ -573,6 +576,7 @@
int option_index = 0;
// list of long-argument only strings for later comparison
static const char pid_str[] = "pid";
+ static const char id_str[] = "id";
static const char wrap_str[] = "wrap";
static const char print_str[] = "print";
static const struct option long_options[] = {
@@ -587,6 +591,7 @@
{ "grep", required_argument, NULL, 'e' },
// hidden and undocumented reserved alias for --max-count
{ "head", required_argument, NULL, 'm' },
+ { id_str, required_argument, NULL, 0 },
{ "last", no_argument, NULL, 'L' },
{ "max-count", required_argument, NULL, 'm' },
{ pid_str, required_argument, NULL, 0 },
@@ -612,7 +617,7 @@
switch (ret) {
case 0:
- // One of the long options
+ // only long options
if (long_options[option_index].name == pid_str) {
// ToDo: determine runtime PID_MAX?
if (!getSizeTArg(optarg, &pid, 1)) {
@@ -643,6 +648,10 @@
g_printItAnyways = true;
break;
}
+ if (long_options[option_index].name == id_str) {
+ setId = optarg && optarg[0] ? optarg : NULL;
+ break;
+ }
break;
case 's':
@@ -967,7 +976,20 @@
logcat_panic(true, "-r requires -f as well\n");
}
- setupOutput();
+ if (setId != NULL) {
+ if (g_outputFileName == NULL) {
+ logcat_panic(true, "--id='%s' requires -f as well\n", setId);
+ }
+
+ std::string file_name = android::base::StringPrintf("%s.id", g_outputFileName);
+ std::string file;
+ bool file_ok = android::base::ReadFileToString(file_name, &file);
+ android::base::WriteStringToFile(setId, file_name,
+ S_IRUSR | S_IWUSR, getuid(), getgid());
+ if (!file_ok || (file.compare(setId) == 0)) {
+ setId = NULL;
+ }
+ }
if (hasSetLogFormat == 0) {
const char* logFormat = getenv("ANDROID_PRINTF_LOG");
@@ -1033,7 +1055,7 @@
continue;
}
- if (clearLog) {
+ if (clearLog || setId) {
if (g_outputFileName) {
int maxRotationCountDigits =
(g_maxRotatedLogs > 0) ? (int) (floor(log10(g_maxRotatedLogs) + 1)) : 0;
@@ -1175,7 +1197,6 @@
return EXIT_SUCCESS;
}
-
if (getLogSize) {
return EXIT_SUCCESS;
}
@@ -1186,6 +1207,8 @@
return EXIT_SUCCESS;
}
+ setupOutput(mode);
+
//LOG_EVENT_INT(10, 12345);
//LOG_EVENT_LONG(11, 0x1122334455667788LL);
//LOG_EVENT_STRING(0, "whassup, doc?");
diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp
index 0043d1b..3daee13 100644
--- a/logcat/tests/logcat_test.cpp
+++ b/logcat/tests/logcat_test.cpp
@@ -20,6 +20,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -848,6 +849,75 @@
EXPECT_FALSE(system(command));
}
+static int logrotate_count_id(const char *logcat_cmd, const char *tmp_out_dir) {
+
+ static const char log_filename[] = "log.txt";
+ char command[strlen(tmp_out_dir) + strlen(logcat_cmd) + strlen(log_filename) + 32];
+
+ snprintf(command, sizeof(command), logcat_cmd, tmp_out_dir, log_filename);
+
+ int ret;
+ EXPECT_FALSE((ret = system(command)));
+ if (ret) {
+ return -1;
+ }
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(tmp_out_dir), closedir);
+ EXPECT_NE(nullptr, dir);
+ if (!dir) {
+ return -1;
+ }
+ struct dirent *entry;
+ int count = 0;
+ while ((entry = readdir(dir.get()))) {
+ if (strncmp(entry->d_name, log_filename, sizeof(log_filename) - 1)) {
+ continue;
+ }
+ ++count;
+ }
+ return count;
+}
+
+TEST(logcat, logrotate_id) {
+ static const char logcat_cmd[] = "logcat -b all -d -f %s/%s -n 32 -r 1 --id=test";
+ static const char logcat_short_cmd[] = "logcat -b all -t 10 -f %s/%s -n 32 -r 1 --id=test";
+ static const char tmp_out_dir_form[] = "/data/local/tmp/logcat.logrotate.XXXXXX";
+ static const char log_filename[] = "log.txt";
+ char tmp_out_dir[strlen(tmp_out_dir_form) + 1];
+ ASSERT_TRUE(NULL != mkdtemp(strcpy(tmp_out_dir, tmp_out_dir_form)));
+
+ EXPECT_EQ(34, logrotate_count_id(logcat_cmd, tmp_out_dir));
+ EXPECT_EQ(34, logrotate_count_id(logcat_short_cmd, tmp_out_dir));
+
+ char id_file[strlen(tmp_out_dir_form) + strlen(log_filename) + 5];
+ snprintf(id_file, sizeof(id_file), "%s/%s.id", tmp_out_dir, log_filename);
+ if (getuid() != 0) {
+ chmod(id_file, 0);
+ EXPECT_EQ(34, logrotate_count_id(logcat_short_cmd, tmp_out_dir));
+ }
+ unlink(id_file);
+ EXPECT_EQ(34, logrotate_count_id(logcat_short_cmd, tmp_out_dir));
+
+ FILE *fp = fopen(id_file, "w");
+ if (fp) {
+ fprintf(fp, "not_a_test");
+ fclose(fp);
+ }
+ if (getuid() != 0) {
+ chmod(id_file, 0); // API to preserve content even with signature change
+ ASSERT_EQ(34, logrotate_count_id(logcat_short_cmd, tmp_out_dir));
+ chmod(id_file, 0600);
+ }
+
+ int new_signature;
+ EXPECT_LE(2, (new_signature = logrotate_count_id(logcat_short_cmd, tmp_out_dir)));
+ EXPECT_GT(34, new_signature);
+
+ static const char cleanup_cmd[] = "rm -rf %s";
+ char command[strlen(cleanup_cmd) + strlen(tmp_out_dir_form)];
+ snprintf(command, sizeof(command), cleanup_cmd, tmp_out_dir);
+ EXPECT_FALSE(system(command));
+}
+
TEST(logcat, logrotate_nodir) {
// expect logcat to error out on writing content and exit(1) for nodir
EXPECT_EQ(W_EXITCODE(1, 0),