Merge "Set mode bits for /system/etc/recovery.img."
diff --git a/adb/Android.mk b/adb/Android.mk
index 7977009..355bb7f 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -48,6 +48,11 @@
$(ADB_COMMON_CFLAGS) \
-fvisibility=hidden \
+LIBADB_linux_CFLAGS := \
+ -std=c++14 \
+
+LIBADB_CFLAGS += $(LIBADB_$(HOST_OS)_CFLAGS)
+
LIBADB_darwin_SRC_FILES := \
fdevent.cpp \
get_my_path_darwin.cpp \
diff --git a/adb/__init__.py b/adb/__init__.py
new file mode 100644
index 0000000..6b509c6
--- /dev/null
+++ b/adb/__init__.py
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2015 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.
+#
+from __future__ import absolute_import
+from .device import * # pylint: disable=wildcard-import
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 2e59634..97ce125 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -41,6 +41,7 @@
#include "adb_auth.h"
#include "adb_io.h"
#include "adb_listeners.h"
+#include "adb_utils.h"
#include "transport.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
@@ -174,7 +175,7 @@
for (const auto& elem : elements) {
const auto& flag = trace_flags.find(elem);
if (flag == trace_flags.end()) {
- D("Unknown trace flag: %s", flag->first.c_str());
+ D("Unknown trace flag: %s\n", flag->first.c_str());
continue;
}
@@ -333,10 +334,10 @@
D("Calling send_connect \n");
apacket *cp = get_apacket();
cp->msg.command = A_CNXN;
- cp->msg.arg0 = A_VERSION;
- cp->msg.arg1 = MAX_PAYLOAD;
+ cp->msg.arg0 = t->get_protocol_version();
+ cp->msg.arg1 = t->get_max_payload();
cp->msg.data_length = fill_connect_data((char *)cp->data,
- sizeof(cp->data));
+ MAX_PAYLOAD_V1);
send_packet(cp, t);
}
@@ -423,12 +424,12 @@
return;
case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
- /* XXX verify version, etc */
if(t->connection_state != kCsOffline) {
t->connection_state = kCsOffline;
handle_offline(t);
}
+ t->update_version(p->msg.arg0, p->msg.arg1);
parse_banner(reinterpret_cast<const char*>(p->data), t);
if (HOST || !auth_required) {
@@ -896,28 +897,28 @@
// remove TCP transport
if (!strncmp(service, "disconnect:", 11)) {
- char buffer[4096];
- memset(buffer, 0, sizeof(buffer));
- const char* serial = service + 11;
- if (serial[0] == 0) {
+ const std::string address(service + 11);
+ if (address.empty()) {
// disconnect from all TCP devices
unregister_all_tcp_transports();
- } else {
- char hostbuf[100];
- // assume port 5555 if no port is specified
- if (!strchr(serial, ':')) {
- snprintf(hostbuf, sizeof(hostbuf) - 1, "%s:5555", serial);
- serial = hostbuf;
- }
- atransport* t = find_transport(serial);
- if (t) {
- unregister_transport(t);
- } else {
- snprintf(buffer, sizeof(buffer), "No such device %s", serial);
- }
+ return SendOkay(reply_fd, "disconnected everything");
}
- return SendOkay(reply_fd, buffer);
+ std::string serial;
+ std::string host;
+ int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
+ std::string error;
+ if (!parse_host_and_port(address, &serial, &host, &port, &error)) {
+ return SendFail(reply_fd, android::base::StringPrintf("couldn't parse '%s': %s",
+ address.c_str(), error.c_str()));
+ }
+ atransport* t = find_transport(serial.c_str());
+ if (t == nullptr) {
+ return SendFail(reply_fd, android::base::StringPrintf("no such device '%s'",
+ serial.c_str()));
+ }
+ unregister_transport(t);
+ return SendOkay(reply_fd, android::base::StringPrintf("disconnected %s", address.c_str()));
}
// returns our value for ADB_SERVER_VERSION
diff --git a/adb/adb.h b/adb/adb.h
index 1be83d7..309b0e9 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -22,10 +22,14 @@
#include <base/macros.h>
+#include <string>
+
#include "adb_trace.h"
#include "fdevent.h"
-#define MAX_PAYLOAD 4096
+constexpr size_t MAX_PAYLOAD_V1 = 4 * 1024;
+constexpr size_t MAX_PAYLOAD_V2 = 256 * 1024;
+constexpr size_t MAX_PAYLOAD = MAX_PAYLOAD_V2;
#define A_SYNC 0x434e5953
#define A_CNXN 0x4e584e43
@@ -137,6 +141,8 @@
/* A socket is bound to atransport */
atransport *transport;
+
+ size_t get_max_payload() const;
};
@@ -193,6 +199,8 @@
atransport() {
auth_fde = {};
transport_fde = {};
+ protocol_version = A_VERSION;
+ max_payload = MAX_PAYLOAD;
}
virtual ~atransport() {}
@@ -234,7 +242,14 @@
const char* connection_state_name() const;
+ void update_version(int version, size_t payload);
+ int get_protocol_version() const;
+ size_t get_max_payload() const;
+
private:
+ int protocol_version;
+ size_t max_payload;
+
DISALLOW_COPY_AND_ASSIGN(atransport);
};
@@ -344,8 +359,8 @@
void local_init(int port);
-int local_connect(int port);
-int local_connect_arbitrary_ports(int console_port, int adb_port);
+void local_connect(int port);
+int local_connect_arbitrary_ports(int console_port, int adb_port, std::string* error);
/* usb host/client interface */
void usb_init();
@@ -365,7 +380,9 @@
extern const char *adb_device_banner;
extern int HOST;
+#if !ADB_HOST
extern int SHELL_EXIT_NOTIFY_FD;
+#endif // !ADB_HOST
#define CHUNK_SIZE (64*1024)
diff --git a/adb/adb_auth.cpp b/adb/adb_auth.cpp
index cff26d6..8a6b156 100644
--- a/adb/adb_auth.cpp
+++ b/adb/adb_auth.cpp
@@ -75,7 +75,7 @@
apacket *p = get_apacket();
int ret;
- ret = adb_auth_get_userkey(p->data, sizeof(p->data));
+ ret = adb_auth_get_userkey(p->data, MAX_PAYLOAD_V1);
if (!ret) {
D("Failed to get user public key\n");
put_apacket(p);
diff --git a/adb/adb_auth_client.cpp b/adb/adb_auth_client.cpp
index 8e7d38b..be28202 100644
--- a/adb/adb_auth_client.cpp
+++ b/adb/adb_auth_client.cpp
@@ -54,7 +54,7 @@
static void read_keys(const char *file, struct listnode *list)
{
FILE *f;
- char buf[MAX_PAYLOAD];
+ char buf[MAX_PAYLOAD_V1];
char *sep;
int ret;
@@ -191,7 +191,7 @@
void adb_auth_confirm_key(unsigned char *key, size_t len, atransport *t)
{
- char msg[MAX_PAYLOAD];
+ char msg[MAX_PAYLOAD_V1];
int ret;
if (!usb_transport) {
@@ -212,7 +212,7 @@
ret = snprintf(msg, sizeof(msg), "PK%s", key);
if (ret >= (signed)sizeof(msg)) {
- D("Key too long. ret=%d", ret);
+ D("Key too long. ret=%d\n", ret);
return;
}
D("Sending '%s'\n", msg);
diff --git a/adb/adb_auth_host.cpp b/adb/adb_auth_host.cpp
index e878f8b..b6bb00c 100644
--- a/adb/adb_auth_host.cpp
+++ b/adb/adb_auth_host.cpp
@@ -157,7 +157,7 @@
{
RSAPublicKey pkey;
FILE *outfile = NULL;
- char path[PATH_MAX], info[MAX_PAYLOAD];
+ char path[PATH_MAX], info[MAX_PAYLOAD_V1];
uint8_t* encoded = nullptr;
size_t encoded_length;
int ret = 0;
@@ -183,7 +183,7 @@
#if defined(OPENSSL_IS_BORINGSSL)
if (!EVP_EncodedLength(&encoded_length, sizeof(pkey))) {
- D("Public key too large to base64 encode");
+ D("Public key too large to base64 encode\n");
goto out;
}
#else
@@ -194,7 +194,7 @@
encoded = new uint8_t[encoded_length];
if (encoded == nullptr) {
- D("Allocation failure");
+ D("Allocation failure\n");
goto out;
}
@@ -203,7 +203,7 @@
if (fwrite(encoded, encoded_length, 1, outfile) != 1 ||
fwrite(info, strlen(info), 1, outfile) != 1) {
- D("Write error while writing public key");
+ D("Write error while writing public key\n");
goto out;
}
@@ -323,7 +323,7 @@
if (stat(android_dir, &buf)) {
if (adb_mkdir(android_dir, 0750) < 0) {
- D("Cannot mkdir '%s'", android_dir);
+ D("Cannot mkdir '%s'\n", android_dir);
return -1;
}
}
@@ -339,7 +339,7 @@
ret = get_user_keyfilepath(path, sizeof(path));
if (ret < 0 || ret >= (signed)sizeof(path)) {
- D("Error getting user key filename");
+ D("Error getting user key filename\n");
return 0;
}
@@ -414,7 +414,7 @@
char path[PATH_MAX];
int ret = get_user_keyfilepath(path, sizeof(path) - 4);
if (ret < 0 || ret >= (signed)(sizeof(path) - 4)) {
- D("Error getting user key filename");
+ D("Error getting user key filename\n");
return 0;
}
strcat(path, ".pub");
diff --git a/adb/adb_client.cpp b/adb/adb_client.cpp
index ef9a586..418662c 100644
--- a/adb/adb_client.cpp
+++ b/adb/adb_client.cpp
@@ -33,8 +33,10 @@
#include <base/stringprintf.h>
#include <base/strings.h>
+#include <cutils/sockets.h>
#include "adb_io.h"
+#include "adb_utils.h"
static TransportType __adb_transport = kTransportAny;
static const char* __adb_serial = NULL;
@@ -152,13 +154,20 @@
int fd;
if (__adb_server_name) {
- fd = socket_network_client(__adb_server_name, __adb_server_port, SOCK_STREAM);
+ std::string reason;
+ fd = network_connect(__adb_server_name, __adb_server_port, SOCK_STREAM, 0, &reason);
+ if (fd == -1) {
+ *error = android::base::StringPrintf("can't connect to %s:%d: %s",
+ __adb_server_name, __adb_server_port,
+ reason.c_str());
+ return -2;
+ }
} else {
fd = socket_loopback_client(__adb_server_port, SOCK_STREAM);
- }
- if (fd < 0) {
- *error = perror_str("cannot connect to daemon");
- return -2;
+ if (fd == -1) {
+ *error = perror_str("cannot connect to daemon");
+ return -2;
+ }
}
if (memcmp(&service[0],"host",4) != 0 && switch_socket_transport(fd, error)) {
@@ -243,7 +252,7 @@
fd = _adb_connect(service, error);
if (fd == -1) {
- D("_adb_connect error: %s", error->c_str());
+ D("_adb_connect error: %s\n", error->c_str());
} else if(fd == -2) {
fprintf(stderr,"** daemon still not running\n");
}
@@ -277,8 +286,7 @@
D("adb_query: %s\n", service.c_str());
int fd = adb_connect(service, error);
if (fd < 0) {
- fprintf(stderr,"error: %s\n", error->c_str());
- return 0;
+ return false;
}
result->clear();
diff --git a/adb/adb_listeners.cpp b/adb/adb_listeners.cpp
index 641b537..bb45022 100644
--- a/adb/adb_listeners.cpp
+++ b/adb/adb_listeners.cpp
@@ -20,6 +20,7 @@
#include <stdlib.h>
#include <base/stringprintf.h>
+#include <cutils/sockets.h>
#include "sysdeps.h"
#include "transport.h"
@@ -142,8 +143,10 @@
continue;
}
// <device-serial> " " <local-name> " " <remote-name> "\n"
+ // Entries from "adb reverse" have no serial.
android::base::StringAppendF(&result, "%s %s %s\n",
- l->transport->serial, l->local_name, l->connect_to);
+ l->transport->serial ? l->transport->serial : "(reverse)",
+ l->local_name, l->connect_to);
}
return result;
}
diff --git a/adb/adb_utils.cpp b/adb/adb_utils.cpp
index 4b97a14..cd3c7bc 100644
--- a/adb/adb_utils.cpp
+++ b/adb/adb_utils.cpp
@@ -25,11 +25,20 @@
#include <algorithm>
+#include <base/logging.h>
#include <base/stringprintf.h>
+#include <base/strings.h>
+#include <cutils/sockets.h>
#include "adb_trace.h"
#include "sysdeps.h"
+#if defined(_WIN32)
+#include <ws2tcpip.h>
+#else
+#include <netdb.h>
+#endif
+
bool getcwd(std::string* s) {
char* cwd = getcwd(nullptr, 0);
if (cwd != nullptr) *s = cwd;
@@ -103,3 +112,71 @@
DR("%s\n", line.c_str());
}
+
+bool parse_host_and_port(const std::string& address,
+ std::string* canonical_address,
+ std::string* host, int* port,
+ std::string* error) {
+ host->clear();
+
+ bool ipv6 = true;
+ bool saw_port = false;
+ size_t colons = std::count(address.begin(), address.end(), ':');
+ size_t dots = std::count(address.begin(), address.end(), '.');
+ std::string port_str;
+ if (address[0] == '[') {
+ // [::1]:123
+ if (address.rfind("]:") == std::string::npos) {
+ *error = android::base::StringPrintf("bad IPv6 address '%s'", address.c_str());
+ return false;
+ }
+ *host = address.substr(1, (address.find("]:") - 1));
+ port_str = address.substr(address.rfind("]:") + 2);
+ saw_port = true;
+ } else if (dots == 0 && colons >= 2 && colons <= 7) {
+ // ::1
+ *host = address;
+ } else if (colons <= 1) {
+ // 1.2.3.4 or some.accidental.domain.com
+ ipv6 = false;
+ std::vector<std::string> pieces = android::base::Split(address, ":");
+ *host = pieces[0];
+ if (pieces.size() > 1) {
+ port_str = pieces[1];
+ saw_port = true;
+ }
+ }
+
+ if (host->empty()) {
+ *error = android::base::StringPrintf("no host in '%s'", address.c_str());
+ return false;
+ }
+
+ if (saw_port) {
+ if (sscanf(port_str.c_str(), "%d", port) != 1 || *port <= 0 || *port > 65535) {
+ *error = android::base::StringPrintf("bad port number '%s' in '%s'",
+ port_str.c_str(), address.c_str());
+ return false;
+ }
+ }
+
+ *canonical_address = android::base::StringPrintf(ipv6 ? "[%s]:%d" : "%s:%d", host->c_str(), *port);
+ LOG(DEBUG) << "parsed " << address << " as " << *host << " and " << *port
+ << " (" << *canonical_address << ")";
+ return true;
+}
+
+int network_connect(const std::string& host, int port, int type, int timeout, std::string* error) {
+ int getaddrinfo_error = 0;
+ int fd = socket_network_client_timeout(host.c_str(), port, type, timeout, &getaddrinfo_error);
+ if (fd != -1) {
+ return fd;
+ }
+ if (getaddrinfo_error != 0) {
+ // TODO: gai_strerror is not thread safe on Win32.
+ *error = gai_strerror(getaddrinfo_error);
+ } else {
+ *error = strerror(errno);
+ }
+ return -1;
+}
diff --git a/adb/adb_utils.h b/adb/adb_utils.h
index c816d00..d1a3f5f 100644
--- a/adb/adb_utils.h
+++ b/adb/adb_utils.h
@@ -28,4 +28,17 @@
void dump_hex(const void* ptr, size_t byte_count);
+// Parses 'address' into 'host' and 'port'.
+// If no port is given, takes the default from *port.
+// 'canonical_address' then becomes "host:port" or "[host]:port" as appropriate.
+// Note that no real checking is done that 'host' or 'port' is valid; that's
+// left to getaddrinfo(3).
+// Returns false on failure and sets *error to an appropriate message.
+bool parse_host_and_port(const std::string& address,
+ std::string* canonical_address,
+ std::string* host, int* port,
+ std::string* error);
+
+int network_connect(const std::string& host, int port, int type, int timeout, std::string* error);
+
#endif
diff --git a/adb/adb_utils_test.cpp b/adb/adb_utils_test.cpp
index 052aea5..7aa610a 100644
--- a/adb/adb_utils_test.cpp
+++ b/adb/adb_utils_test.cpp
@@ -50,3 +50,85 @@
ASSERT_EQ(R"('abc(')", escape_arg("abc("));
ASSERT_EQ(R"('abc)')", escape_arg("abc)"));
}
+
+TEST(adb_utils, parse_host_and_port) {
+ std::string canonical_address;
+ std::string host;
+ int port;
+ std::string error;
+
+ // Name, default port.
+ port = 123;
+ ASSERT_TRUE(parse_host_and_port("www.google.com", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("www.google.com:123", canonical_address);
+ ASSERT_EQ("www.google.com", host);
+ ASSERT_EQ(123, port);
+
+ // Name, explicit port.
+ ASSERT_TRUE(parse_host_and_port("www.google.com:666", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("www.google.com:666", canonical_address);
+ ASSERT_EQ("www.google.com", host);
+ ASSERT_EQ(666, port);
+
+ // IPv4, default port.
+ port = 123;
+ ASSERT_TRUE(parse_host_and_port("1.2.3.4", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("1.2.3.4:123", canonical_address);
+ ASSERT_EQ("1.2.3.4", host);
+ ASSERT_EQ(123, port);
+
+ // IPv4, explicit port.
+ ASSERT_TRUE(parse_host_and_port("1.2.3.4:666", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("1.2.3.4:666", canonical_address);
+ ASSERT_EQ("1.2.3.4", host);
+ ASSERT_EQ(666, port);
+
+ // Simple IPv6, default port.
+ port = 123;
+ ASSERT_TRUE(parse_host_and_port("::1", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("[::1]:123", canonical_address);
+ ASSERT_EQ("::1", host);
+ ASSERT_EQ(123, port);
+
+ // Simple IPv6, explicit port.
+ ASSERT_TRUE(parse_host_and_port("[::1]:666", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("[::1]:666", canonical_address);
+ ASSERT_EQ("::1", host);
+ ASSERT_EQ(666, port);
+
+ // Hairy IPv6, default port.
+ port = 123;
+ ASSERT_TRUE(parse_host_and_port("fe80::200:5aee:feaa:20a2", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("[fe80::200:5aee:feaa:20a2]:123", canonical_address);
+ ASSERT_EQ("fe80::200:5aee:feaa:20a2", host);
+ ASSERT_EQ(123, port);
+
+ // Simple IPv6, explicit port.
+ ASSERT_TRUE(parse_host_and_port("[fe80::200:5aee:feaa:20a2]:666", &canonical_address, &host, &port, &error));
+ ASSERT_EQ("[fe80::200:5aee:feaa:20a2]:666", canonical_address);
+ ASSERT_EQ("fe80::200:5aee:feaa:20a2", host);
+ ASSERT_EQ(666, port);
+
+ // Invalid IPv4.
+ EXPECT_FALSE(parse_host_and_port("1.2.3.4:", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("1.2.3.4::", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("1.2.3.4:hello", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port(":123", &canonical_address, &host, &port, &error));
+
+ // Invalid IPv6.
+ EXPECT_FALSE(parse_host_and_port(":1", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("::::::::1", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("[::1", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("[::1]", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("[::1]:", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("[::1]::", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("[::1]:hello", &canonical_address, &host, &port, &error));
+
+ // Invalid ports.
+ EXPECT_FALSE(parse_host_and_port("[::1]:-1", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("[::1]:0", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("[::1]:65536", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("1.2.3.4:-1", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("1.2.3.4:0", &canonical_address, &host, &port, &error));
+ EXPECT_FALSE(parse_host_and_port("1.2.3.4:65536", &canonical_address, &host, &port, &error));
+}
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 4e0db2b..f190336 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -302,13 +302,32 @@
if (buf == nullptr) fatal("couldn't allocate buffer for copy_to_file");
int len;
long total = 0;
+#ifdef _WIN32
+ int old_stdin_mode = -1;
+ int old_stdout_mode = -1;
+#endif
D("copy_to_file(%d -> %d)\n", inFd, outFd);
if (inFd == STDIN_FILENO) {
stdin_raw_init(STDIN_FILENO);
+#ifdef _WIN32
+ old_stdin_mode = _setmode(STDIN_FILENO, _O_BINARY);
+ if (old_stdin_mode == -1) {
+ fatal_errno("could not set stdin to binary");
+ }
+#endif
}
+#ifdef _WIN32
+ if (outFd == STDOUT_FILENO) {
+ old_stdout_mode = _setmode(STDOUT_FILENO, _O_BINARY);
+ if (old_stdout_mode == -1) {
+ fatal_errno("could not set stdout to binary");
+ }
+ }
+#endif
+
while (true) {
if (inFd == STDIN_FILENO) {
len = unix_read(inFd, buf, BUFSIZE);
@@ -338,8 +357,21 @@
if (inFd == STDIN_FILENO) {
stdin_raw_restore(STDIN_FILENO);
+#ifdef _WIN32
+ if (_setmode(STDIN_FILENO, old_stdin_mode) == -1) {
+ fatal_errno("could not restore stdin mode");
+ }
+#endif
}
+#ifdef _WIN32
+ if (outFd == STDOUT_FILENO) {
+ if (_setmode(STDOUT_FILENO, old_stdout_mode) == -1) {
+ fatal_errno("could not restore stdout mode");
+ }
+ }
+#endif
+
D("copy_to_file() finished after %lu bytes\n", total);
free(buf);
}
@@ -1200,10 +1232,12 @@
return 0;
}
}
+ else if (!strcmp(argv[0], "tcpip") && argc > 1) {
+ return adb_connect_command(android::base::StringPrintf("tcpip:%s", argv[1]));
+ }
else if (!strcmp(argv[0], "remount") ||
!strcmp(argv[0], "reboot") ||
!strcmp(argv[0], "reboot-bootloader") ||
- !strcmp(argv[0], "tcpip") ||
!strcmp(argv[0], "usb") ||
!strcmp(argv[0], "root") ||
!strcmp(argv[0], "unroot") ||
diff --git a/adb/console.cpp b/adb/console.cpp
index 0707960..b7f5345 100644
--- a/adb/console.cpp
+++ b/adb/console.cpp
@@ -18,9 +18,10 @@
#include <stdio.h>
-#include "base/file.h"
-#include "base/logging.h"
-#include "base/strings.h"
+#include <base/file.h>
+#include <base/logging.h>
+#include <base/strings.h>
+#include <cutils/sockets.h>
#include "adb.h"
#include "adb_client.h"
diff --git a/adb/device.py b/adb/device.py
new file mode 100644
index 0000000..601989b
--- /dev/null
+++ b/adb/device.py
@@ -0,0 +1,233 @@
+#
+# Copyright (C) 2015 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.
+#
+import os
+import re
+import subprocess
+
+
+class FindDeviceError(RuntimeError):
+ pass
+
+
+class DeviceNotFoundError(FindDeviceError):
+ def __init__(self, serial):
+ self.serial = serial
+ super(DeviceNotFoundError, self).__init__(
+ 'No device with serial {}'.format(serial))
+
+
+class NoUniqueDeviceError(FindDeviceError):
+ def __init__(self):
+ super(NoUniqueDeviceError, self).__init__('No unique device')
+
+
+def get_devices():
+ with open(os.devnull, 'wb') as devnull:
+ subprocess.check_call(['adb', 'start-server'], stdout=devnull,
+ stderr=devnull)
+ out = subprocess.check_output(['adb', 'devices']).splitlines()
+
+ # The first line of `adb devices` just says "List of attached devices", so
+ # skip that.
+ devices = []
+ for line in out[1:]:
+ if not line.strip():
+ continue
+ if 'offline' in line:
+ continue
+
+ serial, _ = re.split(r'\s+', line, maxsplit=1)
+ devices.append(serial)
+ return devices
+
+
+def _get_unique_device(product=None):
+ devices = get_devices()
+ if len(devices) != 1:
+ raise NoUniqueDeviceError()
+ return AndroidDevice(devices[0], product)
+
+def _get_device_by_serial(serial, product=None):
+ for device in get_devices():
+ if device == serial:
+ return AndroidDevice(serial, product)
+ raise DeviceNotFoundError(serial)
+
+
+def get_device(serial=None, product=None):
+ """Get a uniquely identified AndroidDevice if one is available.
+
+ Raises:
+ DeviceNotFoundError:
+ The serial specified by `serial` or $ANDROID_SERIAL is not
+ connected.
+
+ NoUniqueDeviceError:
+ Neither `serial` nor $ANDROID_SERIAL was set, and the number of
+ devices connected to the system is not 1. Having 0 connected
+ devices will also result in this error.
+
+ Returns:
+ An AndroidDevice associated with the first non-None identifier in the
+ following order of preference:
+
+ 1) The `serial` argument.
+ 2) The environment variable $ANDROID_SERIAL.
+ 3) The single device connnected to the system.
+ """
+ if serial is not None:
+ return _get_device_by_serial(serial, product)
+
+ android_serial = os.getenv('ANDROID_SERIAL')
+ if android_serial is not None:
+ return _get_device_by_serial(android_serial, product)
+
+ return _get_unique_device(product)
+
+
+class AndroidDevice(object):
+ def __init__(self, serial, product=None):
+ self.serial = serial
+ self.product = product
+ self.adb_cmd = ['adb']
+ if self.serial is not None:
+ self.adb_cmd.extend(['-s', serial])
+ if self.product is not None:
+ self.adb_cmd.extend(['-p', product])
+ self._linesep = None
+ self._shell_result_pattern = None
+
+ @property
+ def linesep(self):
+ if self._linesep is None:
+ self._linesep = subprocess.check_output(['adb', 'shell', 'echo'])
+ return self._linesep
+
+ def _make_shell_cmd(self, user_cmd):
+ # Follow any shell command with `; echo; echo $?` to get the exit
+ # status of a program since this isn't propagated by adb.
+ #
+ # The leading newline is needed because `printf 1; echo $?` would print
+ # "10", and we wouldn't be able to distinguish the exit code.
+ rc_probe = '; echo "\n$?"'
+ return self.adb_cmd + ['shell'] + user_cmd + [rc_probe]
+
+ def _parse_shell_output(self, out): # pylint: disable=no-self-use
+ search_text = out
+ max_result_len = len('{0}255{0}'.format(self.linesep))
+ if len(search_text) > max_result_len:
+ # We don't want to regex match over massive amounts of data when we
+ # know the part we want is right at the end.
+ search_text = search_text[-max_result_len:]
+ if self._shell_result_pattern is None:
+ self._shell_result_pattern = re.compile(
+ r'({0}\d+{0})$'.format(self.linesep), re.MULTILINE)
+ m = self._shell_result_pattern.search(search_text)
+ if m is None:
+ raise RuntimeError('Could not find exit status in shell output.')
+
+ result_text = m.group(1)
+ result = int(result_text.strip())
+ out = out[:-len(result_text)] # Trim the result text from the output.
+ return result, out
+
+ def _simple_call(self, cmd):
+ return subprocess.check_output(
+ self.adb_cmd + cmd, stderr=subprocess.STDOUT)
+
+ def shell(self, cmd):
+ cmd = self._make_shell_cmd(cmd)
+ out = subprocess.check_output(cmd)
+ rc, out = self._parse_shell_output(out)
+ if rc != 0:
+ error = subprocess.CalledProcessError(rc, cmd)
+ error.out = out
+ raise error
+ return out
+
+ def shell_nocheck(self, cmd):
+ cmd = self._make_shell_cmd(cmd)
+ p = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ out, _ = p.communicate()
+ return self._parse_shell_output(out)
+
+ def install(self, filename):
+ return self._simple_call(['install', filename])
+
+ def push(self, local, remote):
+ return self._simple_call(['push', local, remote])
+
+ def pull(self, remote, local):
+ return self._simple_call(['pull', remote, local])
+
+ def sync(self, directory=None):
+ cmd = ['sync']
+ if directory is not None:
+ cmd.append(directory)
+ return self._simple_call(cmd)
+
+ def forward(self, local, remote):
+ return self._simple_call(['forward', local, remote])
+
+ def tcpip(self, port):
+ return self._simple_call(['tcpip', port])
+
+ def usb(self):
+ return self._simple_call(['usb'])
+
+ def root(self):
+ return self._simple_call(['root'])
+
+ def unroot(self):
+ return self._simple_call(['unroot'])
+
+ def forward_remove(self, local):
+ return self._simple_call(['forward', '--remove', local])
+
+ def forward_remove_all(self):
+ return self._simple_call(['forward', '--remove-all'])
+
+ def connect(self, host):
+ return self._simple_call(['connect', host])
+
+ def disconnect(self, host):
+ return self._simple_call(['disconnect', host])
+
+ def reverse(self, remote, local):
+ return self._simple_call(['reverse', remote, local])
+
+ def reverse_remove_all(self):
+ return self._simple_call(['reverse', '--remove-all'])
+
+ def reverse_remove(self, remote):
+ return self._simple_call(['reverse', '--remove', remote])
+
+ def wait(self):
+ return self._simple_call(['wait-for-device'])
+
+ def get_prop(self, prop_name):
+ output = self.shell(['getprop', prop_name])
+ if len(output) != 1:
+ raise RuntimeError('Too many lines in getprop output:\n' +
+ '\n'.join(output))
+ value = output[0]
+ if not value.strip():
+ return None
+ return value
+
+ def set_prop(self, prop_name, value):
+ self.shell(['setprop', prop_name, value])
diff --git a/adb/fdevent.cpp b/adb/fdevent.cpp
index 0c43c5e..5cd4988 100644
--- a/adb/fdevent.cpp
+++ b/adb/fdevent.cpp
@@ -42,7 +42,9 @@
// This socket is used when a subproc shell service exists.
// It wakes up the fdevent_loop() and cause the correct handling
// of the shell's pseudo-tty master. I.e. force close it.
+#if !ADB_HOST
int SHELL_EXIT_NOTIFY_FD = -1;
+#endif // !ADB_HOST
static void fatal(const char *fn, const char *fmt, ...)
{
@@ -81,7 +83,6 @@
static void fdevent_plist_enqueue(fdevent *node);
static void fdevent_plist_remove(fdevent *node);
static fdevent *fdevent_plist_dequeue(void);
-static void fdevent_subproc_event_func(int fd, unsigned events, void *userdata);
static fdevent list_pending = {
.next = &list_pending,
@@ -510,6 +511,7 @@
fde->func(fde->fd, events, fde->arg);
}
+#if !ADB_HOST
static void fdevent_subproc_event_func(int fd, unsigned ev,
void* /* userdata */)
{
@@ -569,6 +571,24 @@
}
}
+void fdevent_subproc_setup()
+{
+ int s[2];
+
+ if(adb_socketpair(s)) {
+ FATAL("cannot create shell-exit socket-pair\n");
+ }
+ D("socketpair: (%d,%d)\n", s[0], s[1]);
+
+ SHELL_EXIT_NOTIFY_FD = s[0];
+ fdevent *fde;
+ fde = fdevent_create(s[1], fdevent_subproc_event_func, NULL);
+ if(!fde)
+ FATAL("cannot create fdevent for shell-exit handler\n");
+ fdevent_add(fde, FDE_READ);
+}
+#endif // !ADB_HOST
+
fdevent *fdevent_create(int fd, fd_func func, void *arg)
{
fdevent *fde = (fdevent*) malloc(sizeof(fdevent));
@@ -661,27 +681,12 @@
fde, (fde->state & FDE_EVENTMASK) & (~(events & FDE_EVENTMASK)));
}
-void fdevent_subproc_setup()
-{
- int s[2];
-
- if(adb_socketpair(s)) {
- FATAL("cannot create shell-exit socket-pair\n");
- }
- D("socketpair: (%d,%d)", s[0], s[1]);
-
- SHELL_EXIT_NOTIFY_FD = s[0];
- fdevent *fde;
- fde = fdevent_create(s[1], fdevent_subproc_event_func, NULL);
- if(!fde)
- FATAL("cannot create fdevent for shell-exit handler\n");
- fdevent_add(fde, FDE_READ);
-}
-
void fdevent_loop()
{
fdevent *fde;
+#if !ADB_HOST
fdevent_subproc_setup();
+#endif // !ADB_HOST
for(;;) {
D("--- ---- waiting for events\n");
diff --git a/adb/jdwp_service.cpp b/adb/jdwp_service.cpp
index c0f7ec2..06e7780 100644
--- a/adb/jdwp_service.cpp
+++ b/adb/jdwp_service.cpp
@@ -22,6 +22,7 @@
#include <errno.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -435,7 +436,7 @@
__FUNCTION__, strerror(errno));
return -1;
}
- D("socketpair: (%d,%d)", fds[0], fds[1]);
+ D("socketpair: (%d,%d)\n", fds[0], fds[1]);
proc->out_fds[ proc->out_count ] = fds[1];
if (++proc->out_count == 1)
@@ -608,7 +609,7 @@
*/
if (jdwp->pass == 0) {
apacket* p = get_apacket();
- p->len = jdwp_process_list((char*)p->data, MAX_PAYLOAD);
+ p->len = jdwp_process_list((char*)p->data, s->get_max_payload());
peer->enqueue(peer, p);
jdwp->pass = 1;
}
@@ -695,7 +696,7 @@
if (t->need_update) {
apacket* p = get_apacket();
t->need_update = 0;
- p->len = jdwp_process_list_msg((char*)p->data, sizeof(p->data));
+ p->len = jdwp_process_list_msg((char*)p->data, s->get_max_payload());
s->peer->enqueue(s->peer, p);
}
}
diff --git a/adb/protocol.txt b/adb/protocol.txt
index 333e7e7..5c7c6ba 100644
--- a/adb/protocol.txt
+++ b/adb/protocol.txt
@@ -60,11 +60,14 @@
declares the maximum message body size that the remote system
is willing to accept.
-Currently, version=0x01000000 and maxdata=4096
+Currently, version=0x01000000 and maxdata=256*1024. Older versions of adb
+hard-coded maxdata=4096, so CONNECT and AUTH packets sent to a device must not
+be larger than that because they're sent before the CONNECT from the device
+that tells the adb server what maxdata the device can support.
Both sides send a CONNECT message when the connection between them is
established. Until a CONNECT message is received no other messages may
-be sent. Any messages received before a CONNECT message MUST be ignored.
+be sent. Any messages received before a CONNECT message MUST be ignored.
If a CONNECT message is received with an unknown version or insufficiently
large maxdata value, the connection with the other side must be closed.
diff --git a/adb/services.cpp b/adb/services.cpp
index 227f22a..82efb1c 100644
--- a/adb/services.cpp
+++ b/adb/services.cpp
@@ -39,6 +39,7 @@
#include <base/file.h>
#include <base/stringprintf.h>
#include <base/strings.h>
+#include <cutils/sockets.h>
#if !ADB_HOST
#include "cutils/android_reboot.h"
@@ -47,6 +48,7 @@
#include "adb.h"
#include "adb_io.h"
+#include "adb_utils.h"
#include "file_sync_service.h"
#include "remount_service.h"
#include "transport.h"
@@ -205,7 +207,7 @@
printf("cannot create service socket pair\n");
return -1;
}
- D("socketpair: (%d,%d)", s[0], s[1]);
+ D("socketpair: (%d,%d)\n", s[0], s[1]);
stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo)));
if (sti == nullptr) {
@@ -317,7 +319,7 @@
printf("[ cannot create socket pair - %s ]\n", strerror(errno));
return -1;
}
- D("socketpair: (%d,%d)", sv[0], sv[1]);
+ D("socketpair: (%d,%d)\n", sv[0], sv[1]);
*pid = fork();
if (*pid < 0) {
@@ -435,7 +437,8 @@
disable_tcp_nagle(ret);
} else {
#if ADB_HOST
- ret = socket_network_client(name + 1, port, SOCK_STREAM);
+ std::string error;
+ ret = network_connect(name + 1, port, SOCK_STREAM, 0, &error);
#else
return -1;
#endif
@@ -485,7 +488,7 @@
} else if(!strncmp(name, "tcpip:", 6)) {
int port;
if (sscanf(name + 6, "%d", &port) != 1) {
- port = 0;
+ return -1;
}
ret = create_service_thread(restart_tcp_service, (void *) (uintptr_t) port);
} else if(!strncmp(name, "usb:", 4)) {
@@ -541,35 +544,28 @@
D("wait_for_state is done\n");
}
-static void connect_device(const std::string& host, std::string* response) {
- if (host.empty()) {
- *response = "empty host name";
+static void connect_device(const std::string& address, std::string* response) {
+ if (address.empty()) {
+ *response = "empty address";
return;
}
- std::vector<std::string> pieces = android::base::Split(host, ":");
- const std::string& hostname = pieces[0];
-
+ std::string serial;
+ std::string host;
int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
- if (pieces.size() > 1) {
- if (sscanf(pieces[1].c_str(), "%d", &port) != 1) {
- *response = android::base::StringPrintf("bad port number %s", pieces[1].c_str());
- return;
- }
- }
-
- // This may look like we're putting 'host' back together,
- // but we're actually inserting the default port if necessary.
- std::string serial = android::base::StringPrintf("%s:%d", hostname.c_str(), port);
-
- int fd = socket_network_client_timeout(hostname.c_str(), port, SOCK_STREAM, 10);
- if (fd < 0) {
- *response = android::base::StringPrintf("unable to connect to %s:%d",
- hostname.c_str(), port);
+ if (!parse_host_and_port(address, &serial, &host, &port, response)) {
return;
}
- D("client: connected on remote on fd %d\n", fd);
+ std::string error;
+ int fd = network_connect(host.c_str(), port, SOCK_STREAM, 10, &error);
+ if (fd == -1) {
+ *response = android::base::StringPrintf("unable to connect to %s: %s",
+ serial.c_str(), error.c_str());
+ return;
+ }
+
+ D("client: connected %s remote on fd %d\n", serial.c_str(), fd);
close_on_exec(fd);
disable_tcp_nagle(fd);
@@ -619,12 +615,13 @@
}
// Preconditions met, try to connect to the emulator.
- if (!local_connect_arbitrary_ports(console_port, adb_port)) {
+ std::string error;
+ if (!local_connect_arbitrary_ports(console_port, adb_port, &error)) {
*response = android::base::StringPrintf("Connected to emulator on ports %d,%d",
console_port, adb_port);
} else {
- *response = android::base::StringPrintf("Could not connect to emulator on ports %d,%d",
- console_port, adb_port);
+ *response = android::base::StringPrintf("Could not connect to emulator on ports %d,%d: %s",
+ console_port, adb_port, error.c_str());
}
}
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 621944e..d8ea2ee 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -330,8 +330,9 @@
if (ev & FDE_READ) {
apacket *p = get_apacket();
unsigned char *x = p->data;
- size_t avail = MAX_PAYLOAD;
- int r;
+ const size_t max_payload = s->get_max_payload();
+ size_t avail = max_payload;
+ int r = 0;
int is_eof = 0;
while (avail > 0) {
@@ -354,10 +355,10 @@
}
D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d\n",
s->id, s->fd, r, is_eof, s->fde.force_eof);
- if ((avail == MAX_PAYLOAD) || (s->peer == 0)) {
+ if ((avail == max_payload) || (s->peer == 0)) {
put_apacket(p);
} else {
- p->len = MAX_PAYLOAD - avail;
+ p->len = max_payload - avail;
r = s->peer->enqueue(s->peer, p);
D("LS(%d): fd=%d post peer->enqueue(). r=%d\n", s->id, s->fd,
@@ -569,9 +570,9 @@
{
D("Connect_to_remote call RS(%d) fd=%d\n", s->id, s->fd);
apacket *p = get_apacket();
- int len = strlen(destination) + 1;
+ size_t len = strlen(destination) + 1;
- if(len > (MAX_PAYLOAD-1)) {
+ if(len > (s->get_max_payload()-1)) {
fatal("destination oversized");
}
@@ -700,7 +701,7 @@
s->pkt_first = p;
s->pkt_last = p;
} else {
- if((s->pkt_first->len + p->len) > MAX_PAYLOAD) {
+ if((s->pkt_first->len + p->len) > s->get_max_payload()) {
D("SS(%d): overflow\n", s->id);
put_apacket(p);
goto fail;
@@ -901,3 +902,14 @@
ss->peer = s;
s->ready(s);
}
+
+size_t asocket::get_max_payload() const {
+ size_t max_payload = MAX_PAYLOAD;
+ if (transport) {
+ max_payload = std::min(max_payload, transport->get_max_payload());
+ }
+ if (peer && peer->transport) {
+ max_payload = std::min(max_payload, peer->transport->get_max_payload());
+ }
+ return max_payload;
+}
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index b7c16d9..6160923 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -24,6 +24,8 @@
# undef _WIN32
#endif
+#include <errno.h>
+
/*
* TEMP_FAILURE_RETRY is defined by some, but not all, versions of
* <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
@@ -185,14 +187,6 @@
/* normally provided by <cutils/misc.h> */
extern void* load_file(const char* pathname, unsigned* psize);
-/* normally provided by <cutils/sockets.h> */
-extern int socket_loopback_client(int port, int type);
-extern int socket_network_client(const char *host, int port, int type);
-extern int socket_network_client_timeout(const char *host, int port, int type,
- int timeout);
-extern int socket_loopback_server(int port, int type);
-extern int socket_inaddr_any_server(int port, int type);
-
/* normally provided by "fdevent.h" */
#define FDE_READ 0x0001
@@ -274,8 +268,8 @@
#else /* !_WIN32 a.k.a. Unix */
#include "fdevent.h"
-#include <cutils/sockets.h>
#include <cutils/misc.h>
+#include <cutils/threads.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
@@ -544,7 +538,7 @@
static __inline__ unsigned long adb_thread_id()
{
- return (unsigned long)pthread_self();
+ return (unsigned long)gettid();
}
#endif /* !_WIN32 */
diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp
index 50c99f1..a274892 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -25,6 +25,8 @@
#include <stdio.h>
#include <stdlib.h>
+#include <cutils/sockets.h>
+
#include "adb.h"
extern void fatal(const char *fmt, ...);
@@ -361,9 +363,10 @@
0, NULL );
if ( f->fh_handle == INVALID_HANDLE_VALUE ) {
+ const DWORD err = GetLastError();
_fh_close(f);
- D( "adb_open: could not open '%s':", path );
- switch (GetLastError()) {
+ D( "adb_open: could not open '%s': ", path );
+ switch (err) {
case ERROR_FILE_NOT_FOUND:
D( "file not found\n" );
errno = ENOENT;
@@ -375,7 +378,7 @@
return -1;
default:
- D( "unknown error\n" );
+ D( "unknown error: %ld\n", err );
errno = ENOENT;
return -1;
}
@@ -402,9 +405,10 @@
NULL );
if ( f->fh_handle == INVALID_HANDLE_VALUE ) {
+ const DWORD err = GetLastError();
_fh_close(f);
- D( "adb_creat: could not open '%s':", path );
- switch (GetLastError()) {
+ D( "adb_creat: could not open '%s': ", path );
+ switch (err) {
case ERROR_FILE_NOT_FOUND:
D( "file not found\n" );
errno = ENOENT;
@@ -416,7 +420,7 @@
return -1;
default:
- D( "unknown error\n" );
+ D( "unknown error: %ld\n", err );
errno = ENOENT;
return -1;
}
@@ -666,55 +670,45 @@
}
-int socket_network_client(const char *host, int port, int type)
-{
+int socket_network_client_timeout(const char *host, int port, int type, int timeout,
+ int* getaddrinfo_error) {
FH f = _fh_alloc( &_fh_socket_class );
- struct hostent *hp;
- struct sockaddr_in addr;
- SOCKET s;
+ if (!f) return -1;
- if (!f)
- return -1;
+ if (!_winsock_init) _init_winsock();
- if (!_winsock_init)
- _init_winsock();
-
- hp = gethostbyname(host);
+ hostent* hp = gethostbyname(host);
if(hp == 0) {
_fh_close(f);
return -1;
}
+ sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = hp->h_addrtype;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
- s = socket(hp->h_addrtype, type, 0);
+ SOCKET s = socket(hp->h_addrtype, type, 0);
if(s == INVALID_SOCKET) {
_fh_close(f);
return -1;
}
f->fh_socket = s;
+ // TODO: implement timeouts for Windows.
+
if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
_fh_close(f);
return -1;
}
snprintf( f->name, sizeof(f->name), "%d(net-client:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port );
- D( "socket_network_client: host '%s' port %d type %s => fd %d\n", host, port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) );
+ D( "socket_network_client_timeout: host '%s' port %d type %s => fd %d\n", host, port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) );
return _fh_to_int(f);
}
-int socket_network_client_timeout(const char *host, int port, int type, int timeout)
-{
- // TODO: implement timeouts for Windows.
- return socket_network_client(host, port, type);
-}
-
-
int socket_inaddr_any_server(int port, int type)
{
FH f = _fh_alloc( &_fh_socket_class );
@@ -781,8 +775,9 @@
fh->fh_socket = accept( serverfh->fh_socket, addr, addrlen );
if (fh->fh_socket == INVALID_SOCKET) {
+ const DWORD err = WSAGetLastError();
_fh_close( fh );
- D( "adb_socket_accept: accept on fd %d return error %ld\n", serverfd, GetLastError() );
+ D( "adb_socket_accept: accept on fd %d return error %ld\n", serverfd, err );
return -1;
}
@@ -1546,7 +1541,7 @@
threads = (WaitForAllParam*)malloc((chunks + (remains ? 1 : 0)) *
sizeof(WaitForAllParam));
if (threads == NULL) {
- D("Unable to allocate thread array for %d handles.", handles_count);
+ D("Unable to allocate thread array for %d handles.\n", handles_count);
return (int)WAIT_FAILED;
}
@@ -1554,7 +1549,7 @@
* reset" event that will remain set once it was set. */
main_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (main_event == NULL) {
- D("Unable to create main event. Error: %d", (int)GetLastError());
+ D("Unable to create main event. Error: %ld\n", GetLastError());
free(threads);
return (int)WAIT_FAILED;
}
@@ -1587,7 +1582,7 @@
&threads[chunk], 0, NULL);
if (threads[chunk].thread == NULL) {
/* Unable to create a waiter thread. Collapse. */
- D("Unable to create a waiting thread %d of %d. errno=%d",
+ D("Unable to create a waiting thread %d of %d. errno=%d\n",
chunk, chunks, errno);
chunks = chunk;
SetEvent(main_event);
diff --git a/adb/test_adb.py b/adb/test_adb.py
new file mode 100644
index 0000000..59aa14d
--- /dev/null
+++ b/adb/test_adb.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2015 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.
+#
+"""Tests for the adb program itself.
+
+This differs from things in test_device.py in that there is no API for these
+things. Most of these tests involve specific error messages or the help text.
+"""
+from __future__ import print_function
+
+import random
+import subprocess
+import unittest
+
+import adb
+
+
+class NonApiTest(unittest.TestCase):
+ """Tests for ADB that aren't a part of the AndroidDevice API."""
+
+ def test_help(self):
+ """Make sure we get _something_ out of help."""
+ out = subprocess.check_output(
+ ['adb', 'help'], stderr=subprocess.STDOUT)
+ self.assertGreater(len(out), 0)
+
+ def test_version(self):
+ """Get a version number out of the output of adb."""
+ lines = subprocess.check_output(['adb', 'version']).splitlines()
+ version_line = lines[0]
+ self.assertRegexpMatches(
+ version_line, r'^Android Debug Bridge version \d+\.\d+\.\d+$')
+ if len(lines) == 2:
+ # Newer versions of ADB have a second line of output for the
+ # version that includes a specific revision (git SHA).
+ revision_line = lines[1]
+ self.assertRegexpMatches(
+ revision_line, r'^Revision [0-9a-f]{12}-android$')
+
+ def test_tcpip_error_messages(self):
+ p = subprocess.Popen(['adb', 'tcpip'], stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ out, _ = p.communicate()
+ self.assertEqual(1, p.returncode)
+ self.assertIn('help message', out)
+
+ p = subprocess.Popen(['adb', 'tcpip', 'foo'], stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ out, _ = p.communicate()
+ self.assertEqual(1, p.returncode)
+ self.assertIn('error', out)
+
+
+def main():
+ random.seed(0)
+ if len(adb.get_devices()) > 0:
+ suite = unittest.TestLoader().loadTestsFromName(__name__)
+ unittest.TextTestRunner(verbosity=3).run(suite)
+ else:
+ print('Test suite must be run with attached devices')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/adb/test_device.py b/adb/test_device.py
new file mode 100644
index 0000000..6c20b6e
--- /dev/null
+++ b/adb/test_device.py
@@ -0,0 +1,424 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 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.
+#
+from __future__ import print_function
+
+import hashlib
+import os
+import posixpath
+import random
+import shlex
+import shutil
+import subprocess
+import tempfile
+import unittest
+
+import mock
+
+import adb
+
+
+class GetDeviceTest(unittest.TestCase):
+ def setUp(self):
+ self.android_serial = os.getenv('ANDROID_SERIAL')
+ del os.environ['ANDROID_SERIAL']
+
+ def tearDown(self):
+ os.environ['ANDROID_SERIAL'] = self.android_serial
+
+ @mock.patch('adb.device.get_devices')
+ def test_explicit(self, mock_get_devices):
+ mock_get_devices.return_value = ['foo', 'bar']
+ device = adb.get_device('foo')
+ self.assertEqual(device.serial, 'foo')
+
+ @mock.patch('adb.device.get_devices')
+ def test_from_env(self, mock_get_devices):
+ mock_get_devices.return_value = ['foo', 'bar']
+ os.environ['ANDROID_SERIAL'] = 'foo'
+ device = adb.get_device()
+ self.assertEqual(device.serial, 'foo')
+
+ @mock.patch('adb.device.get_devices')
+ def test_arg_beats_env(self, mock_get_devices):
+ mock_get_devices.return_value = ['foo', 'bar']
+ os.environ['ANDROID_SERIAL'] = 'bar'
+ device = adb.get_device('foo')
+ self.assertEqual(device.serial, 'foo')
+
+ @mock.patch('adb.device.get_devices')
+ def test_no_such_device(self, mock_get_devices):
+ mock_get_devices.return_value = ['foo', 'bar']
+ self.assertRaises(adb.DeviceNotFoundError, adb.get_device, ['baz'])
+
+ os.environ['ANDROID_SERIAL'] = 'baz'
+ self.assertRaises(adb.DeviceNotFoundError, adb.get_device)
+
+ @mock.patch('adb.device.get_devices')
+ def test_unique_device(self, mock_get_devices):
+ mock_get_devices.return_value = ['foo']
+ device = adb.get_device()
+ self.assertEqual(device.serial, 'foo')
+
+ @mock.patch('adb.device.get_devices')
+ def test_no_unique_device(self, mock_get_devices):
+ mock_get_devices.return_value = ['foo', 'bar']
+ self.assertRaises(adb.NoUniqueDeviceError, adb.get_device)
+
+
+class DeviceTest(unittest.TestCase):
+ def setUp(self):
+ self.device = adb.get_device()
+
+
+class ShellTest(DeviceTest):
+ def test_cat(self):
+ """Check that we can at least cat a file."""
+ out = self.device.shell(['cat', '/proc/uptime']).strip()
+ elements = out.split()
+ self.assertEqual(len(elements), 2)
+
+ uptime, idle = elements
+ self.assertGreater(float(uptime), 0.0)
+ self.assertGreater(float(idle), 0.0)
+
+ def test_throws_on_failure(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ self.device.shell, ['false'])
+
+ def test_output_not_stripped(self):
+ out = self.device.shell(['echo', 'foo'])
+ self.assertEqual(out, 'foo' + self.device.linesep)
+
+ def test_shell_nocheck_failure(self):
+ rc, out = self.device.shell_nocheck(['false'])
+ self.assertNotEqual(rc, 0)
+ self.assertEqual(out, '')
+
+ def test_shell_nocheck_output_not_stripped(self):
+ rc, out = self.device.shell_nocheck(['echo', 'foo'])
+ self.assertEqual(rc, 0)
+ self.assertEqual(out, 'foo' + self.device.linesep)
+
+ def test_can_distinguish_tricky_results(self):
+ # If result checking on ADB shell is naively implemented as
+ # `adb shell <cmd>; echo $?`, we would be unable to distinguish the
+ # output from the result for a cmd of `echo -n 1`.
+ rc, out = self.device.shell_nocheck(['echo', '-n', '1'])
+ self.assertEqual(rc, 0)
+ self.assertEqual(out, '1')
+
+ def test_line_endings(self):
+ """Ensure that line ending translation is not happening in the pty.
+
+ Bug: http://b/19735063
+ """
+ output = self.device.shell(['uname'])
+ self.assertEqual(output, 'Linux' + self.device.linesep)
+
+
+class ArgumentEscapingTest(DeviceTest):
+ def test_shell_escaping(self):
+ """Make sure that argument escaping is somewhat sane."""
+
+ # http://b/19734868
+ # Note that this actually matches ssh(1)'s behavior --- it's
+ # converted to `sh -c echo hello; echo world` which sh interprets
+ # as `sh -c echo` (with an argument to that shell of "hello"),
+ # and then `echo world` back in the first shell.
+ result = self.device.shell(
+ shlex.split("sh -c 'echo hello; echo world'"))
+ result = result.splitlines()
+ self.assertEqual(['', 'world'], result)
+ # If you really wanted "hello" and "world", here's what you'd do:
+ result = self.device.shell(
+ shlex.split(r'echo hello\;echo world')).splitlines()
+ self.assertEqual(['hello', 'world'], result)
+
+ # http://b/15479704
+ result = self.device.shell(shlex.split("'true && echo t'")).strip()
+ self.assertEqual('t', result)
+ result = self.device.shell(
+ shlex.split("sh -c 'true && echo t'")).strip()
+ self.assertEqual('t', result)
+
+ # http://b/20564385
+ result = self.device.shell(shlex.split('FOO=a BAR=b echo t')).strip()
+ self.assertEqual('t', result)
+ result = self.device.shell(shlex.split(r'echo -n 123\;uname')).strip()
+ self.assertEqual('123Linux', result)
+
+ def test_install_argument_escaping(self):
+ """Make sure that install argument escaping works."""
+ # http://b/20323053
+ tf = tempfile.NamedTemporaryFile('wb', suffix='-text;ls;1.apk')
+ self.assertIn("-text;ls;1.apk", self.device.install(tf.name))
+
+ # http://b/3090932
+ tf = tempfile.NamedTemporaryFile('wb', suffix="-Live Hold'em.apk")
+ self.assertIn("-Live Hold'em.apk", self.device.install(tf.name))
+
+
+class RootUnrootTest(DeviceTest):
+ def _test_root(self):
+ message = self.device.root()
+ if 'adbd cannot run as root in production builds' in message:
+ return
+ self.device.wait()
+ self.assertEqual('root', self.device.shell(['id', '-un']).strip())
+
+ def _test_unroot(self):
+ self.device.unroot()
+ self.device.wait()
+ self.assertEqual('shell', self.device.shell(['id', '-un']).strip())
+
+ def test_root_unroot(self):
+ """Make sure that adb root and adb unroot work, using id(1)."""
+ original_user = self.device.shell(['id', '-un']).strip()
+ try:
+ if original_user == 'root':
+ self._test_unroot()
+ self._test_root()
+ elif original_user == 'shell':
+ self._test_root()
+ self._test_unroot()
+ finally:
+ if original_user == 'root':
+ self.device.root()
+ else:
+ self.device.unroot()
+ self.device.wait()
+
+
+class TcpIpTest(DeviceTest):
+ def test_tcpip_failure_raises(self):
+ """adb tcpip requires a port.
+
+ Bug: http://b/22636927
+ """
+ self.assertRaises(
+ subprocess.CalledProcessError, self.device.tcpip, '')
+ self.assertRaises(
+ subprocess.CalledProcessError, self.device.tcpip, 'foo')
+
+
+def compute_md5(string):
+ hsh = hashlib.md5()
+ hsh.update(string)
+ return hsh.hexdigest()
+
+
+def get_md5_prog(device):
+ """Older platforms (pre-L) had the name md5 rather than md5sum."""
+ try:
+ device.shell(['md5sum', '/proc/uptime'])
+ return 'md5sum'
+ except subprocess.CalledProcessError:
+ return 'md5'
+
+
+class HostFile(object):
+ def __init__(self, handle, checksum):
+ self.handle = handle
+ self.checksum = checksum
+ self.full_path = handle.name
+ self.base_name = os.path.basename(self.full_path)
+
+
+class DeviceFile(object):
+ def __init__(self, checksum, full_path):
+ self.checksum = checksum
+ self.full_path = full_path
+ self.base_name = posixpath.basename(self.full_path)
+
+
+def make_random_host_files(in_dir, num_files):
+ min_size = 1 * (1 << 10)
+ max_size = 16 * (1 << 10)
+
+ files = []
+ for _ in xrange(num_files):
+ file_handle = tempfile.NamedTemporaryFile(dir=in_dir, delete=False)
+
+ size = random.randrange(min_size, max_size, 1024)
+ rand_str = os.urandom(size)
+ file_handle.write(rand_str)
+ file_handle.flush()
+ file_handle.close()
+
+ md5 = compute_md5(rand_str)
+ files.append(HostFile(file_handle, md5))
+ return files
+
+
+def make_random_device_files(device, in_dir, num_files):
+ min_size = 1 * (1 << 10)
+ max_size = 16 * (1 << 10)
+
+ files = []
+ for file_num in xrange(num_files):
+ size = random.randrange(min_size, max_size, 1024)
+
+ base_name = 'device_tmpfile' + str(file_num)
+ full_path = os.path.join(in_dir, base_name)
+
+ device.shell(['dd', 'if=/dev/urandom', 'of={}'.format(full_path),
+ 'bs={}'.format(size), 'count=1'])
+ dev_md5, _ = device.shell([get_md5_prog(device), full_path]).split()
+
+ files.append(DeviceFile(dev_md5, full_path))
+ return files
+
+
+class FileOperationsTest(DeviceTest):
+ SCRATCH_DIR = '/data/local/tmp'
+ DEVICE_TEMP_FILE = SCRATCH_DIR + '/adb_test_file'
+ DEVICE_TEMP_DIR = SCRATCH_DIR + '/adb_test_dir'
+
+ def _test_push(self, local_file, checksum):
+ self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
+ try:
+ self.device.push(
+ local=local_file, remote=self.DEVICE_TEMP_FILE)
+ dev_md5, _ = self.device.shell(
+ [get_md5_prog(self.device), self.DEVICE_TEMP_FILE]).split()
+ self.assertEqual(checksum, dev_md5)
+ finally:
+ self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
+
+ def test_push(self):
+ """Push a randomly generated file to specified device."""
+ kbytes = 512
+ tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
+ try:
+ rand_str = os.urandom(1024 * kbytes)
+ tmp.write(rand_str)
+ tmp.close()
+ self._test_push(tmp.name, compute_md5(rand_str))
+ finally:
+ os.remove(tmp.name)
+
+ # TODO: write push directory test.
+
+ def _test_pull(self, remote_file, checksum):
+ tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
+ try:
+ tmp_write.close()
+ self.device.pull(remote=remote_file, local=tmp_write.name)
+ with open(tmp_write.name, 'rb') as tmp_read:
+ host_contents = tmp_read.read()
+ host_md5 = compute_md5(host_contents)
+ self.assertEqual(checksum, host_md5)
+ finally:
+ os.remove(tmp_write.name)
+
+ def test_pull(self):
+ """Pull a randomly generated file from specified device."""
+ kbytes = 512
+ self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
+ try:
+ cmd = ['dd', 'if=/dev/urandom',
+ 'of={}'.format(self.DEVICE_TEMP_FILE), 'bs=1024',
+ 'count={}'.format(kbytes)]
+ self.device.shell(cmd)
+ dev_md5, _ = self.device.shell(
+ [get_md5_prog(self.device), self.DEVICE_TEMP_FILE]).split()
+ self._test_pull(self.DEVICE_TEMP_FILE, dev_md5)
+ finally:
+ self.device.shell_nocheck(['rm', self.DEVICE_TEMP_FILE])
+
+ def test_pull_dir(self):
+ """Pull a randomly generated directory of files from the device."""
+ host_dir = tempfile.mkdtemp()
+ try:
+ self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+ self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+
+ # Populate device directory with random files.
+ temp_files = make_random_device_files(
+ self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+
+ self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
+
+ for temp_file in temp_files:
+ host_path = os.path.join(host_dir, temp_file.base_name)
+ with open(host_path, 'rb') as host_file:
+ host_md5 = compute_md5(host_file.read())
+ self.assertEqual(host_md5, temp_file.checksum)
+ finally:
+ self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+ if host_dir is not None:
+ shutil.rmtree(host_dir)
+
+ def test_sync(self):
+ """Sync a randomly generated directory of files to specified device."""
+ base_dir = tempfile.mkdtemp()
+ try:
+ # Create mirror device directory hierarchy within base_dir.
+ full_dir_path = base_dir + self.DEVICE_TEMP_DIR
+ os.makedirs(full_dir_path)
+
+ # Create 32 random files within the host mirror.
+ temp_files = make_random_host_files(in_dir=full_dir_path,
+ num_files=32)
+
+ # Clean up any trash on the device.
+ device = adb.get_device(product=base_dir)
+ device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+
+ device.sync('data')
+
+ # Confirm that every file on the device mirrors that on the host.
+ for temp_file in temp_files:
+ device_full_path = posixpath.join(
+ self.DEVICE_TEMP_DIR, temp_file.base_name)
+ dev_md5, _ = device.shell(
+ [get_md5_prog(self.device), device_full_path]).split()
+ self.assertEqual(temp_file.checksum, dev_md5)
+ finally:
+ self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+ shutil.rmtree(base_dir + self.DEVICE_TEMP_DIR)
+
+
+ def test_unicode_paths(self):
+ """Ensure that we can support non-ASCII paths, even on Windows."""
+ name = u'로보카 폴리'.encode('utf-8')
+
+ ## push.
+ tf = tempfile.NamedTemporaryFile('wb', suffix=name)
+ self.device.push(tf.name, '/data/local/tmp/adb-test-{}'.format(name))
+ self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
+
+ # pull.
+ cmd = ['touch', '"/data/local/tmp/adb-test-{}"'.format(name)]
+ self.device.shell(cmd)
+
+ tf = tempfile.NamedTemporaryFile('wb', suffix=name)
+ self.device.pull('/data/local/tmp/adb-test-{}'.format(name), tf.name)
+
+
+def main():
+ random.seed(0)
+ if len(adb.get_devices()) > 0:
+ suite = unittest.TestLoader().loadTestsFromName(__name__)
+ unittest.TextTestRunner(verbosity=3).run(suite)
+ else:
+ print('Test suite must be run with attached devices')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/adb/tests/test_adb.py b/adb/tests/test_adb.py
deleted file mode 100755
index 730f668..0000000
--- a/adb/tests/test_adb.py
+++ /dev/null
@@ -1,469 +0,0 @@
-#!/usr/bin/env python2
-"""Simple conformance test for adb.
-
-This script will use the available adb in path and run simple
-tests that attempt to touch all accessible attached devices.
-"""
-import hashlib
-import os
-import pipes
-import posixpath
-import random
-import re
-import shlex
-import subprocess
-import sys
-import tempfile
-import unittest
-
-
-def trace(cmd):
- """Print debug message if tracing enabled."""
- if False:
- print >> sys.stderr, cmd
-
-
-def call(cmd_str):
- """Run process and return output tuple (stdout, stderr, ret code)."""
- trace(cmd_str)
- process = subprocess.Popen(shlex.split(cmd_str),
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- stdout, stderr = process.communicate()
- return stdout, stderr, process.returncode
-
-
-def call_combined(cmd_str):
- """Run process and return output tuple (stdout+stderr, ret code)."""
- trace(cmd_str)
- process = subprocess.Popen(shlex.split(cmd_str),
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- stdout, _ = process.communicate()
- return stdout, process.returncode
-
-
-def call_checked(cmd_str):
- """Run process and get stdout+stderr, raise an exception on trouble."""
- trace(cmd_str)
- return subprocess.check_output(shlex.split(cmd_str),
- stderr=subprocess.STDOUT)
-
-
-def call_checked_list(cmd_str):
- return call_checked(cmd_str).split('\n')
-
-
-def call_checked_list_skip(cmd_str):
- out_list = call_checked_list(cmd_str)
-
- def is_init_line(line):
- if (len(line) >= 3) and (line[0] == "*") and (line[-2] == "*"):
- return True
- else:
- return False
-
- return [line for line in out_list if not is_init_line(line)]
-
-
-def get_device_list():
- output = call_checked_list_skip("adb devices")
- dev_list = []
- for line in output[1:]:
- if line.strip() == "":
- continue
- device, _ = line.split()
- dev_list.append(device)
- return dev_list
-
-
-def get_attached_device_count():
- return len(get_device_list())
-
-
-def compute_md5(string):
- hsh = hashlib.md5()
- hsh.update(string)
- return hsh.hexdigest()
-
-
-class HostFile(object):
- def __init__(self, handle, md5):
- self.handle = handle
- self.md5 = md5
- self.full_path = handle.name
- self.base_name = os.path.basename(self.full_path)
-
-
-class DeviceFile(object):
- def __init__(self, md5, full_path):
- self.md5 = md5
- self.full_path = full_path
- self.base_name = posixpath.basename(self.full_path)
-
-
-def make_random_host_files(in_dir, num_files, rand_size=True):
- files = {}
- min_size = 1 * (1 << 10)
- max_size = 16 * (1 << 10)
- fixed_size = min_size
-
- for _ in range(num_files):
- file_handle = tempfile.NamedTemporaryFile(dir=in_dir, delete=False)
-
- if rand_size:
- size = random.randrange(min_size, max_size, 1024)
- else:
- size = fixed_size
- rand_str = os.urandom(size)
- file_handle.write(rand_str)
- file_handle.flush()
- file_handle.close()
-
- md5 = compute_md5(rand_str)
- files[file_handle.name] = HostFile(file_handle, md5)
- return files
-
-
-def make_random_device_files(adb, in_dir, num_files, rand_size=True):
- files = {}
- min_size = 1 * (1 << 10)
- max_size = 16 * (1 << 10)
- fixed_size = min_size
-
- for i in range(num_files):
- if rand_size:
- size = random.randrange(min_size, max_size, 1024)
- else:
- size = fixed_size
-
- base_name = "device_tmpfile" + str(i)
- full_path = in_dir + "/" + base_name
-
- adb.shell("dd if=/dev/urandom of={} bs={} count=1".format(full_path,
- size))
- dev_md5, _ = adb.shell("md5sum {}".format(full_path)).split()
-
- files[full_path] = DeviceFile(dev_md5, full_path)
- return files
-
-
-class AdbWrapper(object):
- """Convenience wrapper object for the adb command."""
- def __init__(self, device=None, out_dir=None):
- self.device = device
- self.out_dir = out_dir
- self.adb_cmd = "adb "
- if self.device:
- self.adb_cmd += "-s {} ".format(pipes.quote(device))
- if self.out_dir:
- self.adb_cmd += "-p {} ".format(pipes.quote(out_dir))
-
- def shell(self, cmd):
- return call_checked(self.adb_cmd + "shell " + cmd)
-
- def shell_nocheck(self, cmd):
- return call_combined(self.adb_cmd + "shell " + cmd)
-
- def install(self, filename):
- return call_checked(
- self.adb_cmd + "install {}".format(pipes.quote(filename)))
-
- def push(self, local, remote):
- return call_checked(self.adb_cmd + "push {} {}".format(
- pipes.quote(local), pipes.quote(remote)))
-
- def pull(self, remote, local):
- return call_checked(self.adb_cmd + "pull {} {}".format(
- pipes.quote(remote), pipes.quote(local)))
-
- def sync(self, directory=""):
- return call_checked(self.adb_cmd + "sync {}".format(
- pipes.quote(directory) if directory else directory))
-
- def forward(self, local, remote):
- return call_checked(self.adb_cmd + "forward {} {}".format(local,
- remote))
-
- def tcpip(self, port):
- return call_checked(self.adb_cmd + "tcpip {}".format(port))
-
- def usb(self):
- return call_checked(self.adb_cmd + "usb")
-
- def root(self):
- return call_checked(self.adb_cmd + "root")
-
- def unroot(self):
- return call_checked(self.adb_cmd + "unroot")
-
- def forward_remove(self, local):
- return call_checked(self.adb_cmd + "forward --remove {}".format(local))
-
- def forward_remove_all(self):
- return call_checked(self.adb_cmd + "forward --remove-all")
-
- def connect(self, host):
- return call_checked(self.adb_cmd + "connect {}".format(host))
-
- def disconnect(self, host):
- return call_checked(self.adb_cmd + "disconnect {}".format(host))
-
- def reverse(self, remote, local):
- return call_checked(self.adb_cmd + "reverse {} {}".format(remote,
- local))
-
- def reverse_remove_all(self):
- return call_checked(self.adb_cmd + "reverse --remove-all")
-
- def reverse_remove(self, remote):
- return call_checked(
- self.adb_cmd + "reverse --remove {}".format(remote))
-
- def wait(self):
- return call_checked(self.adb_cmd + "wait-for-device")
-
-
-class AdbBasic(unittest.TestCase):
- def test_shell(self):
- """Check that we can at least cat a file."""
- adb = AdbWrapper()
- out = adb.shell("cat /proc/uptime")
- self.assertEqual(len(out.split()), 2)
- self.assertGreater(float(out.split()[0]), 0.0)
- self.assertGreater(float(out.split()[1]), 0.0)
-
- def test_help(self):
- """Make sure we get _something_ out of help."""
- out = call_checked("adb help")
- self.assertTrue(len(out) > 0)
-
- def test_version(self):
- """Get a version number out of the output of adb."""
- out = call_checked("adb version").split()
- version_num = False
- for item in out:
- if re.match(r"[\d+\.]*\d", item):
- version_num = True
- self.assertTrue(version_num)
-
- def _test_root(self):
- adb = AdbWrapper()
- if "adbd cannot run as root in production builds" in adb.root():
- return
- adb.wait()
- self.assertEqual("root", adb.shell("id -un").strip())
-
- def _test_unroot(self):
- adb = AdbWrapper()
- adb.unroot()
- adb.wait()
- self.assertEqual("shell", adb.shell("id -un").strip())
-
- def test_root_unroot(self):
- """Make sure that adb root and adb unroot work, using id(1)."""
- adb = AdbWrapper()
- original_user = adb.shell("id -un").strip()
- try:
- if original_user == "root":
- self._test_unroot()
- self._test_root()
- elif original_user == "shell":
- self._test_root()
- self._test_unroot()
- finally:
- if original_user == "root":
- adb.root()
- else:
- adb.unroot()
- adb.wait()
-
- def test_argument_escaping(self):
- """Make sure that argument escaping is somewhat sane."""
- adb = AdbWrapper()
-
- # http://b/19734868
- # Note that this actually matches ssh(1)'s behavior --- it's
- # converted to "sh -c echo hello; echo world" which sh interprets
- # as "sh -c echo" (with an argument to that shell of "hello"),
- # and then "echo world" back in the first shell.
- result = adb.shell("sh -c 'echo hello; echo world'").splitlines()
- self.assertEqual(["", "world"], result)
- # If you really wanted "hello" and "world", here's what you'd do:
- result = adb.shell(r"echo hello\;echo world").splitlines()
- self.assertEqual(["hello", "world"], result)
-
- # http://b/15479704
- self.assertEqual('t', adb.shell("'true && echo t'").strip())
- self.assertEqual('t', adb.shell("sh -c 'true && echo t'").strip())
-
- # http://b/20564385
- self.assertEqual('t', adb.shell("FOO=a BAR=b echo t").strip())
- self.assertEqual('123Linux', adb.shell(r"echo -n 123\;uname").strip())
-
- def test_install_argument_escaping(self):
- """Make sure that install argument escaping works."""
- adb = AdbWrapper()
-
- # http://b/20323053
- tf = tempfile.NamedTemporaryFile("wb", suffix="-text;ls;1.apk")
- self.assertIn("-text;ls;1.apk", adb.install(tf.name))
-
- # http://b/3090932
- tf = tempfile.NamedTemporaryFile("wb", suffix="-Live Hold'em.apk")
- self.assertIn("-Live Hold'em.apk", adb.install(tf.name))
-
- def test_line_endings(self):
- """Ensure that line ending translation is not happening in the pty.
-
- Bug: http://b/19735063
- """
- output = AdbWrapper().shell("uname")
- if sys.platform == 'win32':
- # adb.exe running on Windows does translation to the Windows \r\n
- # convention, so we should expect those chars.
- self.assertEqual(output, "Linux\r\n")
- else:
- self.assertEqual(output, "Linux\n")
-
-
-class AdbFile(unittest.TestCase):
- SCRATCH_DIR = "/data/local/tmp"
- DEVICE_TEMP_FILE = SCRATCH_DIR + "/adb_test_file"
- DEVICE_TEMP_DIR = SCRATCH_DIR + "/adb_test_dir"
-
- def test_push(self):
- """Push a randomly generated file to specified device."""
- kbytes = 512
- adb = AdbWrapper()
- with tempfile.NamedTemporaryFile(mode="wb", delete=False) as tmp:
- try:
- rand_str = os.urandom(1024 * kbytes)
- tmp.write(rand_str)
- tmp.flush()
- tmp.close()
-
- host_md5 = compute_md5(rand_str)
- adb.shell_nocheck("rm -r {}".format(AdbFile.DEVICE_TEMP_FILE))
- try:
- adb.push(local=tmp.name, remote=AdbFile.DEVICE_TEMP_FILE)
- dev_md5, _ = adb.shell(
- "md5sum {}".format(AdbFile.DEVICE_TEMP_FILE)).split()
- self.assertEqual(host_md5, dev_md5)
- finally:
- adb.shell_nocheck("rm {}".format(AdbFile.DEVICE_TEMP_FILE))
- finally:
- os.remove(tmp.name)
-
- # TODO: write push directory test.
-
- def test_pull(self):
- """Pull a randomly generated file from specified device."""
- kbytes = 512
- adb = AdbWrapper()
- adb.shell_nocheck("rm -r {}".format(AdbFile.DEVICE_TEMP_FILE))
- try:
- adb.shell("dd if=/dev/urandom of={} bs=1024 count={}".format(
- AdbFile.DEVICE_TEMP_FILE, kbytes))
- dev_md5, _ = adb.shell(
- "md5sum {}".format(AdbFile.DEVICE_TEMP_FILE)).split()
-
- with tempfile.NamedTemporaryFile(mode="wb", delete=False) \
- as tmp_write:
- try:
- tmp_write.close()
- adb.pull(remote=AdbFile.DEVICE_TEMP_FILE,
- local=tmp_write.name)
- with open(tmp_write.name, "rb") as tmp_read:
- host_contents = tmp_read.read()
- host_md5 = compute_md5(host_contents)
- self.assertEqual(dev_md5, host_md5)
- finally:
- os.remove(tmp_write.name)
- finally:
- adb.shell_nocheck("rm {}".format(AdbFile.DEVICE_TEMP_FILE))
-
- def test_pull_dir(self):
- """Pull a randomly generated directory of files from the device."""
- adb = AdbWrapper()
- temp_files = {}
- host_dir = None
- try:
- # create temporary host directory
- host_dir = tempfile.mkdtemp()
-
- # create temporary dir on device
- adb.shell_nocheck("rm -r {}".format(AdbFile.DEVICE_TEMP_DIR))
- adb.shell("mkdir -p {}".format(AdbFile.DEVICE_TEMP_DIR))
-
- # populate device dir with random files
- temp_files = make_random_device_files(
- adb, in_dir=AdbFile.DEVICE_TEMP_DIR, num_files=32)
-
- adb.pull(remote=AdbFile.DEVICE_TEMP_DIR, local=host_dir)
-
- for device_full_path in temp_files:
- host_path = os.path.join(
- host_dir, temp_files[device_full_path].base_name)
- with open(host_path, "rb") as host_file:
- host_md5 = compute_md5(host_file.read())
- self.assertEqual(host_md5,
- temp_files[device_full_path].md5)
- finally:
- for dev_file in temp_files.values():
- host_path = os.path.join(host_dir, dev_file.base_name)
- os.remove(host_path)
- adb.shell_nocheck("rm -r {}".format(AdbFile.DEVICE_TEMP_DIR))
- if host_dir:
- os.removedirs(host_dir)
-
- def test_sync(self):
- """Sync a randomly generated directory of files to specified device."""
- try:
- adb = AdbWrapper()
- temp_files = {}
-
- # create temporary host directory
- base_dir = tempfile.mkdtemp()
-
- # create mirror device directory hierarchy within base_dir
- full_dir_path = base_dir + AdbFile.DEVICE_TEMP_DIR
- os.makedirs(full_dir_path)
-
- # create 32 random files within the host mirror
- temp_files = make_random_host_files(in_dir=full_dir_path,
- num_files=32)
-
- # clean up any trash on the device
- adb = AdbWrapper(out_dir=base_dir)
- adb.shell_nocheck("rm -r {}".format(AdbFile.DEVICE_TEMP_DIR))
-
- # issue the sync
- adb.sync("data")
-
- # confirm that every file on the device mirrors that on the host
- for host_full_path in temp_files.keys():
- device_full_path = posixpath.join(
- AdbFile.DEVICE_TEMP_DIR,
- temp_files[host_full_path].base_name)
- dev_md5, _ = adb.shell(
- "md5sum {}".format(device_full_path)).split()
- self.assertEqual(temp_files[host_full_path].md5, dev_md5)
-
- finally:
- adb.shell_nocheck("rm -r {}".format(AdbFile.DEVICE_TEMP_DIR))
- if temp_files:
- for tf in temp_files.values():
- os.remove(tf.full_path)
- if base_dir:
- os.removedirs(base_dir + AdbFile.DEVICE_TEMP_DIR)
-
-
-if __name__ == '__main__':
- random.seed(0)
- dev_count = get_attached_device_count()
- if dev_count:
- suite = unittest.TestLoader().loadTestsFromName(__name__)
- unittest.TextTestRunner(verbosity=3).run(suite)
- else:
- print "Test suite must be run with attached devices"
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 274449b..87aff88 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -283,7 +283,7 @@
p->msg.magic = A_SYNC ^ 0xffffffff;
if(write_packet(t->fd, t->serial, &p)) {
put_apacket(p);
- D("%s: failed to write SYNC apacket to transport", t->serial);
+ D("%s: failed to write SYNC apacket to transport\n", t->serial);
}
oops:
@@ -579,7 +579,7 @@
fatal_errno("cannot open transport socketpair");
}
- D("transport: %s socketpair: (%d,%d) starting", t->serial, s[0], s[1]);
+ D("transport: %s socketpair: (%d,%d) starting\n", t->serial, s[0], s[1]);
t->transport_socket = s[0];
t->fd = s[1];
@@ -617,7 +617,7 @@
if(adb_socketpair(s)){
fatal_errno("cannot open transport registration socketpair");
}
- D("socketpair: (%d,%d)", s[0], s[1]);
+ D("socketpair: (%d,%d)\n", s[0], s[1]);
transport_registration_send = s[0];
transport_registration_recv = s[1];
@@ -789,9 +789,10 @@
if (result->connection_state == kCsUnauthorized) {
*error_out = "device unauthorized.\n";
char* ADB_VENDOR_KEYS = getenv("ADB_VENDOR_KEYS");
- *error_out += "This adbd's $ADB_VENDOR_KEYS is ";
+ *error_out += "This adb server's $ADB_VENDOR_KEYS is ";
*error_out += ADB_VENDOR_KEYS ? ADB_VENDOR_KEYS : "not set";
- *error_out += "; try 'adb kill-server' if that seems wrong.\n";
+ *error_out += "\n";
+ *error_out += "Try 'adb kill-server' if that seems wrong.\n";
*error_out += "Otherwise check for a confirmation dialog on your device.";
result = NULL;
}
@@ -834,6 +835,19 @@
}
}
+void atransport::update_version(int version, size_t payload) {
+ protocol_version = std::min(version, A_VERSION);
+ max_payload = std::min(payload, MAX_PAYLOAD);
+}
+
+int atransport::get_protocol_version() const {
+ return protocol_version;
+}
+
+size_t atransport::get_max_payload() const {
+ return max_payload;
+}
+
#if ADB_HOST
static void append_transport_info(std::string* result, const char* key,
@@ -1019,15 +1033,16 @@
#undef TRACE_TAG
#define TRACE_TAG TRACE_RWX
-int check_header(apacket *p)
+int check_header(apacket *p, atransport *t)
{
if(p->msg.magic != (p->msg.command ^ 0xffffffff)) {
D("check_header(): invalid magic\n");
return -1;
}
- if(p->msg.data_length > MAX_PAYLOAD) {
- D("check_header(): %d > MAX_PAYLOAD\n", p->msg.data_length);
+ if(p->msg.data_length > t->get_max_payload()) {
+ D("check_header(): %u > atransport::max_payload = %zu\n",
+ p->msg.data_length, t->get_max_payload());
return -1;
}
diff --git a/adb/transport.h b/adb/transport.h
index 538f63e..edcc99d 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -57,7 +57,7 @@
void unregister_transport(atransport* t);
void unregister_all_tcp_transports();
-int check_header(apacket* p);
+int check_header(apacket* p, atransport* t);
int check_data(apacket* p);
/* for MacOS X cleanup */
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index 97e3d50..0dc9581 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -26,6 +26,7 @@
#include <sys/types.h>
#include <base/stringprintf.h>
+#include <cutils/sockets.h>
#if !ADB_HOST
#include "cutils/properties.h"
@@ -33,6 +34,7 @@
#include "adb.h"
#include "adb_io.h"
+#include "adb_utils.h"
#if ADB_HOST
/* we keep a list of opened transports. The atransport struct knows to which
@@ -53,7 +55,7 @@
return -1;
}
- if(check_header(p)) {
+ if(check_header(p, t)) {
D("bad header: terminated (data)\n");
return -1;
}
@@ -83,19 +85,18 @@
return 0;
}
-
-int local_connect(int port) {
- return local_connect_arbitrary_ports(port-1, port);
+void local_connect(int port) {
+ std::string dummy;
+ local_connect_arbitrary_ports(port-1, port, &dummy);
}
-int local_connect_arbitrary_ports(int console_port, int adb_port)
-{
- int fd = -1;
+int local_connect_arbitrary_ports(int console_port, int adb_port, std::string* error) {
+ int fd = -1;
#if ADB_HOST
const char *host = getenv("ADBHOST");
if (host) {
- fd = socket_network_client(host, adb_port, SOCK_STREAM);
+ fd = network_connect(host, adb_port, SOCK_STREAM, 0, error);
}
#endif
if (fd < 0) {
@@ -126,7 +127,7 @@
/* this is only done when ADB starts up. later, each new emulator */
/* will send a message to ADB to indicate that is is starting up */
for ( ; count > 0; count--, port += 2 ) {
- (void) local_connect(port);
+ local_connect(port);
}
#endif
return 0;
diff --git a/adb/transport_usb.cpp b/adb/transport_usb.cpp
index eb3454d..2c975a9 100644
--- a/adb/transport_usb.cpp
+++ b/adb/transport_usb.cpp
@@ -32,7 +32,7 @@
return -1;
}
- if(check_header(p)) {
+ if(check_header(p, t)) {
D("remote usb: check_header failed\n");
return -1;
}
diff --git a/adb/usb_linux.cpp b/adb/usb_linux.cpp
index c6f712b..e570ef5 100644
--- a/adb/usb_linux.cpp
+++ b/adb/usb_linux.cpp
@@ -22,6 +22,7 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <linux/usb/ch9.h>
#include <linux/usbdevice_fs.h>
#include <linux/version.h>
#include <stdio.h>
@@ -31,7 +32,12 @@
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
-#include <linux/usb/ch9.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <list>
+#include <mutex>
+#include <string>
#include <base/file.h>
#include <base/stringprintf.h>
@@ -40,110 +46,92 @@
#include "adb.h"
#include "transport.h"
+using namespace std::literals;
+
/* usb scan debugging is waaaay too verbose */
#define DBGX(x...)
-ADB_MUTEX_DEFINE( usb_lock );
+struct usb_handle {
+ ~usb_handle() {
+ if (fd != -1) unix_close(fd);
+ }
-struct usb_handle
-{
- usb_handle *prev;
- usb_handle *next;
-
- char fname[64];
- int desc;
+ std::string path;
+ int fd = -1;
unsigned char ep_in;
unsigned char ep_out;
unsigned zero_mask;
- unsigned writeable;
+ unsigned writeable = 1;
- struct usbdevfs_urb urb_in;
- struct usbdevfs_urb urb_out;
+ usbdevfs_urb urb_in;
+ usbdevfs_urb urb_out;
- int urb_in_busy;
- int urb_out_busy;
- int dead;
+ bool urb_in_busy = false;
+ bool urb_out_busy = false;
+ bool dead = false;
- adb_cond_t notify;
- adb_mutex_t lock;
+ std::condition_variable cv;
+ std::mutex mutex;
// for garbage collecting disconnected devices
- int mark;
+ bool mark;
// ID of thread currently in REAPURB
- pthread_t reaper_thread;
+ pthread_t reaper_thread = 0;
};
-static usb_handle handle_list = {
- .prev = &handle_list,
- .next = &handle_list,
-};
+static std::mutex g_usb_handles_mutex;
+static std::list<usb_handle*> g_usb_handles;
-static int known_device(const char *dev_name)
-{
- usb_handle *usb;
-
- adb_mutex_lock(&usb_lock);
- for(usb = handle_list.next; usb != &handle_list; usb = usb->next){
- if(!strcmp(usb->fname, dev_name)) {
+static int is_known_device(const char* dev_name) {
+ std::lock_guard<std::mutex> lock(g_usb_handles_mutex);
+ for (usb_handle* usb : g_usb_handles) {
+ if (usb->path == dev_name) {
// set mark flag to indicate this device is still alive
- usb->mark = 1;
- adb_mutex_unlock(&usb_lock);
+ usb->mark = true;
return 1;
}
}
- adb_mutex_unlock(&usb_lock);
return 0;
}
-static void kick_disconnected_devices()
-{
- usb_handle *usb;
-
- adb_mutex_lock(&usb_lock);
+static void kick_disconnected_devices() {
+ std::lock_guard<std::mutex> lock(g_usb_handles_mutex);
// kick any devices in the device list that were not found in the device scan
- for(usb = handle_list.next; usb != &handle_list; usb = usb->next){
- if (usb->mark == 0) {
+ for (usb_handle* usb : g_usb_handles) {
+ if (!usb->mark) {
usb_kick(usb);
} else {
- usb->mark = 0;
+ usb->mark = false;
}
}
- adb_mutex_unlock(&usb_lock);
-
}
-static inline int badname(const char *name)
-{
- while(*name) {
- if(!isdigit(*name++)) return 1;
+static inline bool contains_non_digit(const char* name) {
+ while (*name) {
+ if (!isdigit(*name++)) return true;
}
- return 0;
+ return false;
}
-static void find_usb_device(const char *base,
+static void find_usb_device(const std::string& base,
void (*register_device_callback)
- (const char *, const char *, unsigned char, unsigned char, int, int, unsigned))
+ (const char*, const char*, unsigned char, unsigned char, int, int, unsigned))
{
- char busname[32], devname[32];
- unsigned char local_ep_in, local_ep_out;
- DIR *busdir , *devdir ;
- struct dirent *de;
- int fd ;
+ std::unique_ptr<DIR, int(*)(DIR*)> bus_dir(opendir(base.c_str()), closedir);
+ if (!bus_dir) return;
- busdir = opendir(base);
- if(busdir == 0) return;
+ dirent* de;
+ while ((de = readdir(bus_dir.get())) != 0) {
+ if (contains_non_digit(de->d_name)) continue;
- while((de = readdir(busdir)) != 0) {
- if(badname(de->d_name)) continue;
+ std::string bus_name = base + "/" + de->d_name;
- snprintf(busname, sizeof busname, "%s/%s", base, de->d_name);
- devdir = opendir(busname);
- if(devdir == 0) continue;
+ std::unique_ptr<DIR, int(*)(DIR*)> dev_dir(opendir(bus_name.c_str()), closedir);
+ if (!dev_dir) continue;
-// DBGX("[ scanning %s ]\n", busname);
- while((de = readdir(devdir))) {
+ while ((de = readdir(dev_dir.get()))) {
unsigned char devdesc[4096];
unsigned char* bufptr = devdesc;
unsigned char* bufend;
@@ -153,22 +141,20 @@
struct usb_endpoint_descriptor *ep1, *ep2;
unsigned zero_mask = 0;
unsigned vid, pid;
- size_t desclength;
- if(badname(de->d_name)) continue;
- snprintf(devname, sizeof devname, "%s/%s", busname, de->d_name);
+ if (contains_non_digit(de->d_name)) continue;
- if(known_device(devname)) {
- DBGX("skipping %s\n", devname);
+ std::string dev_name = bus_name + "/" + de->d_name;
+ if (is_known_device(dev_name.c_str())) {
continue;
}
-// DBGX("[ scanning %s ]\n", devname);
- if((fd = unix_open(devname, O_RDONLY | O_CLOEXEC)) < 0) {
+ int fd = unix_open(dev_name.c_str(), O_RDONLY | O_CLOEXEC);
+ if (fd == -1) {
continue;
}
- desclength = unix_read(fd, devdesc, sizeof(devdesc));
+ size_t desclength = unix_read(fd, devdesc, sizeof(devdesc));
bufend = bufptr + desclength;
// should have device and configuration descriptors, and atleast two endpoints
@@ -188,7 +174,7 @@
vid = device->idVendor;
pid = device->idProduct;
- DBGX("[ %s is V:%04x P:%04x ]\n", devname, vid, pid);
+ DBGX("[ %s is V:%04x P:%04x ]\n", dev_name.c_str(), vid, pid);
// should have config descriptor next
config = (struct usb_config_descriptor *)bufptr;
@@ -225,7 +211,7 @@
struct stat st;
char pathbuf[128];
char link[256];
- char *devpath = NULL;
+ char *devpath = nullptr;
DBGX("looking for bulk endpoints\n");
// looks like ADB...
@@ -267,6 +253,7 @@
}
// we have a match. now we just need to figure out which is in and which is out.
+ unsigned char local_ep_in, local_ep_out;
if (ep1->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
local_ep_in = ep1->bEndpointAddress;
local_ep_out = ep2->bEndpointAddress;
@@ -293,7 +280,7 @@
}
}
- register_device_callback(devname, devpath,
+ register_device_callback(dev_name.c_str(), devpath,
local_ep_in, local_ep_out,
interface->bInterfaceNumber, device->iSerialNumber, zero_mask);
break;
@@ -304,72 +291,54 @@
} // end of while
unix_close(fd);
- } // end of devdir while
- closedir(devdir);
- } //end of busdir while
- closedir(busdir);
+ }
+ }
}
-static int usb_bulk_write(usb_handle *h, const void *data, int len)
-{
- struct usbdevfs_urb *urb = &h->urb_out;
- int res;
- struct timeval tv;
- struct timespec ts;
+static int usb_bulk_write(usb_handle* h, const void* data, int len) {
+ std::unique_lock<std::mutex> lock(h->mutex);
+ D("++ usb_bulk_write ++\n");
+ usbdevfs_urb* urb = &h->urb_out;
memset(urb, 0, sizeof(*urb));
urb->type = USBDEVFS_URB_TYPE_BULK;
urb->endpoint = h->ep_out;
urb->status = -1;
- urb->buffer = (void*) data;
+ urb->buffer = const_cast<void*>(data);
urb->buffer_length = len;
- D("++ write ++\n");
-
- adb_mutex_lock(&h->lock);
- if(h->dead) {
- res = -1;
- goto fail;
- }
- do {
- res = ioctl(h->desc, USBDEVFS_SUBMITURB, urb);
- } while((res < 0) && (errno == EINTR));
-
- if(res < 0) {
- goto fail;
+ if (h->dead) {
+ errno = EINVAL;
+ return -1;
}
- res = -1;
- h->urb_out_busy = 1;
- for(;;) {
- /* time out after five seconds */
- gettimeofday(&tv, NULL);
- ts.tv_sec = tv.tv_sec + 5;
- ts.tv_nsec = tv.tv_usec * 1000L;
- res = pthread_cond_timedwait(&h->notify, &h->lock, &ts);
- if(res < 0 || h->dead) {
- break;
+ if (TEMP_FAILURE_RETRY(ioctl(h->fd, USBDEVFS_SUBMITURB, urb)) == -1) {
+ return -1;
+ }
+
+ h->urb_out_busy = true;
+ while (true) {
+ auto now = std::chrono::system_clock::now();
+ if (h->cv.wait_until(lock, now + 5s) == std::cv_status::timeout || h->dead) {
+ // TODO: call USBDEVFS_DISCARDURB?
+ errno = ETIMEDOUT;
+ return -1;
}
- if(h->urb_out_busy == 0) {
- if(urb->status == 0) {
- res = urb->actual_length;
+ if (!h->urb_out_busy) {
+ if (urb->status != 0) {
+ errno = -urb->status;
+ return -1;
}
- break;
+ return urb->actual_length;
}
}
-fail:
- adb_mutex_unlock(&h->lock);
- D("-- write --\n");
- return res;
}
-static int usb_bulk_read(usb_handle *h, void *data, int len)
-{
- struct usbdevfs_urb *urb = &h->urb_in;
- struct usbdevfs_urb *out = NULL;
- int res;
-
+static int usb_bulk_read(usb_handle* h, void* data, int len) {
+ std::unique_lock<std::mutex> lock(h->mutex);
D("++ usb_bulk_read ++\n");
+
+ usbdevfs_urb* urb = &h->urb_in;
memset(urb, 0, sizeof(*urb));
urb->type = USBDEVFS_URB_TYPE_BULK;
urb->endpoint = h->ep_in;
@@ -377,100 +346,76 @@
urb->buffer = data;
urb->buffer_length = len;
-
- adb_mutex_lock(&h->lock);
- if(h->dead) {
- res = -1;
- goto fail;
- }
- do {
- res = ioctl(h->desc, USBDEVFS_SUBMITURB, urb);
- } while((res < 0) && (errno == EINTR));
-
- if(res < 0) {
- goto fail;
+ if (h->dead) {
+ errno = EINVAL;
+ return -1;
}
- h->urb_in_busy = 1;
- for(;;) {
+ if (TEMP_FAILURE_RETRY(ioctl(h->fd, USBDEVFS_SUBMITURB, urb)) == -1) {
+ return -1;
+ }
+
+ h->urb_in_busy = true;
+ while (true) {
D("[ reap urb - wait ]\n");
h->reaper_thread = pthread_self();
- adb_mutex_unlock(&h->lock);
- res = ioctl(h->desc, USBDEVFS_REAPURB, &out);
+ int fd = h->fd;
+ lock.unlock();
+
+ // This ioctl must not have TEMP_FAILURE_RETRY because we send SIGALRM to break out.
+ usbdevfs_urb* out = nullptr;
+ int res = ioctl(fd, USBDEVFS_REAPURB, &out);
int saved_errno = errno;
- adb_mutex_lock(&h->lock);
+
+ lock.lock();
h->reaper_thread = 0;
- if(h->dead) {
- res = -1;
- break;
+ if (h->dead) {
+ errno = EINVAL;
+ return -1;
}
- if(res < 0) {
- if(saved_errno == EINTR) {
+ if (res < 0) {
+ if (saved_errno == EINTR) {
continue;
}
D("[ reap urb - error ]\n");
- break;
+ errno = saved_errno;
+ return -1;
}
- D("[ urb @%p status = %d, actual = %d ]\n",
- out, out->status, out->actual_length);
+ D("[ urb @%p status = %d, actual = %d ]\n", out, out->status, out->actual_length);
- if(out == &h->urb_in) {
+ if (out == &h->urb_in) {
D("[ reap urb - IN complete ]\n");
- h->urb_in_busy = 0;
- if(urb->status == 0) {
- res = urb->actual_length;
- } else {
- res = -1;
+ h->urb_in_busy = false;
+ if (urb->status != 0) {
+ errno = -urb->status;
+ return -1;
}
- break;
+ return urb->actual_length;
}
- if(out == &h->urb_out) {
+ if (out == &h->urb_out) {
D("[ reap urb - OUT compelete ]\n");
- h->urb_out_busy = 0;
- adb_cond_broadcast(&h->notify);
+ h->urb_out_busy = false;
+ h->cv.notify_all();
}
}
-fail:
- adb_mutex_unlock(&h->lock);
- D("-- usb_bulk_read --\n");
- return res;
}
int usb_write(usb_handle *h, const void *_data, int len)
{
- unsigned char *data = (unsigned char*) _data;
- int n;
- int need_zero = 0;
-
D("++ usb_write ++\n");
- if(h->zero_mask) {
- /* if we need 0-markers and our transfer
- ** is an even multiple of the packet size,
- ** we make note of it
- */
- if(!(len & h->zero_mask)) {
- need_zero = 1;
- }
+
+ unsigned char *data = (unsigned char*) _data;
+ int n = usb_bulk_write(h, data, len);
+ if (n != len) {
+ D("ERROR: n = %d, errno = %d (%s)\n", n, errno, strerror(errno));
+ return -1;
}
- while(len > 0) {
- int xfer = (len > 4096) ? 4096 : len;
-
- n = usb_bulk_write(h, data, xfer);
- if(n != xfer) {
- D("ERROR: n = %d, errno = %d (%s)\n",
- n, errno, strerror(errno));
- return -1;
- }
-
- len -= xfer;
- data += xfer;
- }
-
- if(need_zero){
- n = usb_bulk_write(h, _data, 0);
- return n;
+ if (h->zero_mask && !(len & h->zero_mask)) {
+ // If we need 0-markers and our transfer is an even multiple of the packet size,
+ // then send a zero marker.
+ return usb_bulk_write(h, _data, 0);
}
D("-- usb_write --\n");
@@ -484,13 +429,13 @@
D("++ usb_read ++\n");
while(len > 0) {
- int xfer = (len > 4096) ? 4096 : len;
+ int xfer = len;
- D("[ usb read %d fd = %d], fname=%s\n", xfer, h->desc, h->fname);
+ D("[ usb read %d fd = %d], path=%s\n", xfer, h->fd, h->path.c_str());
n = usb_bulk_read(h, data, xfer);
- D("[ usb read %d ] = %d, fname=%s\n", xfer, n, h->fname);
+ D("[ usb read %d ] = %d, path=%s\n", xfer, n, h->path.c_str());
if(n != xfer) {
- if((errno == ETIMEDOUT) && (h->desc != -1)) {
+ if((errno == ETIMEDOUT) && (h->fd != -1)) {
D("[ timeout ]\n");
if(n > 0){
data += n;
@@ -511,12 +456,11 @@
return 0;
}
-void usb_kick(usb_handle *h)
-{
- D("[ kicking %p (fd = %d) ]\n", h, h->desc);
- adb_mutex_lock(&h->lock);
- if(h->dead == 0) {
- h->dead = 1;
+void usb_kick(usb_handle* h) {
+ std::lock_guard<std::mutex> lock(h->mutex);
+ D("[ kicking %p (fd = %d) ]\n", h, h->fd);
+ if (!h->dead) {
+ h->dead = true;
if (h->writeable) {
/* HACK ALERT!
@@ -532,34 +476,27 @@
** but this ensures that a reader blocked on REAPURB
** will get unblocked
*/
- ioctl(h->desc, USBDEVFS_DISCARDURB, &h->urb_in);
- ioctl(h->desc, USBDEVFS_DISCARDURB, &h->urb_out);
+ ioctl(h->fd, USBDEVFS_DISCARDURB, &h->urb_in);
+ ioctl(h->fd, USBDEVFS_DISCARDURB, &h->urb_out);
h->urb_in.status = -ENODEV;
h->urb_out.status = -ENODEV;
- h->urb_in_busy = 0;
- h->urb_out_busy = 0;
- adb_cond_broadcast(&h->notify);
+ h->urb_in_busy = false;
+ h->urb_out_busy = false;
+ h->cv.notify_all();
} else {
unregister_usb_transport(h);
}
}
- adb_mutex_unlock(&h->lock);
}
-int usb_close(usb_handle *h)
-{
- D("++ usb close ++\n");
- adb_mutex_lock(&usb_lock);
- h->next->prev = h->prev;
- h->prev->next = h->next;
- h->prev = 0;
- h->next = 0;
+int usb_close(usb_handle* h) {
+ std::lock_guard<std::mutex> lock(g_usb_handles_mutex);
+ g_usb_handles.remove(h);
- unix_close(h->desc);
- D("-- usb closed %p (fd = %d) --\n", h, h->desc);
- adb_mutex_unlock(&usb_lock);
+ D("-- usb close %p (fd = %d) --\n", h, h->fd);
- free(h);
+ delete h;
+
return 0;
}
@@ -572,54 +509,44 @@
// from the list when we're finally closed and everything will work out
// fine.
//
- // If we have a usb_handle on the list 'o handles with a matching name, we
+ // If we have a usb_handle on the list of handles with a matching name, we
// have no further work to do.
- adb_mutex_lock(&usb_lock);
- for (usb_handle* usb = handle_list.next; usb != &handle_list; usb = usb->next) {
- if (!strcmp(usb->fname, dev_name)) {
- adb_mutex_unlock(&usb_lock);
- return;
+ {
+ std::lock_guard<std::mutex> lock(g_usb_handles_mutex);
+ for (usb_handle* usb: g_usb_handles) {
+ if (usb->path == dev_name) {
+ return;
+ }
}
}
- adb_mutex_unlock(&usb_lock);
D("[ usb located new device %s (%d/%d/%d) ]\n", dev_name, ep_in, ep_out, interface);
- usb_handle* usb = reinterpret_cast<usb_handle*>(calloc(1, sizeof(usb_handle)));
- if (usb == nullptr) fatal("couldn't allocate usb_handle");
- strcpy(usb->fname, dev_name);
+ std::unique_ptr<usb_handle> usb(new usb_handle);
+ usb->path = dev_name;
usb->ep_in = ep_in;
usb->ep_out = ep_out;
usb->zero_mask = zero_mask;
- usb->writeable = 1;
- adb_cond_init(&usb->notify, 0);
- adb_mutex_init(&usb->lock, 0);
- // Initialize mark to 1 so we don't get garbage collected after the device
- // scan.
- usb->mark = 1;
- usb->reaper_thread = 0;
+ // Initialize mark so we don't get garbage collected after the device scan.
+ usb->mark = true;
- usb->desc = unix_open(usb->fname, O_RDWR | O_CLOEXEC);
- if (usb->desc == -1) {
+ usb->fd = unix_open(usb->path.c_str(), O_RDWR | O_CLOEXEC);
+ if (usb->fd == -1) {
// Opening RW failed, so see if we have RO access.
- usb->desc = unix_open(usb->fname, O_RDONLY | O_CLOEXEC);
- if (usb->desc == -1) {
- D("[ usb open %s failed: %s]\n", usb->fname, strerror(errno));
- free(usb);
+ usb->fd = unix_open(usb->path.c_str(), O_RDONLY | O_CLOEXEC);
+ if (usb->fd == -1) {
+ D("[ usb open %s failed: %s]\n", usb->path.c_str(), strerror(errno));
return;
}
usb->writeable = 0;
}
- D("[ usb opened %s%s, fd=%d]\n", usb->fname,
- (usb->writeable ? "" : " (read-only)"), usb->desc);
+ D("[ usb opened %s%s, fd=%d]\n",
+ usb->path.c_str(), (usb->writeable ? "" : " (read-only)"), usb->fd);
if (usb->writeable) {
- if (ioctl(usb->desc, USBDEVFS_CLAIMINTERFACE, &interface) != 0) {
- D("[ usb ioctl(%d, USBDEVFS_CLAIMINTERFACE) failed: %s]\n",
- usb->desc, strerror(errno));
- unix_close(usb->desc);
- free(usb);
+ if (ioctl(usb->fd, USBDEVFS_CLAIMINTERFACE, &interface) != 0) {
+ D("[ usb ioctl(%d, USBDEVFS_CLAIMINTERFACE) failed: %s]\n", usb->fd, strerror(errno));
return;
}
}
@@ -638,14 +565,12 @@
serial = android::base::Trim(serial);
// Add to the end of the active handles.
- adb_mutex_lock(&usb_lock);
- usb->next = &handle_list;
- usb->prev = handle_list.prev;
- usb->prev->next = usb;
- usb->next->prev = usb;
- adb_mutex_unlock(&usb_lock);
-
- register_usb_transport(usb, serial.c_str(), dev_path, usb->writeable);
+ usb_handle* done_usb = usb.release();
+ {
+ std::lock_guard<std::mutex> lock(g_usb_handles_mutex);
+ g_usb_handles.push_back(done_usb);
+ }
+ register_usb_transport(done_usb, serial.c_str(), dev_path, done_usb->writeable);
}
static void* device_poll_thread(void* unused) {
@@ -659,19 +584,13 @@
return nullptr;
}
-static void sigalrm_handler(int signo) {
- // don't need to do anything here
-}
-
-void usb_init()
-{
- struct sigaction actions;
-
+void usb_init() {
+ struct sigaction actions;
memset(&actions, 0, sizeof(actions));
sigemptyset(&actions.sa_mask);
actions.sa_flags = 0;
- actions.sa_handler = sigalrm_handler;
- sigaction(SIGALRM,& actions, NULL);
+ actions.sa_handler = [](int) {};
+ sigaction(SIGALRM, &actions, nullptr);
if (!adb_thread_create(device_poll_thread, nullptr)) {
fatal_errno("cannot create input thread");
diff --git a/adb/usb_linux_client.cpp b/adb/usb_linux_client.cpp
index d34c454..b1b3538 100644
--- a/adb/usb_linux_client.cpp
+++ b/adb/usb_linux_client.cpp
@@ -213,14 +213,20 @@
static int usb_adb_read(usb_handle *h, void *data, int len)
{
- int n;
-
D("about to read (fd=%d, len=%d)\n", h->fd, len);
- n = unix_read(h->fd, data, len);
- if(n != len) {
- D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
- h->fd, n, errno, strerror(errno));
- return -1;
+ while (len > 0) {
+ // The kernel implementation of adb_read in f_adb.c doesn't support
+ // reads larger then 4096 bytes. Read the data in 4096 byte chunks to
+ // avoid the issue. (The ffs implementation doesn't have this limit.)
+ int bytes_to_read = len < 4096 ? len : 4096;
+ int n = unix_read(h->fd, data, bytes_to_read);
+ if (n != bytes_to_read) {
+ D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+ h->fd, n, errno, strerror(errno));
+ return -1;
+ }
+ len -= n;
+ data = ((char*)data) + n;
}
D("[ done fd=%d ]\n", h->fd);
return 0;
@@ -444,11 +450,11 @@
err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT);
if (err < 0)
- D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in, errno);
+ D("[ kick: source (fd=%d) clear halt failed (%d) ]\n", h->bulk_in, errno);
err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT);
if (err < 0)
- D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out, errno);
+ D("[ kick: sink (fd=%d) clear halt failed (%d) ]\n", h->bulk_out, errno);
adb_mutex_lock(&h->lock);
diff --git a/adb/usb_windows.cpp b/adb/usb_windows.cpp
index 25deb1b..4c9a152 100644
--- a/adb/usb_windows.cpp
+++ b/adb/usb_windows.cpp
@@ -298,20 +298,13 @@
int usb_read(usb_handle *handle, void* data, int len) {
unsigned long time_out = 0;
unsigned long read = 0;
- int ret;
D("usb_read %d\n", len);
- if (NULL != handle) {
+ if (handle != nullptr) {
while (len > 0) {
- int xfer = (len > 4096) ? 4096 : len;
-
- ret = AdbReadEndpointSync(handle->adb_read_pipe,
- data,
- (unsigned long)xfer,
- &read,
- time_out);
+ int ret = AdbReadEndpointSync(handle->adb_read_pipe, data, len, &read, time_out);
int saved_errno = GetLastError();
- D("usb_write got: %ld, expected: %d, errno: %d\n", read, xfer, saved_errno);
+ D("usb_write got: %ld, expected: %d, errno: %d\n", read, len, saved_errno);
if (ret) {
data = (char *)data + read;
len -= read;
diff --git a/adf/libadf/adf.c b/adf/libadf/adf.c
index 66c329c..c4d6681 100644
--- a/adf/libadf/adf.c
+++ b/adf/libadf/adf.c
@@ -87,7 +87,6 @@
int adf_device_open(adf_id_t id, int flags, struct adf_device *dev)
{
char filename[64];
- int err;
dev->id = id;
diff --git a/crash_reporter/.project_alias b/crash_reporter/.project_alias
new file mode 100644
index 0000000..0bc3798
--- /dev/null
+++ b/crash_reporter/.project_alias
@@ -0,0 +1 @@
+crash
diff --git a/crash_reporter/99-crash-reporter.rules b/crash_reporter/99-crash-reporter.rules
new file mode 100644
index 0000000..aea5b1c
--- /dev/null
+++ b/crash_reporter/99-crash-reporter.rules
@@ -0,0 +1,6 @@
+ACTION=="change", SUBSYSTEM=="drm", KERNEL=="card0", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=KERNEL=card0:SUBSYSTEM=drm:ACTION=change"
+# For detecting cypress trackpad issue. Passing into crash_reporter SUBSYSTEM=i2c-cyapa since crash_reporter does not handle DRIVER string.
+ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="cyapa", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-cyapa:ACTION=change"
+# For detecting Atmel trackpad/touchscreen issue. Passing into crash_reporter SUBSYSTEM=i2c-atmel_mxt_ts since crash_reporter does not handle DRIVER string.
+ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="atmel_mxt_ts", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-atmel_mxt_ts:ACTION=change"
+ACTION=="add", SUBSYSTEM=="devcoredump", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=devcoredump:ACTION=add:KERNEL_NUMBER=%n"
diff --git a/crash_reporter/OWNERS b/crash_reporter/OWNERS
new file mode 100644
index 0000000..96ea5b2
--- /dev/null
+++ b/crash_reporter/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+vapier@chromium.org
diff --git a/crash_reporter/TEST_WARNING b/crash_reporter/TEST_WARNING
new file mode 100644
index 0000000..64ad2e9
--- /dev/null
+++ b/crash_reporter/TEST_WARNING
@@ -0,0 +1,31 @@
+Apr 31 25:25:25 localhost kernel: [117959.226729] [<ffffffff810e16bf>] do_vfs_ioctl+0x469/0x4b3
+Apr 31 25:25:25 localhost kernel: [117959.226738] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
+Apr 31 25:25:25 localhost kernel: [117959.226747] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
+Apr 31 25:25:25 localhost kernel: [117959.226756] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
+Apr 31 25:25:25 localhost kernel: [117959.226765] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
+Apr 31 25:25:25 localhost kernel: [117959.226774] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
+Apr 31 25:25:25 localhost kernel: [117959.226782] ---[ end trace f16822cad7406cec ]---
+Apr 31 25:25:25 localhost kernel: [117959.231085] ------------[ cut here ]------------
+Apr 31 25:25:25 localhost kernel: [117959.231100] WARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
+Apr 31 25:25:25 localhost kernel: [117959.231113] Hardware name: Link
+Apr 31 25:25:25 localhost kernel: [117959.231117] eDP powered off while attempting aux channel communication.
+Apr 31 25:25:25 localhost kernel: [117959.231240] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
+Apr 31 25:25:25 localhost kernel: [117959.231247] Call Trace:
+Apr 31 25:25:25 localhost kernel: [117959.231393] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
+Apr 31 25:25:25 localhost kernel: [117959.231402] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
+Apr 31 25:25:25 localhost kernel: [117959.231411] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
+Apr 31 25:25:25 localhost kernel: [117959.231420] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
+Apr 31 25:25:25 localhost kernel: [117959.231431] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
+Apr 31 25:25:25 localhost kernel: [117959.231439] ---[ end trace f16822cad7406ced ]---
+Apr 31 25:25:25 localhost kernel: [117959.231450] ------------[ cut here ]------------
+Apr 31 25:25:25 localhost kernel: [117959.231458] BARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
+Apr 31 25:25:25 localhost kernel: [117959.231458] ("BARNING" above is intentional)
+Apr 31 25:25:25 localhost kernel: [117959.231471] Hardware name: Link
+Apr 31 25:25:25 localhost kernel: [117959.231475] eDP powered off while attempting aux channel communication.
+Apr 31 25:25:25 localhost kernel: [117959.231482] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat rfcomm i2c_dev ath9k_btcoex snd_hda_codec_hdmi snd_hda_codec_ca0132 mac80211 snd_hda_intel ath9k_common_btcoex snd_hda_codec ath9k_hw_btcoex aesni_intel cryptd snd_hwdep ath snd_pcm aes_x86_64 isl29018(C) memconsole snd_timer snd_page_alloc industrialio(C) cfg80211 rtc_cmos nm10_gpio zram(C) zsmalloc(C) lzo_decompress lzo_compress fuse nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables xt_mark option usb_wwan cdc_ether usbnet ath3k btusb bluetooth uvcvideo videobuf2_core videodev videobuf2_vmalloc videobuf2_memops joydev
+Apr 31 25:25:25 localhost kernel: [117959.231588] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
+Apr 31 25:25:25 localhost kernel: [117959.231595] Call Trace:
+Apr 31 25:25:25 localhost kernel: [117959.231601] [<ffffffff8102a931>] warn_slowpath_common+0x83/0x9c
+Apr 31 25:25:25 localhost kernel: [117959.231610] [<ffffffff8102a9ed>] warn_slowpath_fmt+0x46/0x48
+Apr 31 25:25:25 localhost kernel: [117959.231620] [<ffffffff812af495>] intel_dp_check_edp+0x6b/0xb9
+Apr 31 25:25:25 localhost kernel: [117959.231629] [<ffffffff8102a9ed>] ? warn_slowpath_fmt+
diff --git a/crash_reporter/chrome_collector.cc b/crash_reporter/chrome_collector.cc
new file mode 100644
index 0000000..ec291c0
--- /dev/null
+++ b/crash_reporter/chrome_collector.cc
@@ -0,0 +1,335 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/chrome_collector.h"
+
+#include <pcrecpp.h>
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <chromeos/data_encoding.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/process.h>
+#include <chromeos/syslog_logging.h>
+
+using base::FilePath;
+using base::StringPrintf;
+
+namespace {
+
+const char kDefaultMinidumpName[] = "upload_file_minidump";
+
+// Path to the gzip binary.
+const char kGzipPath[] = "/bin/gzip";
+
+// Filenames for logs attached to crash reports. Also used as metadata keys.
+const char kChromeLogFilename[] = "chrome.txt";
+const char kGpuStateFilename[] = "i915_error_state.log.xz";
+
+// From //net/crash/collector/collector.h
+const int kDefaultMaxUploadBytes = 1024 * 1024;
+
+// Extract a string delimited by the given character, from the given offset
+// into a source string. Returns false if the string is zero-sized or no
+// delimiter was found.
+bool GetDelimitedString(const std::string &str, char ch, size_t offset,
+ std::string *substr) {
+ size_t at = str.find_first_of(ch, offset);
+ if (at == std::string::npos || at == offset)
+ return false;
+ *substr = str.substr(offset, at - offset);
+ return true;
+}
+
+// Gets the GPU's error state from debugd and writes it to |error_state_path|.
+// Returns true on success.
+bool GetDriErrorState(const FilePath &error_state_path,
+ org::chromium::debugdProxy *proxy) {
+ chromeos::ErrorPtr error;
+ std::string error_state_str;
+
+ proxy->GetLog("i915_error_state", &error_state_str, &error);
+
+ if (error) {
+ LOG(ERROR) << "Error calling D-Bus proxy call to interface "
+ << "'" << proxy->GetObjectPath().value() << "':"
+ << error->GetMessage();
+ return false;
+ }
+
+ if (error_state_str == "<empty>")
+ return false;
+
+ const char kBase64Header[] = "<base64>: ";
+ const size_t kBase64HeaderLength = sizeof(kBase64Header) - 1;
+ if (error_state_str.compare(0, kBase64HeaderLength, kBase64Header)) {
+ LOG(ERROR) << "i915_error_state is missing base64 header";
+ return false;
+ }
+
+ std::string decoded_error_state;
+
+ if (!chromeos::data_encoding::Base64Decode(
+ error_state_str.c_str() + kBase64HeaderLength,
+ &decoded_error_state)) {
+ LOG(ERROR) << "Could not decode i915_error_state";
+ return false;
+ }
+
+ int written = base::WriteFile(error_state_path,
+ decoded_error_state.c_str(),
+ decoded_error_state.length());
+ if (written < 0 ||
+ static_cast<size_t>(written) != decoded_error_state.length()) {
+ LOG(ERROR) << "Could not write file " << error_state_path.value()
+ << " Written: " << written << " Len: "
+ << decoded_error_state.length();
+ base::DeleteFile(error_state_path, false);
+ return false;
+ }
+
+ return true;
+}
+
+// Gzip-compresses |path|, removes the original file, and returns the path of
+// the new file. On failure, the original file is left alone and an empty path
+// is returned.
+FilePath GzipFile(const FilePath& path) {
+ chromeos::ProcessImpl proc;
+ proc.AddArg(kGzipPath);
+ proc.AddArg(path.value());
+ const int res = proc.Run();
+ if (res != 0) {
+ LOG(ERROR) << "Failed to gzip " << path.value();
+ return FilePath();
+ }
+ return path.AddExtension(".gz");
+}
+
+} // namespace
+
+
+ChromeCollector::ChromeCollector() : output_file_ptr_(stdout) {}
+
+ChromeCollector::~ChromeCollector() {}
+
+bool ChromeCollector::HandleCrash(const FilePath &file_path,
+ const std::string &pid_string,
+ const std::string &uid_string,
+ const std::string &exe_name) {
+ if (!is_feedback_allowed_function_())
+ return true;
+
+ LOG(WARNING) << "Received crash notification for " << exe_name << "["
+ << pid_string << "] user " << uid_string << " (called directly)";
+
+ if (exe_name.find('/') != std::string::npos) {
+ LOG(ERROR) << "exe_name contains illegal characters: " << exe_name;
+ return false;
+ }
+
+ FilePath dir;
+ uid_t uid = atoi(uid_string.c_str());
+ pid_t pid = atoi(pid_string.c_str());
+ if (!GetCreatedCrashDirectoryByEuid(uid, &dir, nullptr)) {
+ LOG(ERROR) << "Can't create crash directory for uid " << uid;
+ return false;
+ }
+
+ std::string dump_basename = FormatDumpBasename(exe_name, time(nullptr), pid);
+ FilePath meta_path = GetCrashPath(dir, dump_basename, "meta");
+ FilePath minidump_path = GetCrashPath(dir, dump_basename, "dmp");
+
+ std::string data;
+ if (!base::ReadFileToString(file_path, &data)) {
+ LOG(ERROR) << "Can't read crash log: " << file_path.value();
+ return false;
+ }
+
+ if (!ParseCrashLog(data, dir, minidump_path, dump_basename)) {
+ LOG(ERROR) << "Failed to parse Chrome's crash log";
+ return false;
+ }
+
+
+ int64_t report_size = 0;
+ base::GetFileSize(minidump_path, &report_size);
+
+ // Keyed by crash metadata key name.
+ const std::map<std::string, base::FilePath> additional_logs =
+ GetAdditionalLogs(dir, dump_basename, exe_name);
+ for (auto it : additional_logs) {
+ int64_t file_size = 0;
+ if (!base::GetFileSize(it.second, &file_size)) {
+ PLOG(WARNING) << "Unable to get size of " << it.second.value();
+ continue;
+ }
+ if (report_size + file_size > kDefaultMaxUploadBytes) {
+ LOG(INFO) << "Skipping upload of " << it.second.value() << "("
+ << file_size << "B) because report size would exceed limit ("
+ << kDefaultMaxUploadBytes << "B)";
+ continue;
+ }
+ VLOG(1) << "Adding metadata: " << it.first << " -> " << it.second.value();
+ // Call AddCrashMetaUploadFile() rather than AddCrashMetaData() here. The
+ // former adds a prefix to the key name; without the prefix, only the key
+ // "logs" appears to be displayed on the crash server.
+ AddCrashMetaUploadFile(it.first, it.second.value());
+ report_size += file_size;
+ }
+
+ // We're done.
+ WriteCrashMetaData(meta_path, exe_name, minidump_path.value());
+
+ fprintf(output_file_ptr_, "%s", kSuccessMagic);
+ fflush(output_file_ptr_);
+
+ return true;
+}
+
+void ChromeCollector::SetUpDBus() {
+ CrashCollector::SetUpDBus();
+
+ debugd_proxy_.reset(
+ new org::chromium::debugdProxy(bus_, debugd::kDebugdServiceName));
+}
+
+bool ChromeCollector::ParseCrashLog(const std::string &data,
+ const FilePath &dir,
+ const FilePath &minidump,
+ const std::string &basename) {
+ size_t at = 0;
+ while (at < data.size()) {
+ // Look for a : followed by a decimal number, followed by another :
+ // followed by N bytes of data.
+ std::string name, size_string;
+ if (!GetDelimitedString(data, ':', at, &name)) {
+ LOG(ERROR) << "Can't find : after name @ offset " << at;
+ break;
+ }
+ at += name.size() + 1; // Skip the name & : delimiter.
+
+ if (!GetDelimitedString(data, ':', at, &size_string)) {
+ LOG(ERROR) << "Can't find : after size @ offset " << at;
+ break;
+ }
+ at += size_string.size() + 1; // Skip the size & : delimiter.
+
+ size_t size;
+ if (!base::StringToSizeT(size_string, &size)) {
+ LOG(ERROR) << "String not convertible to integer: " << size_string;
+ break;
+ }
+
+ // Data would run past the end, did we get a truncated file?
+ if (at + size > data.size()) {
+ LOG(ERROR) << "Overrun, expected " << size << " bytes of data, got "
+ << (data.size() - at);
+ break;
+ }
+
+ if (name.find("filename") != std::string::npos) {
+ // File.
+ // Name will be in a semi-MIME format of
+ // <descriptive name>"; filename="<name>"
+ // Descriptive name will be upload_file_minidump for the dump.
+ std::string desc, filename;
+ pcrecpp::RE re("(.*)\" *; *filename=\"(.*)\"");
+ if (!re.FullMatch(name.c_str(), &desc, &filename)) {
+ LOG(ERROR) << "Filename was not in expected format: " << name;
+ break;
+ }
+
+ if (desc.compare(kDefaultMinidumpName) == 0) {
+ // The minidump.
+ WriteNewFile(minidump, data.c_str() + at, size);
+ } else {
+ // Some other file.
+ FilePath path = GetCrashPath(dir, basename + "-" + filename, "other");
+ if (WriteNewFile(path, data.c_str() + at, size) >= 0) {
+ AddCrashMetaUploadFile(desc, path.value());
+ }
+ }
+ } else {
+ // Other attribute.
+ std::string value_str;
+ value_str.reserve(size);
+
+ // Since metadata is one line/value the values must be escaped properly.
+ for (size_t i = at; i < at + size; i++) {
+ switch (data[i]) {
+ case '"':
+ case '\\':
+ value_str.push_back('\\');
+ value_str.push_back(data[i]);
+ break;
+
+ case '\r':
+ value_str += "\\r";
+ break;
+
+ case '\n':
+ value_str += "\\n";
+ break;
+
+ case '\t':
+ value_str += "\\t";
+ break;
+
+ case '\0':
+ value_str += "\\0";
+ break;
+
+ default:
+ value_str.push_back(data[i]);
+ break;
+ }
+ }
+ AddCrashMetaUploadData(name, value_str);
+ }
+
+ at += size;
+ }
+
+ return at == data.size();
+}
+
+std::map<std::string, base::FilePath> ChromeCollector::GetAdditionalLogs(
+ const FilePath &dir,
+ const std::string &basename,
+ const std::string &exe_name) {
+ std::map<std::string, base::FilePath> logs;
+
+ // Run the command specified by the config file to gather logs.
+ const FilePath chrome_log_path =
+ GetCrashPath(dir, basename, kChromeLogFilename);
+ if (GetLogContents(log_config_path_, exe_name, chrome_log_path)) {
+ const FilePath compressed_path = GzipFile(chrome_log_path);
+ if (!compressed_path.empty())
+ logs[kChromeLogFilename] = compressed_path;
+ else
+ base::DeleteFile(chrome_log_path, false /* recursive */);
+ }
+
+ // For unit testing, debugd_proxy_ isn't initialized, so skip attempting to
+ // get the GPU error state from debugd.
+ if (debugd_proxy_) {
+ const FilePath dri_error_state_path =
+ GetCrashPath(dir, basename, kGpuStateFilename);
+ if (GetDriErrorState(dri_error_state_path, debugd_proxy_.get()))
+ logs[kGpuStateFilename] = dri_error_state_path;
+ }
+
+ return logs;
+}
+
+// static
+const char ChromeCollector::kSuccessMagic[] = "_sys_cr_finished";
diff --git a/crash_reporter/chrome_collector.h b/crash_reporter/chrome_collector.h
new file mode 100644
index 0000000..0b58c19
--- /dev/null
+++ b/crash_reporter/chrome_collector.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_CHROME_COLLECTOR_H_
+#define CRASH_REPORTER_CHROME_COLLECTOR_H_
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "crash-reporter/crash_collector.h"
+#include "debugd/dbus-proxies.h"
+
+class SystemLogging;
+
+// Chrome crash collector.
+class ChromeCollector : public CrashCollector {
+ public:
+ ChromeCollector();
+ ~ChromeCollector() override;
+
+ // Magic string to let Chrome know the crash report succeeded.
+ static const char kSuccessMagic[];
+
+ // Handle a specific chrome crash. Returns true on success.
+ bool HandleCrash(const base::FilePath &file_path,
+ const std::string &pid_string,
+ const std::string &uid_string,
+ const std::string &exe_name);
+
+ protected:
+ void SetUpDBus() override;
+
+ private:
+ friend class ChromeCollectorTest;
+ FRIEND_TEST(ChromeCollectorTest, GoodValues);
+ FRIEND_TEST(ChromeCollectorTest, BadValues);
+ FRIEND_TEST(ChromeCollectorTest, Newlines);
+ FRIEND_TEST(ChromeCollectorTest, File);
+ FRIEND_TEST(ChromeCollectorTest, HandleCrash);
+
+ // Crashes are expected to be in a TLV-style format of:
+ // <name>:<length>:<value>
+ // Length is encoded as a decimal number. It can be zero, but must consist of
+ // at least one character
+ // For file values, name actually contains both a description and a filename,
+ // in a fixed format of: <description>"; filename="<filename>"
+ bool ParseCrashLog(const std::string &data, const base::FilePath &dir,
+ const base::FilePath &minidump,
+ const std::string &basename);
+
+ // Writes additional logs for |exe_name| to files based on |basename| within
+ // |dir|. Crash report metadata key names and the corresponding file paths are
+ // returned.
+ std::map<std::string, base::FilePath> GetAdditionalLogs(
+ const base::FilePath &dir,
+ const std::string &basename,
+ const std::string &exe_name);
+
+ FILE *output_file_ptr_;
+
+ // D-Bus proxy for debugd interface. Unset in unit tests.
+ std::unique_ptr<org::chromium::debugdProxy> debugd_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeCollector);
+};
+
+#endif // CRASH_REPORTER_CHROME_COLLECTOR_H_
diff --git a/crash_reporter/chrome_collector_test.cc b/crash_reporter/chrome_collector_test.cc
new file mode 100644
index 0000000..0d6a7ce
--- /dev/null
+++ b/crash_reporter/chrome_collector_test.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/chrome_collector.h"
+
+#include <stdio.h>
+
+#include <base/auto_reset.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <chromeos/syslog_logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+
+namespace {
+
+const char kCrashFormatGood[] = "value1:10:abcdefghijvalue2:5:12345";
+const char kCrashFormatEmbeddedNewline[] =
+ "value1:10:abcd\r\nghijvalue2:5:12\n34";
+const char kCrashFormatBad1[] = "value1:10:abcdefghijvalue2:6=12345";
+const char kCrashFormatBad2[] = "value1:10:abcdefghijvalue2:512345";
+const char kCrashFormatBad3[] = "value1:10::abcdefghijvalue2:5=12345";
+const char kCrashFormatBad4[] = "value1:10:abcdefghijvalue2:4=12345";
+
+const char kCrashFormatWithFile[] =
+ "value1:10:abcdefghijvalue2:5:12345"
+ "some_file\"; filename=\"foo.txt\":15:12345\n789\n12345"
+ "value3:2:ok";
+
+void CountCrash() {
+}
+
+bool s_allow_crash = false;
+
+bool IsMetrics() {
+ return s_allow_crash;
+}
+
+} // namespace
+
+class ChromeCollectorMock : public ChromeCollector {
+ public:
+ MOCK_METHOD0(SetUpDBus, void());
+};
+
+class ChromeCollectorTest : public ::testing::Test {
+ protected:
+ void ExpectFileEquals(const char *golden,
+ const FilePath &file_path) {
+ std::string contents;
+ EXPECT_TRUE(base::ReadFileToString(file_path, &contents));
+ EXPECT_EQ(golden, contents);
+ }
+
+ ChromeCollectorMock collector_;
+
+ private:
+ void SetUp() override {
+ EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+ collector_.Initialize(CountCrash, IsMetrics);
+ chromeos::ClearLog();
+ }
+};
+
+TEST_F(ChromeCollectorTest, GoodValues) {
+ FilePath dir(".");
+ EXPECT_TRUE(collector_.ParseCrashLog(kCrashFormatGood,
+ dir, dir.Append("minidump.dmp"),
+ "base"));
+
+ // Check to see if the values made it in properly.
+ std::string meta = collector_.extra_metadata_;
+ EXPECT_TRUE(meta.find("value1=abcdefghij") != std::string::npos);
+ EXPECT_TRUE(meta.find("value2=12345") != std::string::npos);
+}
+
+TEST_F(ChromeCollectorTest, Newlines) {
+ FilePath dir(".");
+ EXPECT_TRUE(collector_.ParseCrashLog(kCrashFormatEmbeddedNewline,
+ dir, dir.Append("minidump.dmp"),
+ "base"));
+
+ // Check to see if the values were escaped.
+ std::string meta = collector_.extra_metadata_;
+ EXPECT_TRUE(meta.find("value1=abcd\\r\\nghij") != std::string::npos);
+ EXPECT_TRUE(meta.find("value2=12\\n34") != std::string::npos);
+}
+
+TEST_F(ChromeCollectorTest, BadValues) {
+ FilePath dir(".");
+ const struct {
+ const char *data;
+ } list[] = {
+ {kCrashFormatBad1, },
+ {kCrashFormatBad2, },
+ {kCrashFormatBad3, },
+ {kCrashFormatBad4, },
+ };
+
+ for (size_t i = 0; i < sizeof(list) / sizeof(list[0]); i++) {
+ chromeos::ClearLog();
+ EXPECT_FALSE(collector_.ParseCrashLog(list[i].data,
+ dir, dir.Append("minidump.dmp"),
+ "base"));
+ }
+}
+
+TEST_F(ChromeCollectorTest, File) {
+ base::ScopedTempDir scoped_temp_dir;
+ ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+ const FilePath& dir = scoped_temp_dir.path();
+ EXPECT_TRUE(collector_.ParseCrashLog(kCrashFormatWithFile,
+ dir, dir.Append("minidump.dmp"),
+ "base"));
+
+ // Check to see if the values are still correct and that the file was
+ // written with the right data.
+ std::string meta = collector_.extra_metadata_;
+ EXPECT_TRUE(meta.find("value1=abcdefghij") != std::string::npos);
+ EXPECT_TRUE(meta.find("value2=12345") != std::string::npos);
+ EXPECT_TRUE(meta.find("value3=ok") != std::string::npos);
+ ExpectFileEquals("12345\n789\n12345", dir.Append("base-foo.txt.other"));
+}
+
+TEST_F(ChromeCollectorTest, HandleCrash) {
+ base::AutoReset<bool> auto_reset(&s_allow_crash, true);
+ base::ScopedTempDir scoped_temp_dir;
+ ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+ const FilePath& dir = scoped_temp_dir.path();
+ FilePath dump_file = dir.Append("test.dmp");
+ ASSERT_EQ(strlen(kCrashFormatWithFile),
+ base::WriteFile(dump_file, kCrashFormatWithFile,
+ strlen(kCrashFormatWithFile)));
+ collector_.ForceCrashDirectory(dir);
+
+ FilePath log_file;
+ {
+ base::ScopedFILE output(
+ base::CreateAndOpenTemporaryFileInDir(dir, &log_file));
+ ASSERT_TRUE(output.get());
+ base::AutoReset<FILE*> auto_reset_file_ptr(&collector_.output_file_ptr_,
+ output.get());
+ EXPECT_TRUE(collector_.HandleCrash(dump_file, "123", "456", "chrome_test"));
+ }
+ ExpectFileEquals(ChromeCollector::kSuccessMagic, log_file);
+}
diff --git a/crash_reporter/crash-reporter.gyp b/crash_reporter/crash-reporter.gyp
new file mode 100644
index 0000000..a7f0e7e
--- /dev/null
+++ b/crash_reporter/crash-reporter.gyp
@@ -0,0 +1,147 @@
+{
+ # Shouldn't need this, but doesn't work otherwise.
+ # http://crbug.com/340086 and http://crbug.com/385186
+ # Note: the unused dependencies are optimized out by the compiler.
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libchromeos-<(libbase_ver)',
+ ],
+ },
+ },
+ 'targets': [
+ {
+ 'target_name': 'libcrash',
+ 'type': 'static_library',
+ 'variables': {
+ 'exported_deps': [
+ 'libchrome-<(libbase_ver)',
+ 'libpcrecpp',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'chrome_collector.cc',
+ 'crash_collector.cc',
+ 'kernel_collector.cc',
+ 'kernel_warning_collector.cc',
+ 'udev_collector.cc',
+ 'unclean_shutdown_collector.cc',
+ 'user_collector.cc',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'generate-session-manager-proxies',
+ 'variables': {
+ 'proxy_output_file': 'include/session_manager/dbus-proxies.h'
+ },
+ 'sources': [
+ '../login_manager/org.chromium.SessionManagerInterface.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ {
+ 'action_name': 'generate-debugd-proxies',
+ 'variables': {
+ 'proxy_output_file': 'include/debugd/dbus-proxies.h'
+ },
+ 'sources': [
+ '../debugd/share/org.chromium.debugd.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ ],
+ },
+ {
+ 'target_name': 'crash_reporter',
+ 'type': 'executable',
+ 'variables': {
+ 'deps': [
+ 'dbus-1',
+ 'libmetrics-<(libbase_ver)',
+ ],
+ },
+ 'dependencies': [
+ 'libcrash',
+ ],
+ 'sources': [
+ 'crash_reporter.cc',
+ ],
+ },
+ {
+ 'target_name': 'list_proxies',
+ 'type': 'executable',
+ 'variables': {
+ 'deps': [
+ 'dbus-1',
+ 'libchrome-<(libbase_ver)',
+ ],
+ },
+ 'sources': [
+ 'list_proxies.cc',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'generate-lib-cros-service-proxies',
+ 'variables': {
+ 'proxy_output_file': 'include/libcrosservice/dbus-proxies.h'
+ },
+ 'sources': [
+ './dbus_bindings/org.chromium.LibCrosService.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ ],
+ },
+ {
+ 'target_name': 'warn_collector',
+ 'type': 'executable',
+ 'variables': {
+ 'lexer_out_dir': 'crash-reporter',
+ 'deps': [
+ 'libmetrics-<(libbase_ver)',
+ ],
+ },
+ 'link_settings': {
+ 'libraries': [
+ '-lfl',
+ ],
+ },
+ 'sources': [
+ 'warn_collector.l',
+ ],
+ 'includes': ['../common-mk/lex.gypi'],
+ },
+ ],
+ 'conditions': [
+ ['USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'crash_reporter_test',
+ 'type': 'executable',
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'dependencies': ['libcrash'],
+ 'sources': [
+ 'chrome_collector_test.cc',
+ 'crash_collector_test.cc',
+ 'crash_collector_test.h',
+ 'crash_reporter_logs_test.cc',
+ 'kernel_collector_test.cc',
+ 'kernel_collector_test.h',
+ 'testrunner.cc',
+ 'udev_collector_test.cc',
+ 'unclean_shutdown_collector_test.cc',
+ 'user_collector_test.cc',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/crash_reporter/crash_collector.cc b/crash_reporter/crash_collector.cc
new file mode 100644
index 0000000..04f3ba8
--- /dev/null
+++ b/crash_reporter/crash_collector.cc
@@ -0,0 +1,512 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/crash_collector.h"
+
+#include <dirent.h>
+#include <fcntl.h> // For file creation modes.
+#include <inttypes.h>
+#include <linux/limits.h> // PATH_MAX
+#include <pwd.h> // For struct passwd.
+#include <sys/types.h> // for mode_t.
+#include <sys/wait.h> // For waitpid.
+#include <unistd.h> // For execv and fork.
+
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/cryptohome.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/key_value_store.h>
+#include <chromeos/process.h>
+
+namespace {
+
+const char kCollectChromeFile[] =
+ "/mnt/stateful_partition/etc/collect_chrome_crashes";
+const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress";
+const char kDefaultLogConfig[] = "/etc/crash_reporter_logs.conf";
+const char kDefaultUserName[] = "chronos";
+const char kLeaveCoreFile[] = "/root/.leave_core";
+const char kLsbRelease[] = "/etc/lsb-release";
+const char kShellPath[] = "/bin/sh";
+const char kSystemCrashPath[] = "/var/spool/crash";
+const char kUploadVarPrefix[] = "upload_var_";
+const char kUploadFilePrefix[] = "upload_file_";
+
+// Key of the lsb-release entry containing the OS version.
+const char kLsbVersionKey[] = "CHROMEOS_RELEASE_VERSION";
+
+// Normally this path is not used. Unfortunately, there are a few edge cases
+// where we need this. Any process that runs as kDefaultUserName that crashes
+// is consider a "user crash". That includes the initial Chrome browser that
+// runs the login screen. If that blows up, there is no logged in user yet,
+// so there is no per-user dir for us to stash things in. Instead we fallback
+// to this path as it is at least encrypted on a per-system basis.
+//
+// This also comes up when running autotests. The GUI is sitting at the login
+// screen while tests are sshing in, changing users, and triggering crashes as
+// the user (purposefully).
+const char kFallbackUserCrashPath[] = "/home/chronos/crash";
+
+// Directory mode of the user crash spool directory.
+const mode_t kUserCrashPathMode = 0755;
+
+// Directory mode of the system crash spool directory.
+const mode_t kSystemCrashPathMode = 01755;
+
+const uid_t kRootOwner = 0;
+const uid_t kRootGroup = 0;
+
+} // namespace
+
+// Maximum crash reports per crash spool directory. Note that this is
+// a separate maximum from the maximum rate at which we upload these
+// diagnostics. The higher this rate is, the more space we allow for
+// core files, minidumps, and kcrash logs, and equivalently the more
+// processor and I/O bandwidth we dedicate to handling these crashes when
+// many occur at once. Also note that if core files are configured to
+// be left on the file system, we stop adding crashes when either the
+// number of core files or minidumps reaches this number.
+const int CrashCollector::kMaxCrashDirectorySize = 32;
+
+using base::FilePath;
+using base::StringPrintf;
+
+CrashCollector::CrashCollector()
+ : lsb_release_(kLsbRelease),
+ log_config_path_(kDefaultLogConfig) {
+}
+
+CrashCollector::~CrashCollector() {
+ if (bus_)
+ bus_->ShutdownAndBlock();
+}
+
+void CrashCollector::Initialize(
+ CrashCollector::CountCrashFunction count_crash_function,
+ CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
+ CHECK(count_crash_function);
+ CHECK(is_feedback_allowed_function);
+
+ count_crash_function_ = count_crash_function;
+ is_feedback_allowed_function_ = is_feedback_allowed_function;
+
+ SetUpDBus();
+}
+
+void CrashCollector::SetUpDBus() {
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+
+ bus_ = new dbus::Bus(options);
+ CHECK(bus_->Connect());
+
+ session_manager_proxy_.reset(
+ new org::chromium::SessionManagerInterfaceProxy(
+ bus_,
+ login_manager::kSessionManagerServiceName));
+}
+
+int CrashCollector::WriteNewFile(const FilePath &filename,
+ const char *data,
+ int size) {
+ int fd = HANDLE_EINTR(open(filename.value().c_str(),
+ O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
+ if (fd < 0) {
+ return -1;
+ }
+
+ int rv = base::WriteFileDescriptor(fd, data, size) ? size : -1;
+ IGNORE_EINTR(close(fd));
+ return rv;
+}
+
+std::string CrashCollector::Sanitize(const std::string &name) {
+ // Make sure the sanitized name does not include any periods.
+ // The logic in crash_sender relies on this.
+ std::string result = name;
+ for (size_t i = 0; i < name.size(); ++i) {
+ if (!isalnum(result[i]) && result[i] != '_')
+ result[i] = '_';
+ }
+ return result;
+}
+
+std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
+ time_t timestamp,
+ pid_t pid) {
+ struct tm tm;
+ localtime_r(×tamp, &tm);
+ std::string sanitized_exec_name = Sanitize(exec_name);
+ return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
+ sanitized_exec_name.c_str(),
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ pid);
+}
+
+FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
+ const std::string &basename,
+ const std::string &extension) {
+ return crash_directory.Append(StringPrintf("%s.%s",
+ basename.c_str(),
+ extension.c_str()));
+}
+
+bool CrashCollector::GetActiveUserSessions(
+ std::map<std::string, std::string> *sessions) {
+ chromeos::ErrorPtr error;
+ session_manager_proxy_->RetrieveActiveSessions(sessions, &error);
+
+ if (error) {
+ LOG(ERROR) << "Error calling D-Bus proxy call to interface "
+ << "'" << session_manager_proxy_->GetObjectPath().value() << "':"
+ << error->GetMessage();
+ return false;
+ }
+
+ return true;
+}
+
+FilePath CrashCollector::GetUserCrashPath() {
+ // In this multiprofile world, there is no one-specific user dir anymore.
+ // Ask the session manager for the active ones, then just run with the
+ // first result we get back.
+ FilePath user_path = FilePath(kFallbackUserCrashPath);
+ std::map<std::string, std::string> active_sessions;
+ if (!GetActiveUserSessions(&active_sessions) || active_sessions.empty()) {
+ LOG(ERROR) << "Could not get active user sessions, using default.";
+ return user_path;
+ }
+
+ user_path = chromeos::cryptohome::home::GetHashedUserPath(
+ active_sessions.begin()->second).Append("crash");
+
+ return user_path;
+}
+
+FilePath CrashCollector::GetCrashDirectoryInfo(
+ uid_t process_euid,
+ uid_t default_user_id,
+ gid_t default_user_group,
+ mode_t *mode,
+ uid_t *directory_owner,
+ gid_t *directory_group) {
+ // TODO(mkrebs): This can go away once Chrome crashes are handled
+ // normally (see crosbug.com/5872).
+ // Check if the user crash directory should be used. If we are
+ // collecting chrome crashes during autotesting, we want to put them in
+ // the system crash directory so they are outside the cryptohome -- in
+ // case we are being run during logout (see crosbug.com/18637).
+ if (process_euid == default_user_id && IsUserSpecificDirectoryEnabled()) {
+ *mode = kUserCrashPathMode;
+ *directory_owner = default_user_id;
+ *directory_group = default_user_group;
+ return GetUserCrashPath();
+ } else {
+ *mode = kSystemCrashPathMode;
+ *directory_owner = kRootOwner;
+ *directory_group = kRootGroup;
+ return FilePath(kSystemCrashPath);
+ }
+}
+
+bool CrashCollector::GetUserInfoFromName(const std::string &name,
+ uid_t *uid,
+ gid_t *gid) {
+ char storage[256];
+ struct passwd passwd_storage;
+ struct passwd *passwd_result = nullptr;
+
+ if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
+ &passwd_result) != 0 || passwd_result == nullptr) {
+ LOG(ERROR) << "Cannot find user named " << name;
+ return false;
+ }
+
+ *uid = passwd_result->pw_uid;
+ *gid = passwd_result->pw_gid;
+ return true;
+}
+
+bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
+ FilePath *crash_directory,
+ bool *out_of_capacity) {
+ uid_t default_user_id;
+ gid_t default_user_group;
+
+ if (out_of_capacity) *out_of_capacity = false;
+
+ // For testing.
+ if (!forced_crash_directory_.empty()) {
+ *crash_directory = forced_crash_directory_;
+ return true;
+ }
+
+ if (!GetUserInfoFromName(kDefaultUserName,
+ &default_user_id,
+ &default_user_group)) {
+ LOG(ERROR) << "Could not find default user info";
+ return false;
+ }
+ mode_t directory_mode;
+ uid_t directory_owner;
+ gid_t directory_group;
+ *crash_directory =
+ GetCrashDirectoryInfo(euid,
+ default_user_id,
+ default_user_group,
+ &directory_mode,
+ &directory_owner,
+ &directory_group);
+
+ if (!base::PathExists(*crash_directory)) {
+ // Create the spool directory with the appropriate mode (regardless of
+ // umask) and ownership.
+ mode_t old_mask = umask(0);
+ if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
+ chown(crash_directory->value().c_str(),
+ directory_owner,
+ directory_group) < 0) {
+ LOG(ERROR) << "Unable to create appropriate crash directory";
+ return false;
+ }
+ umask(old_mask);
+ }
+
+ if (!base::PathExists(*crash_directory)) {
+ LOG(ERROR) << "Unable to create crash directory "
+ << crash_directory->value().c_str();
+ return false;
+ }
+
+ if (!CheckHasCapacity(*crash_directory)) {
+ if (out_of_capacity) *out_of_capacity = true;
+ return false;
+ }
+
+ return true;
+}
+
+FilePath CrashCollector::GetProcessPath(pid_t pid) {
+ return FilePath(StringPrintf("/proc/%d", pid));
+}
+
+bool CrashCollector::GetSymlinkTarget(const FilePath &symlink,
+ FilePath *target) {
+ ssize_t max_size = 64;
+ std::vector<char> buffer;
+
+ while (true) {
+ buffer.resize(max_size + 1);
+ ssize_t size = readlink(symlink.value().c_str(), buffer.data(), max_size);
+ if (size < 0) {
+ int saved_errno = errno;
+ LOG(ERROR) << "Readlink failed on " << symlink.value() << " with "
+ << saved_errno;
+ return false;
+ }
+
+ buffer[size] = 0;
+ if (size == max_size) {
+ max_size *= 2;
+ if (max_size > PATH_MAX) {
+ return false;
+ }
+ continue;
+ }
+ break;
+ }
+
+ *target = FilePath(buffer.data());
+ return true;
+}
+
+bool CrashCollector::GetExecutableBaseNameFromPid(pid_t pid,
+ std::string *base_name) {
+ FilePath target;
+ FilePath process_path = GetProcessPath(pid);
+ FilePath exe_path = process_path.Append("exe");
+ if (!GetSymlinkTarget(exe_path, &target)) {
+ LOG(INFO) << "GetSymlinkTarget failed - Path " << process_path.value()
+ << " DirectoryExists: "
+ << base::DirectoryExists(process_path);
+ // Try to further diagnose exe readlink failure cause.
+ struct stat buf;
+ int stat_result = stat(exe_path.value().c_str(), &buf);
+ int saved_errno = errno;
+ if (stat_result < 0) {
+ LOG(INFO) << "stat " << exe_path.value() << " failed: " << stat_result
+ << " " << saved_errno;
+ } else {
+ LOG(INFO) << "stat " << exe_path.value() << " succeeded: st_mode="
+ << buf.st_mode;
+ }
+ return false;
+ }
+ *base_name = target.BaseName().value();
+ return true;
+}
+
+// Return true if the given crash directory has not already reached
+// maximum capacity.
+bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
+ DIR* dir = opendir(crash_directory.value().c_str());
+ if (!dir) {
+ return false;
+ }
+ struct dirent ent_buf;
+ struct dirent* ent;
+ bool full = false;
+ std::set<std::string> basenames;
+ while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) {
+ if ((strcmp(ent->d_name, ".") == 0) ||
+ (strcmp(ent->d_name, "..") == 0))
+ continue;
+
+ std::string filename(ent->d_name);
+ size_t last_dot = filename.rfind(".");
+ std::string basename;
+ // If there is a valid looking extension, use the base part of the
+ // name. If the only dot is the first byte (aka a dot file), treat
+ // it as unique to avoid allowing a directory full of dot files
+ // from accumulating.
+ if (last_dot != std::string::npos && last_dot != 0)
+ basename = filename.substr(0, last_dot);
+ else
+ basename = filename;
+ basenames.insert(basename);
+
+ if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
+ LOG(WARNING) << "Crash directory " << crash_directory.value()
+ << " already full with " << kMaxCrashDirectorySize
+ << " pending reports";
+ full = true;
+ break;
+ }
+ }
+ closedir(dir);
+ return !full;
+}
+
+bool CrashCollector::GetLogContents(const FilePath &config_path,
+ const std::string &exec_name,
+ const FilePath &output_file) {
+ chromeos::KeyValueStore store;
+ if (!store.Load(config_path)) {
+ LOG(INFO) << "Unable to read log configuration file "
+ << config_path.value();
+ return false;
+ }
+
+ std::string command;
+ if (!store.GetString(exec_name, &command))
+ return false;
+
+ chromeos::ProcessImpl diag_process;
+ diag_process.AddArg(kShellPath);
+ diag_process.AddStringOption("-c", command);
+ diag_process.RedirectOutput(output_file.value());
+
+ const int result = diag_process.Run();
+ if (result != 0) {
+ LOG(INFO) << "Log command \"" << command << "\" exited with " << result;
+ return false;
+ }
+ return true;
+}
+
+void CrashCollector::AddCrashMetaData(const std::string &key,
+ const std::string &value) {
+ extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
+}
+
+void CrashCollector::AddCrashMetaUploadFile(const std::string &key,
+ const std::string &path) {
+ if (!path.empty())
+ AddCrashMetaData(kUploadFilePrefix + key, path);
+}
+
+void CrashCollector::AddCrashMetaUploadData(const std::string &key,
+ const std::string &value) {
+ if (!value.empty())
+ AddCrashMetaData(kUploadVarPrefix + key, value);
+}
+
+void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
+ const std::string &exec_name,
+ const std::string &payload_path) {
+ chromeos::KeyValueStore store;
+ if (!store.Load(FilePath(lsb_release_))) {
+ LOG(ERROR) << "Problem parsing " << lsb_release_;
+ // Even though there was some failure, take as much as we could read.
+ }
+
+ std::string version("unknown");
+ if (!store.GetString(kLsbVersionKey, &version)) {
+ LOG(ERROR) << "Unable to read " << kLsbVersionKey << " from "
+ << lsb_release_;
+ }
+ int64_t payload_size = -1;
+ base::GetFileSize(FilePath(payload_path), &payload_size);
+ std::string meta_data = StringPrintf("%sexec_name=%s\n"
+ "ver=%s\n"
+ "payload=%s\n"
+ "payload_size=%" PRId64 "\n"
+ "done=1\n",
+ extra_metadata_.c_str(),
+ exec_name.c_str(),
+ version.c_str(),
+ payload_path.c_str(),
+ payload_size);
+ // We must use WriteNewFile instead of base::WriteFile as we
+ // do not want to write with root access to a symlink that an attacker
+ // might have created.
+ if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
+ LOG(ERROR) << "Unable to write " << meta_path.value();
+ }
+}
+
+bool CrashCollector::IsCrashTestInProgress() {
+ return base::PathExists(FilePath(kCrashTestInProgressPath));
+}
+
+bool CrashCollector::IsDeveloperImage() {
+ // If we're testing crash reporter itself, we don't want to special-case
+ // for developer images.
+ if (IsCrashTestInProgress())
+ return false;
+ return base::PathExists(FilePath(kLeaveCoreFile));
+}
+
+bool CrashCollector::ShouldHandleChromeCrashes() {
+ // If we're testing crash reporter itself, we don't want to allow an
+ // override for chrome crashes. And, let's be conservative and only
+ // allow an override for developer images.
+ if (!IsCrashTestInProgress() && IsDeveloperImage()) {
+ // Check if there's an override to indicate we should indeed collect
+ // chrome crashes. This allows the crashes to still be tracked when
+ // they occur in autotests. See "crosbug.com/17987".
+ if (base::PathExists(FilePath(kCollectChromeFile)))
+ return true;
+ }
+ // We default to ignoring chrome crashes.
+ return false;
+}
+
+bool CrashCollector::IsUserSpecificDirectoryEnabled() {
+ return !ShouldHandleChromeCrashes();
+}
diff --git a/crash_reporter/crash_collector.h b/crash_reporter/crash_collector.h
new file mode 100644
index 0000000..ef443d3
--- /dev/null
+++ b/crash_reporter/crash_collector.h
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_CRASH_COLLECTOR_H_
+#define CRASH_REPORTER_CRASH_COLLECTOR_H_
+
+#include <sys/stat.h>
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "session_manager/dbus-proxies.h"
+
+// User crash collector.
+class CrashCollector {
+ public:
+ typedef void (*CountCrashFunction)();
+ typedef bool (*IsFeedbackAllowedFunction)();
+
+ CrashCollector();
+
+ virtual ~CrashCollector();
+
+ // Initialize the crash collector for detection of crashes, given a
+ // crash counting function, and metrics collection enabled oracle.
+ void Initialize(CountCrashFunction count_crash,
+ IsFeedbackAllowedFunction is_metrics_allowed);
+
+ protected:
+ friend class CrashCollectorTest;
+ FRIEND_TEST(ChromeCollectorTest, HandleCrash);
+ FRIEND_TEST(CrashCollectorTest, CheckHasCapacityCorrectBasename);
+ FRIEND_TEST(CrashCollectorTest, CheckHasCapacityStrangeNames);
+ FRIEND_TEST(CrashCollectorTest, CheckHasCapacityUsual);
+ FRIEND_TEST(CrashCollectorTest, GetCrashDirectoryInfo);
+ FRIEND_TEST(CrashCollectorTest, GetCrashPath);
+ FRIEND_TEST(CrashCollectorTest, GetLogContents);
+ FRIEND_TEST(CrashCollectorTest, ForkExecAndPipe);
+ FRIEND_TEST(CrashCollectorTest, FormatDumpBasename);
+ FRIEND_TEST(CrashCollectorTest, Initialize);
+ FRIEND_TEST(CrashCollectorTest, IsUserSpecificDirectoryEnabled);
+ FRIEND_TEST(CrashCollectorTest, MetaData);
+ FRIEND_TEST(CrashCollectorTest, Sanitize);
+ FRIEND_TEST(CrashCollectorTest, WriteNewFile);
+ FRIEND_TEST(ForkExecAndPipeTest, Basic);
+ FRIEND_TEST(ForkExecAndPipeTest, NonZeroReturnValue);
+ FRIEND_TEST(ForkExecAndPipeTest, BadOutputFile);
+ FRIEND_TEST(ForkExecAndPipeTest, ExistingOutputFile);
+ FRIEND_TEST(ForkExecAndPipeTest, BadExecutable);
+ FRIEND_TEST(ForkExecAndPipeTest, StderrCaptured);
+ FRIEND_TEST(ForkExecAndPipeTest, NULLParam);
+ FRIEND_TEST(ForkExecAndPipeTest, NoParams);
+ FRIEND_TEST(ForkExecAndPipeTest, SegFaultHandling);
+
+ // Set maximum enqueued crashes in a crash directory.
+ static const int kMaxCrashDirectorySize;
+
+ // Set up D-Bus.
+ virtual void SetUpDBus();
+
+ // Writes |data| of |size| to |filename|, which must be a new file.
+ // If the file already exists or writing fails, return a negative value.
+ // Otherwise returns the number of bytes written.
+ int WriteNewFile(const base::FilePath &filename, const char *data, int size);
+
+ // Return a filename that has only [a-z0-1_] characters by mapping
+ // all others into '_'.
+ std::string Sanitize(const std::string &name);
+
+ // For testing, set the directory always returned by
+ // GetCreatedCrashDirectoryByEuid.
+ void ForceCrashDirectory(const base::FilePath &forced_directory) {
+ forced_crash_directory_ = forced_directory;
+ }
+
+ virtual bool GetActiveUserSessions(
+ std::map<std::string, std::string> *sessions);
+ base::FilePath GetUserCrashPath();
+ base::FilePath GetCrashDirectoryInfo(uid_t process_euid,
+ uid_t default_user_id,
+ gid_t default_user_group,
+ mode_t *mode,
+ uid_t *directory_owner,
+ gid_t *directory_group);
+ bool GetUserInfoFromName(const std::string &name,
+ uid_t *uid,
+ gid_t *gid);
+
+ // Determines the crash directory for given euid, and creates the
+ // directory if necessary with appropriate permissions. If
+ // |out_of_capacity| is not nullptr, it is set to indicate if the call
+ // failed due to not having capacity in the crash directory. Returns
+ // true whether or not directory needed to be created, false on any
+ // failure. If the crash directory is at capacity, returns false.
+ bool GetCreatedCrashDirectoryByEuid(uid_t euid,
+ base::FilePath *crash_file_path,
+ bool *out_of_capacity);
+
+ // Format crash name based on components.
+ std::string FormatDumpBasename(const std::string &exec_name,
+ time_t timestamp,
+ pid_t pid);
+
+ // Create a file path to a file in |crash_directory| with the given
+ // |basename| and |extension|.
+ base::FilePath GetCrashPath(const base::FilePath &crash_directory,
+ const std::string &basename,
+ const std::string &extension);
+
+ base::FilePath GetProcessPath(pid_t pid);
+ bool GetSymlinkTarget(const base::FilePath &symlink,
+ base::FilePath *target);
+ bool GetExecutableBaseNameFromPid(pid_t pid,
+ std::string *base_name);
+
+ // Check given crash directory still has remaining capacity for another
+ // crash.
+ bool CheckHasCapacity(const base::FilePath &crash_directory);
+
+ // Write a log applicable to |exec_name| to |output_file| based on the
+ // log configuration file at |config_path|.
+ bool GetLogContents(const base::FilePath &config_path,
+ const std::string &exec_name,
+ const base::FilePath &output_file);
+
+ // Add non-standard meta data to the crash metadata file. Call
+ // before calling WriteCrashMetaData. Key must not contain "=" or
+ // "\n" characters. Value must not contain "\n" characters.
+ void AddCrashMetaData(const std::string &key, const std::string &value);
+
+ // Add a file to be uploaded to the crash reporter server. The file must
+ // persist until the crash report is sent; ideally it should live in the same
+ // place as the .meta file, so it can be cleaned up automatically.
+ void AddCrashMetaUploadFile(const std::string &key, const std::string &path);
+
+ // Add non-standard meta data to the crash metadata file.
+ // Data added though this call will be uploaded to the crash reporter server,
+ // appearing as a form field.
+ void AddCrashMetaUploadData(const std::string &key, const std::string &value);
+
+ // Write a file of metadata about crash.
+ void WriteCrashMetaData(const base::FilePath &meta_path,
+ const std::string &exec_name,
+ const std::string &payload_path);
+
+ // Returns true if the a crash test is currently running.
+ bool IsCrashTestInProgress();
+ // Returns true if we should consider ourselves to be running on a
+ // developer image.
+ bool IsDeveloperImage();
+ // Returns true if chrome crashes should be handled.
+ bool ShouldHandleChromeCrashes();
+ // Returns true if user crash directory may be used.
+ bool IsUserSpecificDirectoryEnabled();
+
+ CountCrashFunction count_crash_function_;
+ IsFeedbackAllowedFunction is_feedback_allowed_function_;
+ std::string extra_metadata_;
+ base::FilePath forced_crash_directory_;
+ std::string lsb_release_;
+ base::FilePath log_config_path_;
+
+ scoped_refptr<dbus::Bus> bus_;
+
+ private:
+ // D-Bus proxy for session manager interface.
+ std::unique_ptr<org::chromium::SessionManagerInterfaceProxy>
+ session_manager_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrashCollector);
+};
+
+#endif // CRASH_REPORTER_CRASH_COLLECTOR_H_
diff --git a/crash_reporter/crash_collector_test.cc b/crash_reporter/crash_collector_test.cc
new file mode 100644
index 0000000..ce9af2b
--- /dev/null
+++ b/crash_reporter/crash_collector_test.cc
@@ -0,0 +1,299 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/crash_collector_test.h"
+
+#include <unistd.h>
+#include <utility>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/syslog_logging.h>
+#include <gtest/gtest.h>
+
+#include "crash-reporter/crash_collector.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using chromeos::FindLog;
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace {
+
+void CountCrash() {
+ ADD_FAILURE();
+}
+
+bool IsMetrics() {
+ ADD_FAILURE();
+ return false;
+}
+
+bool GetActiveUserSessionsImpl(std::map<std::string, std::string> *sessions) {
+ char kUser[] = "chicken@butt.com";
+ char kHash[] = "hashcakes";
+ sessions->insert(std::pair<std::string, std::string>(kUser, kHash));
+ return true;
+}
+
+} // namespace
+
+class CrashCollectorTest : public ::testing::Test {
+ public:
+ void SetUp() {
+ EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(Return());
+
+ collector_.Initialize(CountCrash, IsMetrics);
+ test_dir_ = FilePath("test");
+ base::CreateDirectory(test_dir_);
+ chromeos::ClearLog();
+ }
+
+ void TearDown() {
+ base::DeleteFile(test_dir_, true);
+ }
+
+ bool CheckHasCapacity();
+
+ protected:
+ CrashCollectorMock collector_;
+ FilePath test_dir_;
+};
+
+TEST_F(CrashCollectorTest, Initialize) {
+ ASSERT_TRUE(CountCrash == collector_.count_crash_function_);
+ ASSERT_TRUE(IsMetrics == collector_.is_feedback_allowed_function_);
+}
+
+TEST_F(CrashCollectorTest, WriteNewFile) {
+ FilePath test_file = test_dir_.Append("test_new");
+ const char kBuffer[] = "buffer";
+ EXPECT_EQ(strlen(kBuffer),
+ collector_.WriteNewFile(test_file,
+ kBuffer,
+ strlen(kBuffer)));
+ EXPECT_LT(collector_.WriteNewFile(test_file,
+ kBuffer,
+ strlen(kBuffer)), 0);
+}
+
+TEST_F(CrashCollectorTest, Sanitize) {
+ EXPECT_EQ("chrome", collector_.Sanitize("chrome"));
+ EXPECT_EQ("CHROME", collector_.Sanitize("CHROME"));
+ EXPECT_EQ("1chrome2", collector_.Sanitize("1chrome2"));
+ EXPECT_EQ("chrome__deleted_", collector_.Sanitize("chrome (deleted)"));
+ EXPECT_EQ("foo_bar", collector_.Sanitize("foo.bar"));
+ EXPECT_EQ("", collector_.Sanitize(""));
+ EXPECT_EQ("_", collector_.Sanitize(" "));
+}
+
+TEST_F(CrashCollectorTest, GetCrashDirectoryInfo) {
+ FilePath path;
+ const int kRootUid = 0;
+ const int kRootGid = 0;
+ const int kNtpUid = 5;
+ const int kChronosUid = 1000;
+ const int kChronosGid = 1001;
+ const mode_t kExpectedSystemMode = 01755;
+ const mode_t kExpectedUserMode = 0755;
+
+ mode_t directory_mode;
+ uid_t directory_owner;
+ gid_t directory_group;
+
+ path = collector_.GetCrashDirectoryInfo(kRootUid,
+ kChronosUid,
+ kChronosGid,
+ &directory_mode,
+ &directory_owner,
+ &directory_group);
+ EXPECT_EQ("/var/spool/crash", path.value());
+ EXPECT_EQ(kExpectedSystemMode, directory_mode);
+ EXPECT_EQ(kRootUid, directory_owner);
+ EXPECT_EQ(kRootGid, directory_group);
+
+ path = collector_.GetCrashDirectoryInfo(kNtpUid,
+ kChronosUid,
+ kChronosGid,
+ &directory_mode,
+ &directory_owner,
+ &directory_group);
+ EXPECT_EQ("/var/spool/crash", path.value());
+ EXPECT_EQ(kExpectedSystemMode, directory_mode);
+ EXPECT_EQ(kRootUid, directory_owner);
+ EXPECT_EQ(kRootGid, directory_group);
+
+ EXPECT_CALL(collector_, GetActiveUserSessions(testing::_))
+ .WillOnce(Invoke(&GetActiveUserSessionsImpl));
+
+ EXPECT_EQ(collector_.IsUserSpecificDirectoryEnabled(), true);
+
+ path = collector_.GetCrashDirectoryInfo(kChronosUid,
+ kChronosUid,
+ kChronosGid,
+ &directory_mode,
+ &directory_owner,
+ &directory_group);
+ EXPECT_EQ("/home/user/hashcakes/crash", path.value());
+ EXPECT_EQ(kExpectedUserMode, directory_mode);
+ EXPECT_EQ(kChronosUid, directory_owner);
+ EXPECT_EQ(kChronosGid, directory_group);
+}
+
+TEST_F(CrashCollectorTest, FormatDumpBasename) {
+ struct tm tm = {0};
+ tm.tm_sec = 15;
+ tm.tm_min = 50;
+ tm.tm_hour = 13;
+ tm.tm_mday = 23;
+ tm.tm_mon = 4;
+ tm.tm_year = 110;
+ tm.tm_isdst = -1;
+ std::string basename =
+ collector_.FormatDumpBasename("foo", mktime(&tm), 100);
+ ASSERT_EQ("foo.20100523.135015.100", basename);
+}
+
+TEST_F(CrashCollectorTest, GetCrashPath) {
+ EXPECT_EQ("/var/spool/crash/myprog.20100101.1200.1234.core",
+ collector_.GetCrashPath(FilePath("/var/spool/crash"),
+ "myprog.20100101.1200.1234",
+ "core").value());
+ EXPECT_EQ("/home/chronos/user/crash/chrome.20100101.1200.1234.dmp",
+ collector_.GetCrashPath(FilePath("/home/chronos/user/crash"),
+ "chrome.20100101.1200.1234",
+ "dmp").value());
+}
+
+
+bool CrashCollectorTest::CheckHasCapacity() {
+ static const char kFullMessage[] = "Crash directory test already full";
+ bool has_capacity = collector_.CheckHasCapacity(test_dir_);
+ bool has_message = FindLog(kFullMessage);
+ EXPECT_EQ(has_message, !has_capacity);
+ return has_capacity;
+}
+
+TEST_F(CrashCollectorTest, CheckHasCapacityUsual) {
+ // Test kMaxCrashDirectorySize - 1 non-meta files can be added.
+ for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
+ base::WriteFile(test_dir_.Append(StringPrintf("file%d.core", i)), "", 0);
+ EXPECT_TRUE(CheckHasCapacity());
+ }
+
+ // Test an additional kMaxCrashDirectorySize - 1 meta files fit.
+ for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
+ base::WriteFile(test_dir_.Append(StringPrintf("file%d.meta", i)), "", 0);
+ EXPECT_TRUE(CheckHasCapacity());
+ }
+
+ // Test an additional kMaxCrashDirectorySize meta files don't fit.
+ for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize; ++i) {
+ base::WriteFile(test_dir_.Append(StringPrintf("overage%d.meta", i)), "", 0);
+ EXPECT_FALSE(CheckHasCapacity());
+ }
+}
+
+TEST_F(CrashCollectorTest, CheckHasCapacityCorrectBasename) {
+ // Test kMaxCrashDirectorySize - 1 files can be added.
+ for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
+ base::WriteFile(test_dir_.Append(StringPrintf("file.%d.core", i)), "", 0);
+ EXPECT_TRUE(CheckHasCapacity());
+ }
+ base::WriteFile(test_dir_.Append("file.last.core"), "", 0);
+ EXPECT_FALSE(CheckHasCapacity());
+}
+
+TEST_F(CrashCollectorTest, CheckHasCapacityStrangeNames) {
+ // Test many files with different extensions and same base fit.
+ for (int i = 0; i < 5 * CrashCollector::kMaxCrashDirectorySize; ++i) {
+ base::WriteFile(test_dir_.Append(StringPrintf("a.%d", i)), "", 0);
+ EXPECT_TRUE(CheckHasCapacity());
+ }
+ // Test dot files are treated as individual files.
+ for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 2; ++i) {
+ base::WriteFile(test_dir_.Append(StringPrintf(".file%d", i)), "", 0);
+ EXPECT_TRUE(CheckHasCapacity());
+ }
+ base::WriteFile(test_dir_.Append("normal.meta"), "", 0);
+ EXPECT_FALSE(CheckHasCapacity());
+}
+
+TEST_F(CrashCollectorTest, MetaData) {
+ const char kMetaFileBasename[] = "generated.meta";
+ FilePath meta_file = test_dir_.Append(kMetaFileBasename);
+ FilePath lsb_release = test_dir_.Append("lsb-release");
+ FilePath payload_file = test_dir_.Append("payload-file");
+ std::string contents;
+ collector_.lsb_release_ = lsb_release.value();
+ const char kLsbContents[] =
+ "CHROMEOS_RELEASE_BOARD=lumpy\n"
+ "CHROMEOS_RELEASE_VERSION=6727.0.2015_01_26_0853\n"
+ "CHROMEOS_RELEASE_NAME=Chromium OS\n";
+ ASSERT_TRUE(base::WriteFile(lsb_release, kLsbContents, strlen(kLsbContents)));
+ const char kPayload[] = "foo";
+ ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
+ collector_.AddCrashMetaData("foo", "bar");
+ collector_.WriteCrashMetaData(meta_file, "kernel", payload_file.value());
+ EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
+ const char kExpectedMeta[] =
+ "foo=bar\n"
+ "exec_name=kernel\n"
+ "ver=6727.0.2015_01_26_0853\n"
+ "payload=test/payload-file\n"
+ "payload_size=3\n"
+ "done=1\n";
+ EXPECT_EQ(kExpectedMeta, contents);
+
+ // Test target of symlink is not overwritten.
+ payload_file = test_dir_.Append("payload2-file");
+ ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
+ FilePath meta_symlink_path = test_dir_.Append("symlink.meta");
+ ASSERT_EQ(0,
+ symlink(kMetaFileBasename,
+ meta_symlink_path.value().c_str()));
+ ASSERT_TRUE(base::PathExists(meta_symlink_path));
+ chromeos::ClearLog();
+ collector_.WriteCrashMetaData(meta_symlink_path,
+ "kernel",
+ payload_file.value());
+ // Target metadata contents should have stayed the same.
+ contents.clear();
+ EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
+ EXPECT_EQ(kExpectedMeta, contents);
+ EXPECT_TRUE(FindLog("Unable to write"));
+
+ // Test target of dangling symlink is not created.
+ base::DeleteFile(meta_file, false);
+ ASSERT_FALSE(base::PathExists(meta_file));
+ chromeos::ClearLog();
+ collector_.WriteCrashMetaData(meta_symlink_path, "kernel",
+ payload_file.value());
+ EXPECT_FALSE(base::PathExists(meta_file));
+ EXPECT_TRUE(FindLog("Unable to write"));
+}
+
+TEST_F(CrashCollectorTest, GetLogContents) {
+ FilePath config_file = test_dir_.Append("crash_config");
+ FilePath output_file = test_dir_.Append("crash_log");
+ const char kConfigContents[] =
+ "foobar=echo hello there | \\\n sed -e \"s/there/world/\"";
+ ASSERT_TRUE(
+ base::WriteFile(config_file, kConfigContents, strlen(kConfigContents)));
+ base::DeleteFile(FilePath(output_file), false);
+ EXPECT_FALSE(collector_.GetLogContents(config_file,
+ "barfoo",
+ output_file));
+ EXPECT_FALSE(base::PathExists(output_file));
+ base::DeleteFile(FilePath(output_file), false);
+ EXPECT_TRUE(collector_.GetLogContents(config_file,
+ "foobar",
+ output_file));
+ ASSERT_TRUE(base::PathExists(output_file));
+ std::string contents;
+ EXPECT_TRUE(base::ReadFileToString(output_file, &contents));
+ EXPECT_EQ("hello world\n", contents);
+}
diff --git a/crash_reporter/crash_collector_test.h b/crash_reporter/crash_collector_test.h
new file mode 100644
index 0000000..8339fa0
--- /dev/null
+++ b/crash_reporter/crash_collector_test.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
+#define CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
+
+#include "crash-reporter/crash_collector.h"
+
+#include <map>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+class CrashCollectorMock : public CrashCollector {
+ public:
+ MOCK_METHOD0(SetUpDBus, void());
+ MOCK_METHOD1(GetActiveUserSessions,
+ bool(std::map<std::string, std::string> *sessions));
+};
+
+#endif // CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
diff --git a/crash_reporter/crash_reporter.cc b/crash_reporter/crash_reporter.cc
new file mode 100644
index 0000000..1528b3f
--- /dev/null
+++ b/crash_reporter/crash_reporter.cc
@@ -0,0 +1,334 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fcntl.h> // for open
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/flag_helper.h>
+#include <chromeos/syslog_logging.h>
+#include <metrics/metrics_library.h>
+
+#include "crash-reporter/chrome_collector.h"
+#include "crash-reporter/kernel_collector.h"
+#include "crash-reporter/kernel_warning_collector.h"
+#include "crash-reporter/udev_collector.h"
+#include "crash-reporter/unclean_shutdown_collector.h"
+#include "crash-reporter/user_collector.h"
+
+static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
+static const char kUserCrashSignal[] =
+ "org.chromium.CrashReporter.UserCrash";
+static const char kKernelCrashDetected[] = "/var/run/kernel-crash-detected";
+static const char kUncleanShutdownDetected[] =
+ "/var/run/unclean-shutdown-detected";
+
+// Enumeration of kinds of crashes to be used in the CrashCounter histogram.
+enum CrashKinds {
+ kCrashKindUncleanShutdown = 1,
+ kCrashKindUser = 2,
+ kCrashKindKernel = 3,
+ kCrashKindUdev = 4,
+ kCrashKindKernelWarning = 5,
+ kCrashKindMax
+};
+
+static MetricsLibrary s_metrics_lib;
+
+using base::FilePath;
+using base::StringPrintf;
+
+static bool IsFeedbackAllowed() {
+ return s_metrics_lib.AreMetricsEnabled();
+}
+
+static bool TouchFile(const FilePath &file_path) {
+ return base::WriteFile(file_path, "", 0) == 0;
+}
+
+static void SendCrashMetrics(CrashKinds type, const char* name) {
+ // TODO(kmixter): We can remove this histogram as part of
+ // crosbug.com/11163.
+ s_metrics_lib.SendEnumToUMA(kCrashCounterHistogram, type, kCrashKindMax);
+ s_metrics_lib.SendCrashToUMA(name);
+}
+
+static void CountKernelCrash() {
+ SendCrashMetrics(kCrashKindKernel, "kernel");
+}
+
+static void CountUdevCrash() {
+ SendCrashMetrics(kCrashKindUdev, "udevcrash");
+}
+
+static void CountUncleanShutdown() {
+ SendCrashMetrics(kCrashKindUncleanShutdown, "uncleanshutdown");
+}
+
+static void CountUserCrash() {
+ SendCrashMetrics(kCrashKindUser, "user");
+ std::string command = StringPrintf(
+ "/usr/bin/dbus-send --type=signal --system / \"%s\" &",
+ kUserCrashSignal);
+ // Announce through D-Bus whenever a user crash happens. This is
+ // used by the metrics daemon to log active use time between
+ // crashes.
+ //
+ // This could be done more efficiently by explicit fork/exec or
+ // using a dbus library directly. However, this should run
+ // relatively rarely and longer term we may need to implement a
+ // better way to do this that doesn't rely on D-Bus.
+ //
+ // We run in the background in case dbus daemon itself is crashed
+ // and not responding. This allows us to not block and potentially
+ // deadlock on a dbus-daemon crash. If dbus-daemon crashes without
+ // restarting, each crash will fork off a lot of dbus-send
+ // processes. Such a system is in a unusable state and will need
+ // to be restarted anyway.
+
+ int status = system(command.c_str());
+ LOG_IF(WARNING, status != 0) << "dbus-send running failed";
+}
+
+static void CountChromeCrash() {
+ // For now, consider chrome crashes the same as user crashes for reporting
+ // purposes.
+ CountUserCrash();
+}
+
+
+static int Initialize(KernelCollector *kernel_collector,
+ UserCollector *user_collector,
+ UncleanShutdownCollector *unclean_shutdown_collector,
+ const bool unclean_check,
+ const bool clean_shutdown) {
+ CHECK(!clean_shutdown) << "Incompatible options";
+
+ bool was_kernel_crash = false;
+ bool was_unclean_shutdown = false;
+ kernel_collector->Enable();
+ if (kernel_collector->is_enabled()) {
+ was_kernel_crash = kernel_collector->Collect();
+ }
+
+ if (unclean_check) {
+ was_unclean_shutdown = unclean_shutdown_collector->Collect();
+ }
+
+ // Touch a file to notify the metrics daemon that a kernel
+ // crash has been detected so that it can log the time since
+ // the last kernel crash.
+ if (IsFeedbackAllowed()) {
+ if (was_kernel_crash) {
+ TouchFile(FilePath(kKernelCrashDetected));
+ } else if (was_unclean_shutdown) {
+ // We only count an unclean shutdown if it did not come with
+ // an associated kernel crash.
+ TouchFile(FilePath(kUncleanShutdownDetected));
+ }
+ }
+
+ // Must enable the unclean shutdown collector *after* collecting.
+ unclean_shutdown_collector->Enable();
+ user_collector->Enable();
+
+ return 0;
+}
+
+static int HandleUserCrash(UserCollector *user_collector,
+ const std::string& user, const bool crash_test) {
+ // Handle a specific user space crash.
+ CHECK(!user.empty()) << "--user= must be set";
+
+ // Make it possible to test what happens when we crash while
+ // handling a crash.
+ if (crash_test) {
+ *(volatile char *)0 = 0;
+ return 0;
+ }
+
+ // Accumulate logs to help in diagnosing failures during user collection.
+ chromeos::LogToString(true);
+ // Handle the crash, get the name of the process from procfs.
+ bool handled = user_collector->HandleCrash(user, nullptr);
+ chromeos::LogToString(false);
+ if (!handled)
+ return 1;
+ return 0;
+}
+
+static int HandleChromeCrash(ChromeCollector *chrome_collector,
+ const std::string& chrome_dump_file,
+ const std::string& pid,
+ const std::string& uid,
+ const std::string& exe) {
+ CHECK(!chrome_dump_file.empty()) << "--chrome= must be set";
+ CHECK(!pid.empty()) << "--pid= must be set";
+ CHECK(!uid.empty()) << "--uid= must be set";
+ CHECK(!exe.empty()) << "--exe= must be set";
+
+ chromeos::LogToString(true);
+ bool handled = chrome_collector->HandleCrash(FilePath(chrome_dump_file),
+ pid, uid, exe);
+ chromeos::LogToString(false);
+ if (!handled)
+ return 1;
+ return 0;
+}
+
+static int HandleUdevCrash(UdevCollector *udev_collector,
+ const std::string& udev_event) {
+ // Handle a crash indicated by a udev event.
+ CHECK(!udev_event.empty()) << "--udev= must be set";
+
+ // Accumulate logs to help in diagnosing failures during user collection.
+ chromeos::LogToString(true);
+ bool handled = udev_collector->HandleCrash(udev_event);
+ chromeos::LogToString(false);
+ if (!handled)
+ return 1;
+ return 0;
+}
+
+static int HandleKernelWarning(KernelWarningCollector
+ *kernel_warning_collector) {
+ // Accumulate logs to help in diagnosing failures during collection.
+ chromeos::LogToString(true);
+ bool handled = kernel_warning_collector->Collect();
+ chromeos::LogToString(false);
+ if (!handled)
+ return 1;
+ return 0;
+}
+
+// Interactive/diagnostics mode for generating kernel crash signatures.
+static int GenerateKernelSignature(KernelCollector *kernel_collector,
+ const std::string& kernel_signature_file) {
+ std::string kcrash_contents;
+ std::string signature;
+ if (!base::ReadFileToString(FilePath(kernel_signature_file),
+ &kcrash_contents)) {
+ fprintf(stderr, "Could not read file.\n");
+ return 1;
+ }
+ if (!kernel_collector->ComputeKernelStackSignature(
+ kcrash_contents,
+ &signature,
+ true)) {
+ fprintf(stderr, "Signature could not be generated.\n");
+ return 1;
+ }
+ printf("Kernel crash signature is \"%s\".\n", signature.c_str());
+ return 0;
+}
+
+// Ensure stdout, stdin, and stderr are open file descriptors. If
+// they are not, any code which writes to stderr/stdout may write out
+// to files opened during execution. In particular, when
+// crash_reporter is run by the kernel coredump pipe handler (via
+// kthread_create/kernel_execve), it will not have file table entries
+// 1 and 2 (stdout and stderr) populated. We populate them here.
+static void OpenStandardFileDescriptors() {
+ int new_fd = -1;
+ // We open /dev/null to fill in any of the standard [0, 2] file
+ // descriptors. We leave these open for the duration of the
+ // process. This works because open returns the lowest numbered
+ // invalid fd.
+ do {
+ new_fd = open("/dev/null", 0);
+ CHECK_GE(new_fd, 0) << "Unable to open /dev/null";
+ } while (new_fd >= 0 && new_fd <= 2);
+ close(new_fd);
+}
+
+int main(int argc, char *argv[]) {
+ DEFINE_bool(init, false, "Initialize crash logging");
+ DEFINE_bool(clean_shutdown, false, "Signal clean shutdown");
+ DEFINE_string(generate_kernel_signature, "",
+ "Generate signature from given kcrash file");
+ DEFINE_bool(crash_test, false, "Crash test");
+ DEFINE_string(user, "", "User crash info (pid:signal:exec_name)");
+ DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
+ DEFINE_string(udev, "", "Udev event description (type:device:subsystem)");
+ DEFINE_bool(kernel_warning, false, "Report collected kernel warning");
+ DEFINE_string(chrome, "", "Chrome crash dump file");
+ DEFINE_string(pid, "", "PID of crashing process");
+ DEFINE_string(uid, "", "UID of crashing process");
+ DEFINE_string(exe, "", "Executable name of crashing process");
+ DEFINE_bool(core2md_failure, false, "Core2md failure test");
+ DEFINE_bool(directory_failure, false, "Spool directory failure test");
+ DEFINE_string(filter_in, "",
+ "Ignore all crashes but this for testing");
+
+ OpenStandardFileDescriptors();
+ FilePath my_path = base::MakeAbsoluteFilePath(FilePath(argv[0]));
+ s_metrics_lib.Init();
+ chromeos::FlagHelper::Init(argc, argv, "Chromium OS Crash Reporter");
+ chromeos::OpenLog(my_path.BaseName().value().c_str(), true);
+ chromeos::InitLog(chromeos::kLogToSyslog);
+
+ KernelCollector kernel_collector;
+ kernel_collector.Initialize(CountKernelCrash, IsFeedbackAllowed);
+ UserCollector user_collector;
+ user_collector.Initialize(CountUserCrash,
+ my_path.value(),
+ IsFeedbackAllowed,
+ true, // generate_diagnostics
+ FLAGS_core2md_failure,
+ FLAGS_directory_failure,
+ FLAGS_filter_in);
+ UncleanShutdownCollector unclean_shutdown_collector;
+ unclean_shutdown_collector.Initialize(CountUncleanShutdown,
+ IsFeedbackAllowed);
+ UdevCollector udev_collector;
+ udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
+ ChromeCollector chrome_collector;
+ chrome_collector.Initialize(CountChromeCrash, IsFeedbackAllowed);
+
+ KernelWarningCollector kernel_warning_collector;
+ kernel_warning_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
+
+ if (FLAGS_init) {
+ return Initialize(&kernel_collector,
+ &user_collector,
+ &unclean_shutdown_collector,
+ FLAGS_unclean_check,
+ FLAGS_clean_shutdown);
+ }
+
+ if (FLAGS_clean_shutdown) {
+ unclean_shutdown_collector.Disable();
+ user_collector.Disable();
+ return 0;
+ }
+
+ if (!FLAGS_generate_kernel_signature.empty()) {
+ return GenerateKernelSignature(&kernel_collector,
+ FLAGS_generate_kernel_signature);
+ }
+
+ if (!FLAGS_udev.empty()) {
+ return HandleUdevCrash(&udev_collector, FLAGS_udev);
+ }
+
+ if (FLAGS_kernel_warning) {
+ return HandleKernelWarning(&kernel_warning_collector);
+ }
+
+ if (!FLAGS_chrome.empty()) {
+ return HandleChromeCrash(&chrome_collector,
+ FLAGS_chrome,
+ FLAGS_pid,
+ FLAGS_uid,
+ FLAGS_exe);
+ }
+
+ return HandleUserCrash(&user_collector, FLAGS_user, FLAGS_crash_test);
+}
diff --git a/crash_reporter/crash_reporter_logs.conf b/crash_reporter/crash_reporter_logs.conf
new file mode 100644
index 0000000..f5ca80c
--- /dev/null
+++ b/crash_reporter/crash_reporter_logs.conf
@@ -0,0 +1,112 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can
+# be found in the LICENSE file.
+
+# This file is parsed by chromeos::KeyValueStore. It has the format:
+#
+# <basename>=<shell command>\n
+#
+# Commands may be split across multiple lines using trailing backslashes.
+#
+# When an executable named <basename> crashes, the corresponding command is
+# executed and its standard output and standard error are attached to the crash
+# report.
+#
+# Use caution in modifying this file. Only run common Unix commands here, as
+# these commands will be run when a crash has recently occurred and we should
+# avoid running anything that might cause another crash. Similarly, these
+# commands block notification of the crash to parent processes, so commands
+# should execute quickly.
+
+update_engine=cat $(ls -1tr /var/log/update_engine | tail -5 | \
+ sed s.^./var/log/update_engine/.) | tail -c 50000
+
+# The cros_installer output is logged into the update engine log file,
+# so it is handled in the same way as update_engine.
+cros_installer=cat $(ls -1tr /var/log/update_engine | tail -5 | \
+ sed s.^./var/log/update_engine/.) | tail -c 50000
+
+# Dump the last 20 lines of the last two files in Chrome's system and user log
+# directories, along with the last 20 messages from the session manager.
+chrome=\
+ for f in $(ls -1rt /var/log/chrome/chrome_[0-9]* | tail -2) \
+ $(ls -1rt /home/chronos/u-*/log/chrome_[0-9]* 2>/dev/null | tail -2); do \
+ echo "===$f (tail)==="; \
+ tail -20 $f; \
+ echo EOF; \
+ echo; \
+ done; \
+ echo "===session_manager (tail)==="; \
+ awk '$3 ~ "^session_manager\[" { print }' /var/log/messages | tail -20; \
+ echo EOF
+
+# The following rule is used for generating additional diagnostics when
+# collection of user crashes fails. This output should not be too large
+# as it is stored in memory. The output format specified for 'ps' is the
+# same as with the "u" ("user-oriented") option, except it doesn't show
+# the commands' arguments (i.e. "comm" instead of "command").
+crash_reporter-user-collection=\
+ echo "===ps output==="; \
+ ps axw -o user,pid,%cpu,%mem,vsz,rss,tname,stat,start_time,bsdtime,comm | \
+ tail -c 25000; \
+ echo "===meminfo==="; \
+ cat /proc/meminfo
+
+# This rule is similar to the crash_reporter-user-collection rule, except it is
+# run for kernel errors reported through udev events.
+crash_reporter-udev-collection-change-card0-drm=\
+ for dri in /sys/kernel/debug/dri/*; do \
+ echo "===$dri/i915_error_state==="; \
+ cat $dri/i915_error_state; \
+ done
+
+# When trackpad driver cyapa detects some abnormal behavior, we collect
+# additional logs from kernel messages.
+crash_reporter-udev-collection-change--i2c-cyapa=\
+ /usr/sbin/kernel_log_collector.sh cyapa 30
+# When trackpad/touchscreen driver atmel_mxt_ts detects some abnormal behavior,
+# we collect additional logs from kernel messages.
+crash_reporter-udev-collection-change--i2c-atmel_mxt_ts=\
+ /usr/sbin/kernel_log_collector.sh atmel 30
+# When touch device noise are detected, we collect relevant logs.
+# (crosbug.com/p/16788)
+crash_reporter-udev-collection---TouchNoise=cat /var/log/touch_noise.log
+# Periodically collect touch event log for debugging (crosbug.com/p/17244)
+crash_reporter-udev-collection---TouchEvent=cat /var/log/touch_event.log
+
+# Collect the last 50 lines of /var/log/messages and /var/log/net.log for
+# intel wifi driver (iwlwifi) for debugging purpose.
+crash_reporter-udev-collection-devcoredump-iwlwifi=\
+ echo "===/var/log/messages==="; \
+ tail -n 50 /var/log/messages; \
+ echo "===/var/log/net.log==="; \
+ tail -n 50 /var/log/net.log; \
+ echo EOF
+
+# Dump the last 50 lines of the last two powerd log files -- if the job has
+# already restarted, we want to see the end of the previous instance's logs.
+powerd=\
+ for f in $(ls -1tr /var/log/power_manager/powerd.[0-9]* | tail -2); do \
+ echo "===$(basename $f) (tail)==="; \
+ tail -50 $f; \
+ echo EOF; \
+ done
+# If power_supply_info aborts (due to e.g. a bad battery), its failure message
+# could end up in various places depending on which process was running it.
+# Attach the end of powerd's log since it might've also logged the underlying
+# problem.
+power_supply_info=\
+ echo "===powerd.LATEST (tail)==="; \
+ tail -50 /var/log/power_manager/powerd.LATEST; \
+ echo EOF
+# powerd_setuid_helper gets run by powerd, so its stdout/stderr will be mixed in
+# with powerd's stdout/stderr.
+powerd_setuid_helper=\
+ echo "===powerd.OUT (tail)==="; \
+ tail -50 /var/log/powerd.out; \
+ echo EOF
+
+# The following rules are only for testing purposes.
+crash_log_test=echo hello world
+crash_log_recursion_test=sleep 1 && \
+ /usr/local/autotest/tests/crash_log_recursion_test
diff --git a/crash_reporter/crash_reporter_logs_test.cc b/crash_reporter/crash_reporter_logs_test.cc
new file mode 100644
index 0000000..9879470
--- /dev/null
+++ b/crash_reporter/crash_reporter_logs_test.cc
@@ -0,0 +1,28 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <chromeos/key_value_store.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+// Name of the checked-in configuration file containing log-collection commands.
+const char kConfigFile[] = "crash_reporter_logs.conf";
+
+// Executable name for Chrome. kConfigFile is expected to contain this entry.
+const char kChromeExecName[] = "chrome";
+
+} // namespace
+
+// Tests that the config file is parsable and that Chrome is listed.
+TEST(CrashReporterLogsTest, ReadConfig) {
+ chromeos::KeyValueStore store;
+ ASSERT_TRUE(store.Load(base::FilePath(kConfigFile)));
+ std::string command;
+ EXPECT_TRUE(store.GetString(kChromeExecName, &command));
+ EXPECT_FALSE(command.empty());
+}
diff --git a/crash_reporter/crash_sender b/crash_reporter/crash_sender
new file mode 100755
index 0000000..641ae2d
--- /dev/null
+++ b/crash_reporter/crash_sender
@@ -0,0 +1,713 @@
+#!/bin/sh
+
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+
+# Default product ID in crash report (used if GOOGLE_CRASH_* is undefined).
+CHROMEOS_PRODUCT=ChromeOS
+
+# File whose existence implies crash reports may be sent, and whose
+# contents includes our machine's anonymized guid.
+CONSENT_ID="/home/chronos/Consent To Send Stats"
+
+# Crash sender lock in case the sender is already running.
+CRASH_SENDER_LOCK="/var/lock/crash_sender"
+
+# Path to file that indicates a crash test is currently running.
+CRASH_TEST_IN_PROGRESS_FILE="/tmp/crash-test-in-progress"
+
+# Path to find which is required for computing the crash rate.
+FIND="/usr/bin/find"
+
+# Set this to 1 in the environment to allow uploading crash reports
+# for unofficial versions.
+FORCE_OFFICIAL=${FORCE_OFFICIAL:-0}
+
+# Path to hardware class description.
+HWCLASS_PATH="/sys/devices/platform/chromeos_acpi/HWID"
+
+# Path to file that indicates this is a developer image.
+LEAVE_CORE_FILE="/root/.leave_core"
+
+# Path to list_proxies.
+LIST_PROXIES="/usr/bin/list_proxies"
+
+# Maximum crashes to send per day.
+MAX_CRASH_RATE=${MAX_CRASH_RATE:-32}
+
+# Path to metrics_client.
+METRICS_CLIENT="/usr/bin/metrics_client"
+
+# File whose existence mocks crash sending. If empty we pretend the
+# crash sending was successful, otherwise unsuccessful.
+MOCK_CRASH_SENDING="/tmp/mock-crash-sending"
+
+# Set this to 1 in the environment to pretend to have booted in developer
+# mode. This is used by autotests.
+MOCK_DEVELOPER_MODE=${MOCK_DEVELOPER_MODE:-0}
+
+# Ignore PAUSE_CRASH_SENDING file if set.
+OVERRIDE_PAUSE_SENDING=${OVERRIDE_PAUSE_SENDING:-0}
+
+# File whose existence causes crash sending to be delayed (for testing).
+# Must be stateful to enable testing kernel crashes.
+PAUSE_CRASH_SENDING="/var/lib/crash_sender_paused"
+
+# URL to send official build crash reports to.
+REPORT_UPLOAD_PROD_URL="https://clients2.google.com/cr/report"
+
+# Path to a directory of restricted certificates which includes
+# a certificate for ${REPORT_UPLOAD_PROD_URL}.
+RESTRICTED_CERTIFICATES_PATH="/usr/share/chromeos-ca-certificates"
+
+# File whose existence implies we're running and not to start again.
+RUN_FILE="/var/run/crash_sender.pid"
+
+# Maximum time to sleep between sends.
+SECONDS_SEND_SPREAD=${SECONDS_SEND_SPREAD:-600}
+
+# Set this to 1 to allow uploading of device coredumps.
+DEVCOREDUMP_UPLOAD_FLAG_FILE=\
+"/var/lib/crash_reporter/device_coredump_upload_allowed"
+
+# The syslog tag for all logging we emit.
+TAG="$(basename $0)[$$]"
+
+# Directory to store timestamp files indicating the uploads in the past 24
+# hours.
+TIMESTAMPS_DIR="/var/lib/crash_sender"
+
+# Temp directory for this process.
+TMP_DIR=""
+
+# Chrome's crash report log file.
+CHROME_CRASH_LOG="/var/log/chrome/Crash Reports/uploads.log"
+
+lecho() {
+ logger -t "${TAG}" "$@"
+}
+
+# Returns true if mock is enabled.
+is_mock() {
+ [ -f "${MOCK_CRASH_SENDING}" ] && return 0
+ return 1
+}
+
+is_mock_successful() {
+ local mock_in=$(cat "${MOCK_CRASH_SENDING}")
+ [ "${mock_in}" = "" ] && return 0 # empty file means success
+ return 1
+}
+
+cleanup() {
+ if [ -n "${TMP_DIR}" ]; then
+ rm -rf "${TMP_DIR}"
+ fi
+ rm -f "${RUN_FILE}"
+ crash_done
+}
+
+crash_done() {
+ if is_mock; then
+ # For testing purposes, emit a message to log so that we
+ # know when the test has received all the messages from this run.
+ lecho "crash_sender done."
+ fi
+}
+
+is_official_image() {
+ [ ${FORCE_OFFICIAL} -ne 0 ] && return 0
+ grep ^CHROMEOS_RELEASE_DESCRIPTION /etc/lsb-release | grep -q Official
+}
+
+# Returns 0 if the a crash test is currently running. NOTE: Mirrors
+# crash_collector.cc:CrashCollector::IsCrashTestInProgress().
+is_crash_test_in_progress() {
+ [ -f "${CRASH_TEST_IN_PROGRESS_FILE}" ] && return 0
+ return 1
+}
+
+# Returns 0 if we should consider ourselves to be running on a developer
+# image. NOTE: Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage().
+is_developer_image() {
+ # If we're testing crash reporter itself, we don't want to special-case
+ # for developer images.
+ is_crash_test_in_progress && return 1
+ [ -f "${LEAVE_CORE_FILE}" ] && return 0
+ return 1
+}
+
+# Returns 0 if we should consider ourselves to be running on a test image.
+is_test_image() {
+ # If we're testing crash reporter itself, we don't want to special-case
+ # for test images.
+ is_crash_test_in_progress && return 1
+ case $(get_channel) in
+ test*) return 0;;
+ esac
+ return 1
+}
+
+# Returns 0 if the machine booted up in developer mode.
+is_developer_mode() {
+ [ ${MOCK_DEVELOPER_MODE} -ne 0 ] && return 0
+ # If we're testing crash reporter itself, we don't want to special-case
+ # for developer mode.
+ is_crash_test_in_progress && return 1
+ crossystem "devsw_boot?1" # exit status will be accurate
+}
+
+# Return 0 if the uploading of device coredumps is allowed.
+is_device_coredump_upload_allowed() {
+ [ -f "${DEVCOREDUMP_UPLOAD_FLAG_FILE}" ] && return 0
+ return 1
+}
+
+# Generate a uniform random number in 0..max-1.
+generate_uniform_random() {
+ local max=$1
+ local random="$(od -An -N4 -tu /dev/urandom)"
+ echo $((random % max))
+}
+
+# Check if sending a crash now does not exceed the maximum 24hr rate and
+# commit to doing so, if not.
+check_rate() {
+ mkdir -p ${TIMESTAMPS_DIR}
+ # Only consider minidumps written in the past 24 hours by removing all older.
+ ${FIND} "${TIMESTAMPS_DIR}" -mindepth 1 -mmin +$((24 * 60)) \
+ -exec rm -- '{}' ';'
+ local sends_in_24hrs=$(echo "${TIMESTAMPS_DIR}"/* | wc -w)
+ lecho "Current send rate: ${sends_in_24hrs}sends/24hrs"
+ if [ ${sends_in_24hrs} -ge ${MAX_CRASH_RATE} ]; then
+ lecho "Cannot send more crashes:"
+ lecho " current ${sends_in_24hrs}send/24hrs >= " \
+ "max ${MAX_CRASH_RATE}send/24hrs"
+ return 1
+ fi
+ mktemp "${TIMESTAMPS_DIR}"/XXXX > /dev/null
+ return 0
+}
+
+# Gets the base part of a crash report file, such as name.01234.5678.9012 from
+# name.01234.5678.9012.meta or name.01234.5678.9012.log.tar.xz. We make sure
+# "name" is sanitized in CrashCollector::Sanitize to not include any periods.
+get_base() {
+ echo "$1" | cut -d. -f-4
+}
+
+get_extension() {
+ local extension="${1##*.}"
+ local filename="${1%.*}"
+ # For gzipped file, we ignore .gz and get the real extension
+ if [ "${extension}" = "gz" ]; then
+ echo "${filename##*.}"
+ else
+ echo "${extension}"
+ fi
+}
+
+# Return which kind of report the given metadata file relates to
+get_kind() {
+ local payload="$(get_key_value "$1" "payload")"
+ if [ ! -r "${payload}" ]; then
+ lecho "Missing payload: ${payload}"
+ echo "undefined"
+ return
+ fi
+ local kind="$(get_extension "${payload}")"
+ if [ "${kind}" = "dmp" ]; then
+ echo "minidump"
+ return
+ fi
+ echo "${kind}"
+}
+
+get_key_value() {
+ local file="$1" key="$2" value
+
+ if [ -f "${file}" ]; then
+ # Return the first entry. There shouldn't be more than one anyways.
+ # Substr at length($1) + 2 skips past the key and following = sign (awk
+ # uses 1-based indexes), but preserves embedded = characters.
+ value=$(sed -n "/^${key}[[:space:]]*=/{s:^[^=]*=::p;q}" "${file}")
+ fi
+
+ echo "${value:-undefined}"
+}
+
+get_keys() {
+ local file="$1" regex="$2"
+
+ awk -F'[[:space:]=]' -vregex="${regex}" \
+ 'match($1, regex) { print $1 }' "${file}"
+}
+
+# Return the board name.
+get_board() {
+ get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_BOARD"
+}
+
+# Return the channel name (sans "-channel" suffix).
+get_channel() {
+ get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_TRACK" |
+ sed 's:-channel$::'
+}
+
+# Return the hardware class or "undefined".
+get_hardware_class() {
+ if [ -r "${HWCLASS_PATH}" ]; then
+ cat "${HWCLASS_PATH}"
+ elif crossystem hwid > /dev/null 2>&1; then
+ echo "$(crossystem hwid)"
+ else
+ echo "undefined"
+ fi
+}
+
+send_crash() {
+ local meta_path="$1"
+ local report_payload="$(get_key_value "${meta_path}" "payload")"
+ local kind="$(get_kind "${meta_path}")"
+ local exec_name="$(get_key_value "${meta_path}" "exec_name")"
+ local url="${REPORT_UPLOAD_PROD_URL}"
+ local chromeos_version="$(get_key_value "${meta_path}" "ver")"
+ local board="$(get_board)"
+ local hwclass="$(get_hardware_class)"
+ local write_payload_size="$(get_key_value "${meta_path}" "payload_size")"
+ local log="$(get_key_value "${meta_path}" "log")"
+ local sig="$(get_key_value "${meta_path}" "sig")"
+ local send_payload_size="$(stat --printf=%s "${report_payload}" 2>/dev/null)"
+ local product="$(get_key_value "${meta_path}" "upload_var_prod")"
+ local version="$(get_key_value "${meta_path}" "upload_var_ver")"
+ local upload_prefix="$(get_key_value "${meta_path}" "upload_prefix")"
+ local guid
+
+ set -- \
+ -F "write_payload_size=${write_payload_size}" \
+ -F "send_payload_size=${send_payload_size}"
+ if [ "${sig}" != "undefined" ]; then
+ set -- "$@" \
+ -F "sig=${sig}" \
+ -F "sig2=${sig}"
+ fi
+ if [ -r "${report_payload}" ]; then
+ set -- "$@" \
+ -F "upload_file_${kind}=@${report_payload}"
+ fi
+ if [ "${log}" != "undefined" -a -r "${log}" ]; then
+ set -- "$@" \
+ -F "log=@${log}"
+ fi
+
+ if [ "${upload_prefix}" = "undefined" ]; then
+ upload_prefix=""
+ fi
+
+ # Grab any variable that begins with upload_.
+ local v
+ for k in $(get_keys "${meta_path}" "^upload_"); do
+ v="$(get_key_value "${meta_path}" "${k}")"
+ case ${k} in
+ # Product & version are handled separately.
+ upload_var_prod) ;;
+ upload_var_ver) ;;
+ upload_var_*)
+ set -- "$@" -F "${upload_prefix}${k#upload_var_}=${v}"
+ ;;
+ upload_file_*)
+ if [ -r "${v}" ]; then
+ set -- "$@" -F "${upload_prefix}${k#upload_file_}=@${v}"
+ fi
+ ;;
+ esac
+ done
+
+ # When uploading Chrome reports we need to report the right product and
+ # version. If the meta file does not specify it, use GOOGLE_CRASH_ID
+ # as the product and GOOGLE_CRASH_VERSION_ID as the version.
+ if [ "${product}" = "undefined" ]; then
+ product="$(get_key_value /etc/os-release 'GOOGLE_CRASH_ID')"
+ fi
+ if [ "${version}" = "undefined" ]; then
+ version="$(get_key_value /etc/os-release 'GOOGLE_CRASH_VERSION_ID')"
+ fi
+
+ # If GOOGLE_CRASH_* is undefined, we look for ID and VERSION_ID in
+ # /etc/os-release.
+ if [ "${product}" = "undefined" ]; then
+ product="$(get_key_value /etc/os-release 'ID')"
+ fi
+ if [ "${version}" = "undefined" ]; then
+ version="$(get_key_value /etc/os-release 'VERSION_ID')"
+ fi
+
+ # If ID or VERSION_ID is undefined, we use the default product name
+ # and CHROMEOS_RELEASE_VERSION from /etc/lsb-release.
+ if [ "${product}" = "undefined" ]; then
+ product="${CHROMEOS_PRODUCT}"
+ fi
+ if [ "${version}" = "undefined" ]; then
+ version="${chromeos_version}"
+ fi
+
+ local image_type
+ if is_test_image; then
+ image_type="test"
+ elif is_developer_image; then
+ image_type="dev"
+ elif [ ${FORCE_OFFICIAL} -ne 0 ]; then
+ image_type="force-official"
+ elif is_mock && ! is_mock_successful; then
+ image_type="mock-fail"
+ fi
+
+ local boot_mode
+ if ! crossystem "cros_debug" > /dev/null 2>&1; then
+ # Sanity-check failed that makes sure crossystem exists.
+ lecho "Cannot determine boot mode due to error running crossystem command"
+ boot_mode="missing-crossystem"
+ elif is_developer_mode; then
+ boot_mode="dev"
+ fi
+
+ # Need to strip dashes ourselves as Chrome preserves it in the file
+ # nowadays. This is also what the Chrome breakpad client does.
+ guid=$(tr -d '-' < "${CONSENT_ID}")
+
+ local error_type="$(get_key_value "${meta_path}" "error_type")"
+ [ "${error_type}" = "undefined" ] && error_type=
+
+ lecho "Sending crash:"
+ if [ "${product}" != "${CHROMEOS_PRODUCT}" ]; then
+ lecho " Sending crash report on behalf of ${product}"
+ fi
+ lecho " Metadata: ${meta_path} (${kind})"
+ lecho " Payload: ${report_payload}"
+ lecho " Version: ${version}"
+ [ -n "${image_type}" ] && lecho " Image type: ${image_type}"
+ [ -n "${boot_mode}" ] && lecho " Boot mode: ${boot_mode}"
+ if is_mock; then
+ lecho " Product: ${product}"
+ lecho " URL: ${url}"
+ lecho " Board: ${board}"
+ lecho " HWClass: ${hwclass}"
+ lecho " write_payload_size: ${write_payload_size}"
+ lecho " send_payload_size: ${send_payload_size}"
+ if [ "${log}" != "undefined" ]; then
+ lecho " log: @${log}"
+ fi
+ if [ "${sig}" != "undefined" ]; then
+ lecho " sig: ${sig}"
+ fi
+ fi
+ lecho " Exec name: ${exec_name}"
+ [ -n "${error_type}" ] && lecho " Error type: ${error_type}"
+ if is_mock; then
+ if ! is_mock_successful; then
+ lecho "Mocking unsuccessful send"
+ return 1
+ fi
+ lecho "Mocking successful send"
+ return 0
+ fi
+
+ # Read in the first proxy, if any, for a given URL. NOTE: The
+ # double-quotes are necessary due to a bug in dash with the "local"
+ # builtin command and values that have spaces in them (see
+ # "https://bugs.launchpad.net/ubuntu/+source/dash/+bug/139097").
+ if [ -f "${LIST_PROXIES}" ]; then
+ local proxy ret
+ proxy=$("${LIST_PROXIES}" --quiet "${url}")
+ ret=$?
+ if [ ${ret} -ne 0 ]; then
+ proxy=''
+ lecho -psyslog.warn \
+ "Listing proxies failed with exit code ${ret}"
+ else
+ proxy=$(echo "${proxy}" | head -1)
+ fi
+ fi
+ # if a direct connection should be used, unset the proxy variable.
+ [ "${proxy}" = "direct://" ] && proxy=
+ local report_id="${TMP_DIR}/report_id"
+ local curl_stderr="${TMP_DIR}/curl_stderr"
+
+ set +e
+ curl "${url}" -v ${proxy:+--proxy "$proxy"} \
+ --capath "${RESTRICTED_CERTIFICATES_PATH}" --ciphers HIGH \
+ -F "prod=${product}" \
+ -F "ver=${version}" \
+ -F "board=${board}" \
+ -F "hwclass=${hwclass}" \
+ -F "exec_name=${exec_name}" \
+ ${image_type:+-F "image_type=${image_type}"} \
+ ${boot_mode:+-F "boot_mode=${boot_mode}"} \
+ ${error_type:+-F "error_type=${error_type}"} \
+ -F "guid=${guid}" \
+ -o "${report_id}" \
+ "$@" \
+ 2>"${curl_stderr}"
+ curl_result=$?
+ set -e
+
+ if [ ${curl_result} -eq 0 ]; then
+ local id="$(cat "${report_id}")"
+ local product_name
+ local timestamp="$(date +%s)"
+ case ${product} in
+ Chrome_ChromeOS)
+ if is_official_image; then
+ product_name="Chrome"
+ else
+ product_name="Chromium"
+ fi
+ ;;
+ *)
+ if is_official_image; then
+ product_name="ChromeOS"
+ else
+ product_name="ChromiumOS"
+ fi
+ ;;
+ esac
+ printf '%s,%s,%s\n' \
+ "${timestamp}" "${id}" "${product_name}" >> "${CHROME_CRASH_LOG}"
+ lecho "Crash report receipt ID ${id}"
+ else
+ lecho "Crash sending failed with exit code ${curl_result}: " \
+ "$(cat "${curl_stderr}")"
+ fi
+
+ rm -f "${report_id}"
+
+ return ${curl_result}
+}
+
+# *.meta files always end with done=1 so we can tell if they are complete.
+is_complete_metadata() {
+ grep -q "done=1" "$1"
+}
+
+# Remove the given report path.
+remove_report() {
+ local base="${1%.*}"
+ rm -f -- "${base}".*
+}
+
+# Send all crashes from the given directory. This applies even when we're on a
+# 3G connection (see crosbug.com/3304 for discussion).
+send_crashes() {
+ local dir="$1"
+
+ if [ ! -d "${dir}" ]; then
+ return
+ fi
+
+ # Consider any old files which still have no corresponding meta file
+ # as orphaned, and remove them.
+ for old_file in $(${FIND} "${dir}" -mindepth 1 \
+ -mmin +$((24 * 60)) -type f); do
+ if [ ! -e "$(get_base "${old_file}").meta" ]; then
+ lecho "Removing old orphaned file: ${old_file}."
+ rm -f -- "${old_file}"
+ fi
+ done
+
+ # Look through all metadata (*.meta) files, oldest first. That way, the rate
+ # limit does not stall old crashes if there's a high amount of new crashes
+ # coming in.
+ # For each crash report, first evaluate conditions that might lead to its
+ # removal to honor user choice and to free disk space as soon as possible,
+ # then decide whether it should be sent right now or kept for later sending.
+ for meta_path in $(ls -1tr "${dir}"/*.meta 2>/dev/null); do
+ lecho "Considering metadata ${meta_path}."
+
+ local kind=$(get_kind "${meta_path}")
+ if [ "${kind}" != "minidump" ] && \
+ [ "${kind}" != "kcrash" ] && \
+ [ "${kind}" != "log" ] &&
+ [ "${kind}" != "devcore" ]; then
+ lecho "Unknown report kind ${kind}. Removing report."
+ remove_report "${meta_path}"
+ continue
+ fi
+
+ if ! is_complete_metadata "${meta_path}"; then
+ # This report is incomplete, so if it's old, just remove it.
+ local old_meta=$(${FIND} "${dir}" -mindepth 1 -name \
+ $(basename "${meta_path}") -mmin +$((24 * 60)) -type f)
+ if [ -n "${old_meta}" ]; then
+ lecho "Removing old incomplete metadata."
+ remove_report "${meta_path}"
+ else
+ lecho "Ignoring recent incomplete metadata."
+ fi
+ continue
+ fi
+
+ # Ignore device coredump if device coredump uploading is not allowed.
+ if [ "${kind}" = "devcore" ] && ! is_device_coredump_upload_allowed; then
+ lecho "Ignoring device coredump. Device coredump upload not allowed."
+ continue
+ fi
+
+ if ! is_mock && ! is_official_image; then
+ lecho "Not an official OS version. Removing crash."
+ remove_report "${meta_path}"
+ continue
+ fi
+
+ # Don't send crash reports from previous sessions while we're in guest mode
+ # to avoid the impression that crash reporting was enabled, which it isn't.
+ # (Don't exit right now because subsequent reports may be candidates for
+ # deletion.)
+ if ${METRICS_CLIENT} -g; then
+ lecho "Guest mode has been entered. Delaying crash sending until exited."
+ continue
+ fi
+
+ # Remove existing crashes in case user consent has not (yet) been given or
+ # has been revoked. This must come after the guest mode check because
+ # ${METRICS_CLIENT} always returns "not consented" in guest mode.
+ if ! ${METRICS_CLIENT} -c; then
+ lecho "Crash reporting is disabled. Removing crash."
+ remove_report "${meta_path}"
+ continue
+ fi
+
+ # Skip report if the upload rate is exceeded. (Don't exit right now because
+ # subsequent reports may be candidates for deletion.)
+ if ! check_rate; then
+ lecho "Sending ${meta_path} would exceed rate. Leaving for later."
+ continue
+ fi
+
+ # The .meta file should be written *after* all to-be-uploaded files that it
+ # references. Nevertheless, as a safeguard, a hold-off time of thirty
+ # seconds after writing the .meta file is ensured. Also, sending of crash
+ # reports is spread out randomly by up to SECONDS_SEND_SPREAD. Thus, for
+ # the sleep call the greater of the two delays is used.
+ local now=$(date +%s)
+ local holdoff_time=$(($(stat --format=%Y "${meta_path}") + 30 - ${now}))
+ local spread_time=$(generate_uniform_random "${SECONDS_SEND_SPREAD}")
+ local sleep_time
+ if [ ${spread_time} -gt ${holdoff_time} ]; then
+ sleep_time="${spread_time}"
+ else
+ sleep_time="${holdoff_time}"
+ fi
+ lecho "Scheduled to send in ${sleep_time}s."
+ if ! is_mock; then
+ if ! sleep "${sleep_time}"; then
+ lecho "Sleep failed"
+ return 1
+ fi
+ fi
+
+ # Try to upload.
+ if ! send_crash "${meta_path}"; then
+ lecho "Problem sending ${meta_path}, not removing."
+ continue
+ fi
+
+ # Send was successful, now remove.
+ lecho "Successfully sent crash ${meta_path} and removing."
+ remove_report "${meta_path}"
+ done
+}
+
+usage() {
+ cat <<EOF
+Usage: crash_sender [options]
+
+Options:
+ -e <var>=<val> Set env |var| to |val| (only some vars)
+EOF
+ exit ${1:-1}
+}
+
+parseargs() {
+ # Parse the command line arguments.
+ while [ $# -gt 0 ]; do
+ case $1 in
+ -e)
+ shift
+ case $1 in
+ FORCE_OFFICIAL=*|\
+ MAX_CRASH_RATE=*|\
+ MOCK_DEVELOPER_MODE=*|\
+ OVERRIDE_PAUSE_SENDING=*|\
+ SECONDS_SEND_SPREAD=*)
+ export "$1"
+ ;;
+ *)
+ lecho "Unknown var passed to -e: $1"
+ exit 1
+ ;;
+ esac
+ ;;
+ -h)
+ usage 0
+ ;;
+ *)
+ lecho "Unknown options: $*"
+ exit 1
+ ;;
+ esac
+ shift
+ done
+}
+
+main() {
+ trap cleanup EXIT INT TERM
+
+ parseargs "$@"
+
+ if [ -e "${PAUSE_CRASH_SENDING}" ] && \
+ [ ${OVERRIDE_PAUSE_SENDING} -eq 0 ]; then
+ lecho "Exiting early due to ${PAUSE_CRASH_SENDING}."
+ exit 1
+ fi
+
+ if is_test_image; then
+ lecho "Exiting early due to test image."
+ exit 1
+ fi
+
+ # We don't perform checks on this because we have a master lock with the
+ # CRASH_SENDER_LOCK file. This pid file is for the system to keep track
+ # (like with autotests) that we're still running.
+ echo $$ > "${RUN_FILE}"
+
+ for dependency in "${FIND}" "${METRICS_CLIENT}" \
+ "${RESTRICTED_CERTIFICATES_PATH}"; do
+ if [ ! -x "${dependency}" ]; then
+ lecho "Fatal: Crash sending disabled: ${dependency} not found."
+ exit 1
+ fi
+ done
+
+ TMP_DIR="$(mktemp -d /tmp/crash_sender.XXXXXX)"
+
+ # Send system-wide crashes
+ send_crashes "/var/spool/crash"
+
+ # Send user-specific crashes
+ local d
+ for d in /home/chronos/crash /home/chronos/u-*/crash; do
+ send_crashes "${d}"
+ done
+}
+
+(
+if ! flock -n 9; then
+ lecho "Already running; quitting."
+ crash_done
+ exit 1
+fi
+main "$@"
+) 9>"${CRASH_SENDER_LOCK}"
diff --git a/crash_reporter/dbus_bindings/org.chromium.LibCrosService.xml b/crash_reporter/dbus_bindings/org.chromium.LibCrosService.xml
new file mode 100644
index 0000000..64b8b84
--- /dev/null
+++ b/crash_reporter/dbus_bindings/org.chromium.LibCrosService.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/LibCrosService"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.LibCrosServiceInterface">
+ <method name="ResolveNetworkProxy">
+ <arg name="source_url" type="s" direction="in"/>
+ <arg name="signal_interface" type="s" direction="in"/>
+ <arg name="signal_name" type="s" direction="in"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
+ </method>
+ </interface>
+ <interface name="org.chromium.CrashReporterLibcrosProxyResolvedInterface">
+ <signal name="ProxyResolved">
+ <arg name="source_url" type="s" direction="out"/>
+ <arg name="proxy_info" type="s" direction="out"/>
+ <arg name="error_message" type="s" direction="out"/>
+ </signal>
+ </interface>
+</node>
diff --git a/crash_reporter/init/crash-reporter.conf b/crash_reporter/init/crash-reporter.conf
new file mode 100644
index 0000000..19f2cdb
--- /dev/null
+++ b/crash_reporter/init/crash-reporter.conf
@@ -0,0 +1,27 @@
+# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Initialize crash reporting services"
+author "chromium-os-dev@chromium.org"
+
+# This job merely initializes its service and then terminates; the
+# actual checking and reporting of crash dumps is triggered by an
+# hourly cron job.
+start on starting system-services
+
+pre-start script
+ mkdir -p /var/spool
+
+ # Only allow device coredumps on a "developer system".
+ if ! is_developer_end_user; then
+ # consumer end-user - disable device coredumps, if driver exists.
+ echo 1 > /sys/class/devcoredump/disabled || true
+ fi
+end script
+
+# crash_reporter uses argv[0] as part of the command line for
+# /proc/sys/kernel/core_pattern. That command line is invoked by
+# the kernel, and can't rely on PATH, so argv[0] must be a full
+# path; we invoke it as such here.
+exec /sbin/crash_reporter --init
diff --git a/crash_reporter/init/crash-sender.conf b/crash_reporter/init/crash-sender.conf
new file mode 100644
index 0000000..892186f
--- /dev/null
+++ b/crash_reporter/init/crash-sender.conf
@@ -0,0 +1,11 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Run the crash sender periodically"
+author "chromium-os-dev@chromium.org"
+
+start on starting system-services
+stop on stopping system-services
+
+exec periodic_scheduler 3600 14400 crash_sender /sbin/crash_sender
diff --git a/crash_reporter/init/warn-collector.conf b/crash_reporter/init/warn-collector.conf
new file mode 100644
index 0000000..3be80da
--- /dev/null
+++ b/crash_reporter/init/warn-collector.conf
@@ -0,0 +1,12 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Runs a daemon which collects and reports kernel warnings"
+author "chromium-os-dev@chromium.org"
+
+start on started system-services
+stop on stopping system-services
+respawn
+
+exec warn_collector
diff --git a/crash_reporter/kernel_collector.cc b/crash_reporter/kernel_collector.cc
new file mode 100644
index 0000000..d86efbd
--- /dev/null
+++ b/crash_reporter/kernel_collector.cc
@@ -0,0 +1,591 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/kernel_collector.h"
+
+#include <map>
+#include <sys/stat.h>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+using base::FilePath;
+using base::StringPrintf;
+
+namespace {
+
+const char kDefaultKernelStackSignature[] = "kernel-UnspecifiedStackSignature";
+const char kDumpParentPath[] = "/dev";
+const char kDumpPath[] = "/dev/pstore";
+const char kDumpFormat[] = "dmesg-ramoops-%zu";
+const char kKernelExecName[] = "kernel";
+// Maximum number of records to examine in the kDumpPath.
+const size_t kMaxDumpRecords = 100;
+const pid_t kKernelPid = 0;
+const char kKernelSignatureKey[] = "sig";
+// Byte length of maximum human readable portion of a kernel crash signature.
+const int kMaxHumanStringLength = 40;
+const uid_t kRootUid = 0;
+// Time in seconds from the final kernel log message for a call stack
+// to count towards the signature of the kcrash.
+const int kSignatureTimestampWindow = 2;
+// Kernel log timestamp regular expression.
+const char kTimestampRegex[] = "^<.*>\\[\\s*(\\d+\\.\\d+)\\]";
+
+//
+// These regular expressions enable to us capture the PC in a backtrace.
+// The backtrace is obtained through dmesg or the kernel's preserved/kcrashmem
+// feature.
+//
+// For ARM we see:
+// "<5>[ 39.458982] PC is at write_breakme+0xd0/0x1b4"
+// For MIPS we see:
+// "<5>[ 3378.552000] epc : 804010f0 lkdtm_do_action+0x68/0x3f8"
+// For x86:
+// "<0>[ 37.474699] EIP: [<790ed488>] write_breakme+0x80/0x108
+// SS:ESP 0068:e9dd3efc"
+//
+const char* const kPCRegex[] = {
+ 0,
+ " PC is at ([^\\+ ]+).*",
+ " epc\\s+:\\s+\\S+\\s+([^\\+ ]+).*", // MIPS has an exception program counter
+ " EIP: \\[<.*>\\] ([^\\+ ]+).*", // X86 uses EIP for the program counter
+ " RIP \\[<.*>\\] ([^\\+ ]+).*", // X86_64 uses RIP for the program counter
+};
+
+COMPILE_ASSERT(arraysize(kPCRegex) == KernelCollector::kArchCount,
+ missing_arch_pc_regexp);
+
+} // namespace
+
+KernelCollector::KernelCollector()
+ : is_enabled_(false),
+ ramoops_dump_path_(kDumpPath),
+ records_(0),
+ // We expect crash dumps in the format of architecture we are built for.
+ arch_(GetCompilerArch()) {
+}
+
+KernelCollector::~KernelCollector() {
+}
+
+void KernelCollector::OverridePreservedDumpPath(const FilePath &file_path) {
+ ramoops_dump_path_ = file_path;
+}
+
+bool KernelCollector::ReadRecordToString(std::string *contents,
+ size_t current_record,
+ bool *record_found) {
+ // A record is a ramoops dump. It has an associated size of "record_size".
+ std::string record;
+ std::string captured;
+
+ // Ramoops appends a header to a crash which contains ==== followed by a
+ // timestamp. Ignore the header.
+ pcrecpp::RE record_re(
+ "====\\d+\\.\\d+\n(.*)",
+ pcrecpp::RE_Options().set_multiline(true).set_dotall(true));
+
+ pcrecpp::RE sanity_check_re("\n<\\d+>\\[\\s*(\\d+\\.\\d+)\\]");
+
+ FilePath ramoops_record;
+ GetRamoopsRecordPath(&ramoops_record, current_record);
+ if (!base::ReadFileToString(ramoops_record, &record)) {
+ LOG(ERROR) << "Unable to open " << ramoops_record.value();
+ return false;
+ }
+
+ *record_found = false;
+ if (record_re.FullMatch(record, &captured)) {
+ // Found a ramoops header, so strip the header and append the rest.
+ contents->append(captured);
+ *record_found = true;
+ } else if (sanity_check_re.PartialMatch(record.substr(0, 1024))) {
+ // pstore compression has been added since kernel 3.12. In order to
+ // decompress dmesg correctly, ramoops driver has to strip the header
+ // before handing over the record to the pstore driver, so we don't
+ // need to do it here anymore. However, the sanity check is needed because
+ // sometimes a pstore record is just a chunk of uninitialized memory which
+ // is not the result of a kernel crash. See crbug.com/443764
+ contents->append(record);
+ *record_found = true;
+ } else {
+ LOG(WARNING) << "Found invalid record at " << ramoops_record.value();
+ }
+
+ // Remove the record from pstore after it's found.
+ if (*record_found)
+ base::DeleteFile(ramoops_record, false);
+
+ return true;
+}
+
+void KernelCollector::GetRamoopsRecordPath(FilePath *path,
+ size_t record) {
+ // Disable error "format not a string literal, argument types not checked"
+ // because this is valid, but GNU apparently doesn't bother checking a const
+ // format string.
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ *path = ramoops_dump_path_.Append(StringPrintf(kDumpFormat, record));
+ #pragma GCC diagnostic pop
+}
+
+bool KernelCollector::LoadParameters() {
+ // Discover how many ramoops records are being exported by the driver.
+ size_t count;
+
+ for (count = 0; count < kMaxDumpRecords; ++count) {
+ FilePath ramoops_record;
+ GetRamoopsRecordPath(&ramoops_record, count);
+
+ if (!base::PathExists(ramoops_record))
+ break;
+ }
+
+ records_ = count;
+ return (records_ > 0);
+}
+
+bool KernelCollector::LoadPreservedDump(std::string *contents) {
+ // Load dumps from the preserved memory and save them in contents.
+ // Since the system is set to restart on oops we won't actually ever have
+ // multiple records (only 0 or 1), but check in case we don't restart on
+ // oops in the future.
+ bool any_records_found = false;
+ bool record_found = false;
+ // clear contents since ReadFileToString actually appends to the string.
+ contents->clear();
+
+ for (size_t i = 0; i < records_; ++i) {
+ if (!ReadRecordToString(contents, i, &record_found)) {
+ break;
+ }
+ if (record_found) {
+ any_records_found = true;
+ }
+ }
+
+ if (!any_records_found) {
+ LOG(ERROR) << "No valid records found in " << ramoops_dump_path_.value();
+ return false;
+ }
+
+ return true;
+}
+
+void KernelCollector::StripSensitiveData(std::string *kernel_dump) {
+ // Strip any data that the user might not want sent up to the crash servers.
+ // We'll read in from kernel_dump and also place our output there.
+ //
+ // At the moment, the only sensitive data we strip is MAC addresses.
+
+ // Get rid of things that look like MAC addresses, since they could possibly
+ // give information about where someone has been. This is strings that look
+ // like this: 11:22:33:44:55:66
+ // Complications:
+ // - Within a given kernel_dump, want to be able to tell when the same MAC
+ // was used more than once. Thus, we'll consistently replace the first
+ // MAC found with 00:00:00:00:00:01, the second with ...:02, etc.
+ // - ACPI commands look like MAC addresses. We'll specifically avoid getting
+ // rid of those.
+ std::ostringstream result;
+ std::string pre_mac_str;
+ std::string mac_str;
+ std::map<std::string, std::string> mac_map;
+ pcrecpp::StringPiece input(*kernel_dump);
+
+ // This RE will find the next MAC address and can return us the data preceding
+ // the MAC and the MAC itself.
+ pcrecpp::RE mac_re("(.*?)("
+ "[0-9a-fA-F][0-9a-fA-F]:"
+ "[0-9a-fA-F][0-9a-fA-F]:"
+ "[0-9a-fA-F][0-9a-fA-F]:"
+ "[0-9a-fA-F][0-9a-fA-F]:"
+ "[0-9a-fA-F][0-9a-fA-F]:"
+ "[0-9a-fA-F][0-9a-fA-F])",
+ pcrecpp::RE_Options()
+ .set_multiline(true)
+ .set_dotall(true));
+
+ // This RE will identify when the 'pre_mac_str' shows that the MAC address
+ // was really an ACPI cmd. The full string looks like this:
+ // ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES) filtered out
+ pcrecpp::RE acpi_re("ACPI cmd ef/$",
+ pcrecpp::RE_Options()
+ .set_multiline(true)
+ .set_dotall(true));
+
+ // Keep consuming, building up a result string as we go.
+ while (mac_re.Consume(&input, &pre_mac_str, &mac_str)) {
+ if (acpi_re.PartialMatch(pre_mac_str)) {
+ // We really saw an ACPI command; add to result w/ no stripping.
+ result << pre_mac_str << mac_str;
+ } else {
+ // Found a MAC address; look up in our hash for the mapping.
+ std::string replacement_mac = mac_map[mac_str];
+ if (replacement_mac == "") {
+ // It wasn't present, so build up a replacement string.
+ int mac_id = mac_map.size();
+
+ // Handle up to 2^32 unique MAC address; overkill, but doesn't hurt.
+ replacement_mac = StringPrintf("00:00:%02x:%02x:%02x:%02x",
+ (mac_id & 0xff000000) >> 24,
+ (mac_id & 0x00ff0000) >> 16,
+ (mac_id & 0x0000ff00) >> 8,
+ (mac_id & 0x000000ff));
+ mac_map[mac_str] = replacement_mac;
+ }
+
+ // Dump the string before the MAC and the fake MAC address into result.
+ result << pre_mac_str << replacement_mac;
+ }
+ }
+
+ // One last bit of data might still be in the input.
+ result << input;
+
+ // We'll just assign right back to kernel_dump.
+ *kernel_dump = result.str();
+}
+
+bool KernelCollector::DumpDirMounted() {
+ struct stat st_parent;
+ if (stat(kDumpParentPath, &st_parent)) {
+ PLOG(WARNING) << "Could not stat " << kDumpParentPath;
+ return false;
+ }
+
+ struct stat st_dump;
+ if (stat(kDumpPath, &st_dump)) {
+ PLOG(WARNING) << "Could not stat " << kDumpPath;
+ return false;
+ }
+
+ if (st_parent.st_dev == st_dump.st_dev) {
+ LOG(WARNING) << "Dump dir " << kDumpPath << " not mounted";
+ return false;
+ }
+
+ return true;
+}
+
+bool KernelCollector::Enable() {
+ if (arch_ == kArchUnknown || arch_ >= kArchCount ||
+ kPCRegex[arch_] == nullptr) {
+ LOG(WARNING) << "KernelCollector does not understand this architecture";
+ return false;
+ }
+
+ if (!DumpDirMounted()) {
+ LOG(WARNING) << "Kernel does not support crash dumping";
+ return false;
+ }
+
+ // To enable crashes, we will eventually need to set
+ // the chnv bit in BIOS, but it does not yet work.
+ LOG(INFO) << "Enabling kernel crash handling";
+ is_enabled_ = true;
+ return true;
+}
+
+// Hash a string to a number. We define our own hash function to not
+// be dependent on a C++ library that might change. This function
+// uses basically the same approach as tr1/functional_hash.h but with
+// a larger prime number (16127 vs 131).
+static unsigned HashString(const std::string &input) {
+ unsigned hash = 0;
+ for (size_t i = 0; i < input.length(); ++i)
+ hash = hash * 16127 + input[i];
+ return hash;
+}
+
+void KernelCollector::ProcessStackTrace(
+ pcrecpp::StringPiece kernel_dump,
+ bool print_diagnostics,
+ unsigned *hash,
+ float *last_stack_timestamp,
+ bool *is_watchdog_crash) {
+ pcrecpp::RE line_re("(.+)", pcrecpp::MULTILINE());
+ pcrecpp::RE stack_trace_start_re(std::string(kTimestampRegex) +
+ " (Call Trace|Backtrace):$");
+
+ // Match lines such as the following and grab out "function_name".
+ // The ? may or may not be present.
+ //
+ // For ARM:
+ // <4>[ 3498.731164] [<c0057220>] ? (function_name+0x20/0x2c) from
+ // [<c018062c>] (foo_bar+0xdc/0x1bc)
+ //
+ // For MIPS:
+ // <5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8
+ //
+ // For X86:
+ // <4>[ 6066.849504] [<7937bcee>] ? function_name+0x66/0x6c
+ //
+ pcrecpp::RE stack_entry_re(std::string(kTimestampRegex) +
+ "\\s+\\[<[[:xdigit:]]+>\\]" // Matches " [<7937bcee>]"
+ "([\\s\\?(]+)" // Matches " ? (" (ARM) or " ? " (X86)
+ "([^\\+ )]+)"); // Matches until delimiter reached
+ std::string line;
+ std::string hashable;
+ std::string previous_hashable;
+ bool is_watchdog = false;
+
+ *hash = 0;
+ *last_stack_timestamp = 0;
+
+ // Find the last and second-to-last stack traces. The latter is used when
+ // the panic is from a watchdog timeout.
+ while (line_re.FindAndConsume(&kernel_dump, &line)) {
+ std::string certainty;
+ std::string function_name;
+ if (stack_trace_start_re.PartialMatch(line, last_stack_timestamp)) {
+ if (print_diagnostics) {
+ printf("Stack trace starting.%s\n",
+ hashable.empty() ? "" : " Saving prior trace.");
+ }
+ previous_hashable = hashable;
+ hashable.clear();
+ is_watchdog = false;
+ } else if (stack_entry_re.PartialMatch(line,
+ last_stack_timestamp,
+ &certainty,
+ &function_name)) {
+ bool is_certain = certainty.find('?') == std::string::npos;
+ if (print_diagnostics) {
+ printf("@%f: stack entry for %s (%s)\n",
+ *last_stack_timestamp,
+ function_name.c_str(),
+ is_certain ? "certain" : "uncertain");
+ }
+ // Do not include any uncertain (prefixed by '?') frames in our hash.
+ if (!is_certain)
+ continue;
+ if (!hashable.empty())
+ hashable.append("|");
+ if (function_name == "watchdog_timer_fn" ||
+ function_name == "watchdog") {
+ is_watchdog = true;
+ }
+ hashable.append(function_name);
+ }
+ }
+
+ // If the last stack trace contains a watchdog function we assume the panic
+ // is from the watchdog timer, and we hash the previous stack trace rather
+ // than the last one, assuming that the previous stack is that of the hung
+ // thread.
+ //
+ // In addition, if the hashable is empty (meaning all frames are uncertain,
+ // for whatever reason) also use the previous frame, as it cannot be any
+ // worse.
+ if (is_watchdog || hashable.empty()) {
+ hashable = previous_hashable;
+ }
+
+ *hash = HashString(hashable);
+ *is_watchdog_crash = is_watchdog;
+
+ if (print_diagnostics) {
+ printf("Hash based on stack trace: \"%s\" at %f.\n",
+ hashable.c_str(), *last_stack_timestamp);
+ }
+}
+
+// static
+KernelCollector::ArchKind KernelCollector::GetCompilerArch() {
+#if defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY)
+ return kArchArm;
+#elif defined(COMPILER_GCC) && defined(ARCH_CPU_MIPS_FAMILY)
+ return kArchMips;
+#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_64)
+ return kArchX86_64;
+#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY)
+ return kArchX86;
+#else
+ return kArchUnknown;
+#endif
+}
+
+bool KernelCollector::FindCrashingFunction(
+ pcrecpp::StringPiece kernel_dump,
+ bool print_diagnostics,
+ float stack_trace_timestamp,
+ std::string *crashing_function) {
+ float timestamp = 0;
+
+ // Use the correct regex for this architecture.
+ pcrecpp::RE eip_re(std::string(kTimestampRegex) + kPCRegex[arch_],
+ pcrecpp::MULTILINE());
+
+ while (eip_re.FindAndConsume(&kernel_dump, ×tamp, crashing_function)) {
+ if (print_diagnostics) {
+ printf("@%f: found crashing function %s\n",
+ timestamp,
+ crashing_function->c_str());
+ }
+ }
+ if (timestamp == 0) {
+ if (print_diagnostics) {
+ printf("Found no crashing function.\n");
+ }
+ return false;
+ }
+ if (stack_trace_timestamp != 0 &&
+ abs(static_cast<int>(stack_trace_timestamp - timestamp))
+ > kSignatureTimestampWindow) {
+ if (print_diagnostics) {
+ printf("Found crashing function but not within window.\n");
+ }
+ return false;
+ }
+ if (print_diagnostics) {
+ printf("Found crashing function %s\n", crashing_function->c_str());
+ }
+ return true;
+}
+
+bool KernelCollector::FindPanicMessage(pcrecpp::StringPiece kernel_dump,
+ bool print_diagnostics,
+ std::string *panic_message) {
+ // Match lines such as the following and grab out "Fatal exception"
+ // <0>[ 342.841135] Kernel panic - not syncing: Fatal exception
+ pcrecpp::RE kernel_panic_re(std::string(kTimestampRegex) +
+ " Kernel panic[^\\:]*\\:\\s*(.*)",
+ pcrecpp::MULTILINE());
+ float timestamp = 0;
+ while (kernel_panic_re.FindAndConsume(&kernel_dump,
+ ×tamp,
+ panic_message)) {
+ if (print_diagnostics) {
+ printf("@%f: panic message %s\n",
+ timestamp,
+ panic_message->c_str());
+ }
+ }
+ if (timestamp == 0) {
+ if (print_diagnostics) {
+ printf("Found no panic message.\n");
+ }
+ return false;
+ }
+ return true;
+}
+
+bool KernelCollector::ComputeKernelStackSignature(
+ const std::string &kernel_dump,
+ std::string *kernel_signature,
+ bool print_diagnostics) {
+ unsigned stack_hash = 0;
+ float last_stack_timestamp = 0;
+ std::string human_string;
+ bool is_watchdog_crash;
+
+ ProcessStackTrace(kernel_dump,
+ print_diagnostics,
+ &stack_hash,
+ &last_stack_timestamp,
+ &is_watchdog_crash);
+
+ if (!FindCrashingFunction(kernel_dump,
+ print_diagnostics,
+ last_stack_timestamp,
+ &human_string)) {
+ if (!FindPanicMessage(kernel_dump, print_diagnostics, &human_string)) {
+ if (print_diagnostics) {
+ printf("Found no human readable string, using empty string.\n");
+ }
+ human_string.clear();
+ }
+ }
+
+ if (human_string.empty() && stack_hash == 0) {
+ if (print_diagnostics) {
+ printf("Found neither a stack nor a human readable string, failing.\n");
+ }
+ return false;
+ }
+
+ human_string = human_string.substr(0, kMaxHumanStringLength);
+ *kernel_signature = StringPrintf("%s-%s%s-%08X",
+ kKernelExecName,
+ (is_watchdog_crash ? "(HANG)-" : ""),
+ human_string.c_str(),
+ stack_hash);
+ return true;
+}
+
+bool KernelCollector::Collect() {
+ std::string kernel_dump;
+ FilePath root_crash_directory;
+
+ if (!LoadParameters()) {
+ return false;
+ }
+ if (!LoadPreservedDump(&kernel_dump)) {
+ return false;
+ }
+ StripSensitiveData(&kernel_dump);
+ if (kernel_dump.empty()) {
+ return false;
+ }
+ std::string signature;
+ if (!ComputeKernelStackSignature(kernel_dump, &signature, false)) {
+ signature = kDefaultKernelStackSignature;
+ }
+
+ std::string reason = "handling";
+ bool feedback = true;
+ if (IsDeveloperImage()) {
+ reason = "developer build - always dumping";
+ feedback = true;
+ } else if (!is_feedback_allowed_function_()) {
+ reason = "ignoring - no consent";
+ feedback = false;
+ }
+
+ LOG(INFO) << "Received prior crash notification from "
+ << "kernel (signature " << signature << ") (" << reason << ")";
+
+ if (feedback) {
+ count_crash_function_();
+
+ if (!GetCreatedCrashDirectoryByEuid(kRootUid,
+ &root_crash_directory,
+ nullptr)) {
+ return true;
+ }
+
+ std::string dump_basename =
+ FormatDumpBasename(kKernelExecName, time(nullptr), kKernelPid);
+ FilePath kernel_crash_path = root_crash_directory.Append(
+ StringPrintf("%s.kcrash", dump_basename.c_str()));
+
+ // We must use WriteNewFile instead of base::WriteFile as we
+ // do not want to write with root access to a symlink that an attacker
+ // might have created.
+ if (WriteNewFile(kernel_crash_path,
+ kernel_dump.data(),
+ kernel_dump.length()) !=
+ static_cast<int>(kernel_dump.length())) {
+ LOG(INFO) << "Failed to write kernel dump to "
+ << kernel_crash_path.value().c_str();
+ return true;
+ }
+
+ AddCrashMetaData(kKernelSignatureKey, signature);
+ WriteCrashMetaData(
+ root_crash_directory.Append(
+ StringPrintf("%s.meta", dump_basename.c_str())),
+ kKernelExecName,
+ kernel_crash_path.value());
+
+ LOG(INFO) << "Stored kcrash to " << kernel_crash_path.value();
+ }
+
+ return true;
+}
diff --git a/crash_reporter/kernel_collector.h b/crash_reporter/kernel_collector.h
new file mode 100644
index 0000000..c8aedfe
--- /dev/null
+++ b/crash_reporter/kernel_collector.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_H_
+#define CRASH_REPORTER_KERNEL_COLLECTOR_H_
+
+#include <pcrecpp.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "crash-reporter/crash_collector.h"
+
+// Kernel crash collector.
+class KernelCollector : public CrashCollector {
+ public:
+ // Enumeration to specify architecture type.
+ enum ArchKind {
+ kArchUnknown,
+ kArchArm,
+ kArchMips,
+ kArchX86,
+ kArchX86_64,
+
+ kArchCount // Number of architectures.
+ };
+
+ KernelCollector();
+
+ ~KernelCollector() override;
+
+ void OverridePreservedDumpPath(const base::FilePath &file_path);
+
+ // Enable collection.
+ bool Enable();
+
+ // Returns true if the kernel collection currently enabled.
+ bool is_enabled() const { return is_enabled_; }
+
+ // Collect any preserved kernel crash dump. Returns true if there was
+ // a dump (even if there were problems storing the dump), false otherwise.
+ bool Collect();
+
+ // Compute a stack signature string from a kernel dump.
+ bool ComputeKernelStackSignature(const std::string &kernel_dump,
+ std::string *kernel_signature,
+ bool print_diagnostics);
+
+ // Set the architecture of the crash dumps we are looking at.
+ void set_arch(ArchKind arch) { arch_ = arch; }
+ ArchKind arch() const { return arch_; }
+
+ private:
+ friend class KernelCollectorTest;
+ FRIEND_TEST(KernelCollectorTest, LoadPreservedDump);
+ FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBasic);
+ FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBulk);
+ FRIEND_TEST(KernelCollectorTest, StripSensitiveDataSample);
+ FRIEND_TEST(KernelCollectorTest, CollectOK);
+
+ virtual bool DumpDirMounted();
+
+ bool LoadPreservedDump(std::string *contents);
+ void StripSensitiveData(std::string *kernel_dump);
+
+ void GetRamoopsRecordPath(base::FilePath *path, size_t record);
+ bool LoadParameters();
+ bool HasMoreRecords();
+
+ // Read a record to string, modified from file_utils since that didn't
+ // provide a way to restrict the read length.
+ // Return value indicates (only) error state:
+ // * false when we get an error (can't read from dump location).
+ // * true if no error occured.
+ // Not finding a valid record is not an error state and is signaled by the
+ // record_found output parameter.
+ bool ReadRecordToString(std::string *contents,
+ size_t current_record,
+ bool *record_found);
+
+ void ProcessStackTrace(pcrecpp::StringPiece kernel_dump,
+ bool print_diagnostics,
+ unsigned *hash,
+ float *last_stack_timestamp,
+ bool *is_watchdog_crash);
+ bool FindCrashingFunction(pcrecpp::StringPiece kernel_dump,
+ bool print_diagnostics,
+ float stack_trace_timestamp,
+ std::string *crashing_function);
+ bool FindPanicMessage(pcrecpp::StringPiece kernel_dump,
+ bool print_diagnostics,
+ std::string *panic_message);
+
+ // Returns the architecture kind for which we are built.
+ static ArchKind GetCompilerArch();
+
+ bool is_enabled_;
+ base::FilePath ramoops_dump_path_;
+ size_t records_;
+
+ // The architecture of kernel dump strings we are working with.
+ ArchKind arch_;
+
+ DISALLOW_COPY_AND_ASSIGN(KernelCollector);
+};
+
+#endif // CRASH_REPORTER_KERNEL_COLLECTOR_H_
diff --git a/crash_reporter/kernel_collector_test.cc b/crash_reporter/kernel_collector_test.cc
new file mode 100644
index 0000000..48d94ea
--- /dev/null
+++ b/crash_reporter/kernel_collector_test.cc
@@ -0,0 +1,674 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/kernel_collector_test.h"
+
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/syslog_logging.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using base::StringPrintf;
+using chromeos::FindLog;
+using chromeos::GetLog;
+
+namespace {
+
+int s_crashes = 0;
+bool s_metrics = false;
+
+void CountCrash() {
+ ++s_crashes;
+}
+
+bool IsMetrics() {
+ return s_metrics;
+}
+
+} // namespace
+
+class KernelCollectorTest : public ::testing::Test {
+ protected:
+ void WriteStringToFile(const FilePath &file_path,
+ const char *data) {
+ ASSERT_EQ(strlen(data), base::WriteFile(file_path, data, strlen(data)));
+ }
+
+ void SetUpSuccessfulCollect();
+ void ComputeKernelStackSignatureCommon();
+
+ const FilePath &kcrash_file() const { return test_kcrash_; }
+ const FilePath &test_crash_directory() const { return test_crash_directory_; }
+
+ KernelCollectorMock collector_;
+
+ private:
+ void SetUp() override {
+ s_crashes = 0;
+ s_metrics = true;
+
+ EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+ collector_.Initialize(CountCrash, IsMetrics);
+ ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
+ test_kcrash_ = scoped_temp_dir_.path().Append("kcrash");
+ ASSERT_TRUE(base::CreateDirectory(test_kcrash_));
+ collector_.OverridePreservedDumpPath(test_kcrash_);
+
+ test_kcrash_ = test_kcrash_.Append("dmesg-ramoops-0");
+ ASSERT_FALSE(base::PathExists(test_kcrash_));
+
+ test_crash_directory_ = scoped_temp_dir_.path().Append("crash_directory");
+ ASSERT_TRUE(base::CreateDirectory(test_crash_directory_));
+ chromeos::ClearLog();
+ }
+
+ FilePath test_kcrash_;
+ FilePath test_crash_directory_;
+ base::ScopedTempDir scoped_temp_dir_;
+};
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureBase) {
+ // Make sure the normal build architecture is detected
+ EXPECT_NE(KernelCollector::kArchUnknown, collector_.arch());
+}
+
+TEST_F(KernelCollectorTest, LoadPreservedDump) {
+ ASSERT_FALSE(base::PathExists(kcrash_file()));
+ std::string dump;
+ dump.clear();
+
+ WriteStringToFile(kcrash_file(),
+ "CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]");
+ ASSERT_TRUE(collector_.LoadParameters());
+ ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
+ ASSERT_EQ("CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]", dump);
+
+ WriteStringToFile(kcrash_file(), "====1.1\nsomething");
+ ASSERT_TRUE(collector_.LoadParameters());
+ ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
+ ASSERT_EQ("something", dump);
+
+ WriteStringToFile(kcrash_file(), "\x01\x02\xfe\xff random blob");
+ ASSERT_TRUE(collector_.LoadParameters());
+ ASSERT_FALSE(collector_.LoadPreservedDump(&dump));
+ ASSERT_EQ("", dump);
+}
+
+TEST_F(KernelCollectorTest, EnableMissingKernel) {
+ ASSERT_FALSE(collector_.Enable());
+ ASSERT_FALSE(collector_.is_enabled());
+ ASSERT_TRUE(FindLog(
+ "Kernel does not support crash dumping"));
+ ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(KernelCollectorTest, EnableOK) {
+ WriteStringToFile(kcrash_file(), "");
+ EXPECT_CALL(collector_, DumpDirMounted()).WillOnce(::testing::Return(true));
+ ASSERT_TRUE(collector_.Enable());
+ ASSERT_TRUE(collector_.is_enabled());
+ ASSERT_TRUE(FindLog("Enabling kernel crash handling"));
+ ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(KernelCollectorTest, StripSensitiveDataBasic) {
+ // Basic tests of StripSensitiveData...
+
+ // Make sure we work OK with a string w/ no MAC addresses.
+ const std::string kCrashWithNoMacsOrig =
+ "<7>[111566.131728] PM: Entering mem sleep\n";
+ std::string crash_with_no_macs(kCrashWithNoMacsOrig);
+ collector_.StripSensitiveData(&crash_with_no_macs);
+ EXPECT_EQ(kCrashWithNoMacsOrig, crash_with_no_macs);
+
+ // Make sure that we handle the case where there's nothing before/after the
+ // MAC address.
+ const std::string kJustAMacOrig =
+ "11:22:33:44:55:66";
+ const std::string kJustAMacStripped =
+ "00:00:00:00:00:01";
+ std::string just_a_mac(kJustAMacOrig);
+ collector_.StripSensitiveData(&just_a_mac);
+ EXPECT_EQ(kJustAMacStripped, just_a_mac);
+
+ // Test MAC addresses crammed together to make sure it gets both of them.
+ //
+ // I'm not sure that the code does ideal on these two test cases (they don't
+ // look like two MAC addresses to me), but since we don't see them I think
+ // it's OK to behave as shown here.
+ const std::string kCrammedMacs1Orig =
+ "11:22:33:44:55:66:11:22:33:44:55:66";
+ const std::string kCrammedMacs1Stripped =
+ "00:00:00:00:00:01:00:00:00:00:00:01";
+ std::string crammed_macs_1(kCrammedMacs1Orig);
+ collector_.StripSensitiveData(&crammed_macs_1);
+ EXPECT_EQ(kCrammedMacs1Stripped, crammed_macs_1);
+
+ const std::string kCrammedMacs2Orig =
+ "11:22:33:44:55:6611:22:33:44:55:66";
+ const std::string kCrammedMacs2Stripped =
+ "00:00:00:00:00:0100:00:00:00:00:01";
+ std::string crammed_macs_2(kCrammedMacs2Orig);
+ collector_.StripSensitiveData(&crammed_macs_2);
+ EXPECT_EQ(kCrammedMacs2Stripped, crammed_macs_2);
+
+ // Test case-sensitiveness (we shouldn't be case-senstive).
+ const std::string kCapsMacOrig =
+ "AA:BB:CC:DD:EE:FF";
+ const std::string kCapsMacStripped =
+ "00:00:00:00:00:01";
+ std::string caps_mac(kCapsMacOrig);
+ collector_.StripSensitiveData(&caps_mac);
+ EXPECT_EQ(kCapsMacStripped, caps_mac);
+
+ const std::string kLowerMacOrig =
+ "aa:bb:cc:dd:ee:ff";
+ const std::string kLowerMacStripped =
+ "00:00:00:00:00:01";
+ std::string lower_mac(kLowerMacOrig);
+ collector_.StripSensitiveData(&lower_mac);
+ EXPECT_EQ(kLowerMacStripped, lower_mac);
+}
+
+TEST_F(KernelCollectorTest, StripSensitiveDataBulk) {
+ // Test calling StripSensitiveData w/ lots of MAC addresses in the "log".
+
+ // Test that stripping code handles more than 256 unique MAC addresses, since
+ // that overflows past the last byte...
+ // We'll write up some code that generates 258 unique MAC addresses. Sorta
+ // cheating since the code is very similar to the current code in
+ // StripSensitiveData(), but would catch if someone changed that later.
+ std::string lotsa_macs_orig;
+ std::string lotsa_macs_stripped;
+ int i;
+ for (i = 0; i < 258; i++) {
+ lotsa_macs_orig += StringPrintf(" 11:11:11:11:%02X:%02x",
+ (i & 0xff00) >> 8, i & 0x00ff);
+ lotsa_macs_stripped += StringPrintf(" 00:00:00:00:%02X:%02x",
+ ((i+1) & 0xff00) >> 8, (i+1) & 0x00ff);
+ }
+ std::string lotsa_macs(lotsa_macs_orig);
+ collector_.StripSensitiveData(&lotsa_macs);
+ EXPECT_EQ(lotsa_macs_stripped, lotsa_macs);
+}
+
+TEST_F(KernelCollectorTest, StripSensitiveDataSample) {
+ // Test calling StripSensitiveData w/ some actual lines from a real crash;
+ // included two MAC addresses (though replaced them with some bogusness).
+ const std::string kCrashWithMacsOrig =
+ "<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
+ " filtered out\n"
+ "<7>[108539.540144] wlan0: authenticate with 11:22:33:44:55:66 (try 1)\n"
+ "<7>[108539.554973] wlan0: associate with 11:22:33:44:55:66 (try 1)\n"
+ "<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+ " QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
+ "<7>[110964.314648] wlan0: deauthenticated from 11:22:33:44:55:66"
+ " (Reason: 6)\n"
+ "<7>[110964.325057] phy0: Removed STA 11:22:33:44:55:66\n"
+ "<7>[110964.325115] phy0: Destroyed STA 11:22:33:44:55:66\n"
+ "<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+ " QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
+ "<7>[111566.131728] PM: Entering mem sleep\n";
+ const std::string kCrashWithMacsStripped =
+ "<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
+ " filtered out\n"
+ "<7>[108539.540144] wlan0: authenticate with 00:00:00:00:00:01 (try 1)\n"
+ "<7>[108539.554973] wlan0: associate with 00:00:00:00:00:01 (try 1)\n"
+ "<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+ " QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
+ "<7>[110964.314648] wlan0: deauthenticated from 00:00:00:00:00:01"
+ " (Reason: 6)\n"
+ "<7>[110964.325057] phy0: Removed STA 00:00:00:00:00:01\n"
+ "<7>[110964.325115] phy0: Destroyed STA 00:00:00:00:00:01\n"
+ "<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
+ " QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
+ "<7>[111566.131728] PM: Entering mem sleep\n";
+ std::string crash_with_macs(kCrashWithMacsOrig);
+ collector_.StripSensitiveData(&crash_with_macs);
+ EXPECT_EQ(kCrashWithMacsStripped, crash_with_macs);
+}
+
+TEST_F(KernelCollectorTest, CollectPreservedFileMissing) {
+ ASSERT_FALSE(collector_.Collect());
+ ASSERT_FALSE(FindLog("Stored kcrash to "));
+ ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(KernelCollectorTest, CollectBadDirectory) {
+ WriteStringToFile(kcrash_file(), "====1.1\nsomething");
+ ASSERT_TRUE(collector_.Collect());
+ ASSERT_TRUE(FindLog("Unable to create appropriate crash directory"))
+ << "Did not find expected error string in log: {\n"
+ << GetLog() << "}";
+ ASSERT_EQ(1, s_crashes);
+}
+
+void KernelCollectorTest::SetUpSuccessfulCollect() {
+ collector_.ForceCrashDirectory(test_crash_directory());
+ WriteStringToFile(kcrash_file(), "====1.1\nsomething");
+ ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(KernelCollectorTest, CollectOptedOut) {
+ SetUpSuccessfulCollect();
+ s_metrics = false;
+ ASSERT_TRUE(collector_.Collect());
+ ASSERT_TRUE(FindLog("(ignoring - no consent)"));
+ ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(KernelCollectorTest, CollectOK) {
+ SetUpSuccessfulCollect();
+ ASSERT_TRUE(collector_.Collect());
+ ASSERT_EQ(1, s_crashes);
+ ASSERT_TRUE(FindLog("(handling)"));
+ static const char kNamePrefix[] = "Stored kcrash to ";
+ std::string log = chromeos::GetLog();
+ size_t pos = log.find(kNamePrefix);
+ ASSERT_NE(std::string::npos, pos)
+ << "Did not find string \"" << kNamePrefix << "\" in log: {\n"
+ << log << "}";
+ pos += strlen(kNamePrefix);
+ std::string filename = log.substr(pos, std::string::npos);
+ // Take the name up until \n
+ size_t end_pos = filename.find_first_of("\n");
+ ASSERT_NE(std::string::npos, end_pos);
+ filename = filename.substr(0, end_pos);
+ ASSERT_EQ(0, filename.find(test_crash_directory().value()));
+ ASSERT_TRUE(base::PathExists(FilePath(filename)));
+ std::string contents;
+ ASSERT_TRUE(base::ReadFileToString(FilePath(filename), &contents));
+ ASSERT_EQ("something", contents);
+}
+
+// Perform tests which are common across architectures
+void KernelCollectorTest::ComputeKernelStackSignatureCommon() {
+ std::string signature;
+
+ const char kStackButNoPC[] =
+ "<4>[ 6066.829029] [<790340af>] __do_softirq+0xa6/0x143\n";
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kStackButNoPC, &signature, false));
+ EXPECT_EQ("kernel--83615F0A", signature);
+
+ const char kMissingEverything[] =
+ "<4>[ 6066.829029] [<790340af>] ? __do_softirq+0xa6/0x143\n";
+ EXPECT_FALSE(
+ collector_.ComputeKernelStackSignature(kMissingEverything,
+ &signature,
+ false));
+
+ // Long message.
+ const char kTruncatedMessage[] =
+ "<0>[ 87.485611] Kernel panic - not syncing: 01234567890123456789"
+ "01234567890123456789X\n";
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kTruncatedMessage,
+ &signature,
+ false));
+ EXPECT_EQ("kernel-0123456789012345678901234567890123456789-00000000",
+ signature);
+}
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureARM) {
+ const char kBugToPanic[] =
+ "<5>[ 123.412524] Modules linked in:\n"
+ "<5>[ 123.412534] CPU: 0 Tainted: G W "
+ "(2.6.37-01030-g51cee64 #153)\n"
+ "<5>[ 123.412552] PC is at write_breakme+0xd0/0x1b4\n"
+ "<5>[ 123.412560] LR is at write_breakme+0xc8/0x1b4\n"
+ "<5>[ 123.412569] pc : [<c0058220>] lr : [<c005821c>] "
+ "psr: 60000013\n"
+ "<5>[ 123.412574] sp : f4e0ded8 ip : c04d104c fp : 000e45e0\n"
+ "<5>[ 123.412581] r10: 400ff000 r9 : f4e0c000 r8 : 00000004\n"
+ "<5>[ 123.412589] r7 : f4e0df80 r6 : f4820c80 r5 : 00000004 "
+ "r4 : f4e0dee8\n"
+ "<5>[ 123.412598] r3 : 00000000 r2 : f4e0decc r1 : c05f88a9 "
+ "r0 : 00000039\n"
+ "<5>[ 123.412608] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA "
+ "ARM Segment user\n"
+ "<5>[ 123.412617] Control: 10c53c7d Table: 34dcc04a DAC: 00000015\n"
+ "<0>[ 123.412626] Process bash (pid: 1014, stack limit = 0xf4e0c2f8)\n"
+ "<0>[ 123.412634] Stack: (0xf4e0ded8 to 0xf4e0e000)\n"
+ "<0>[ 123.412641] dec0: "
+ " f4e0dee8 c0183678\n"
+ "<0>[ 123.412654] dee0: 00000000 00000000 00677562 0000081f c06a6a78 "
+ "400ff000 f4e0dfb0 00000000\n"
+ "<0>[ 123.412666] df00: bec7ab44 000b1719 bec7ab0c c004f498 bec7a314 "
+ "c024acc8 00000001 c018359c\n"
+ "<0>[ 123.412679] df20: f4e0df34 c04d10fc f5803c80 271beb39 000e45e0 "
+ "f5803c80 c018359c c017bfe0\n"
+ "<0>[ 123.412691] df40: 00000004 f4820c80 400ff000 f4e0df80 00000004 "
+ "f4e0c000 00000000 c01383e4\n"
+ "<0>[ 123.412703] df60: f4820c80 400ff000 f4820c80 400ff000 00000000 "
+ "00000000 00000004 c0138578\n"
+ "<0>[ 123.412715] df80: 00000000 00000000 00000004 00000000 00000004 "
+ "402f95d0 00000004 00000004\n"
+ "<0>[ 123.412727] dfa0: c0054984 c00547c0 00000004 402f95d0 00000001 "
+ "400ff000 00000004 00000000\n"
+ "<0>[ 123.412739] dfc0: 00000004 402f95d0 00000004 00000004 400ff000 "
+ "000c194c bec7ab58 000e45e0\n"
+ "<0>[ 123.412751] dfe0: 00000000 bec7aad8 40232520 40284e9c 60000010 "
+ "00000001 00000000 00000000\n"
+ "<5>[ 39.496577] Backtrace:\n"
+ "<5>[ 123.412782] [<c0058220>] (__bug+0x20/0x2c) from [<c0183678>] "
+ "(write_breakme+0xdc/0x1bc)\n"
+ "<5>[ 123.412798] [<c0183678>] (write_breakme+0xdc/0x1bc) from "
+ "[<c017bfe0>] (proc_reg_write+0x88/0x9c)\n";
+ std::string signature;
+
+ collector_.set_arch(KernelCollector::kArchArm);
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
+ EXPECT_EQ("kernel-write_breakme-97D3E92F", signature);
+
+ ComputeKernelStackSignatureCommon();
+}
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureMIPS) {
+ const char kBugToPanic[] =
+ "<5>[ 3378.472000] lkdtm: Performing direct entry BUG\n"
+ "<5>[ 3378.476000] Kernel bug detected[#1]:\n"
+ "<5>[ 3378.484000] CPU: 0 PID: 185 Comm: dash Not tainted 3.14.0 #1\n"
+ "<5>[ 3378.488000] task: 8fed5220 ti: 8ec4a000 task.ti: 8ec4a000\n"
+ "<5>[ 3378.496000] $ 0 : 00000000 804018b8 804010f0 7785b507\n"
+ "<5>[ 3378.500000] $ 4 : 8061ab64 81204478 81205b20 00000000\n"
+ "<5>[ 3378.508000] $ 8 : 80830000 20746365 72746e65 55422079\n"
+ "<5>[ 3378.512000] $12 : 8ec4be94 000000fc 00000000 00000048\n"
+ "<5>[ 3378.520000] $16 : 00000004 8ef54000 80710000 00000002\n"
+ "<5>[ 3378.528000] $20 : 7765b6d4 00000004 7fffffff 00000002\n"
+ "<5>[ 3378.532000] $24 : 00000001 803dc0dc \n"
+ "<5>[ 3378.540000] $28 : 8ec4a000 8ec4be20 7775438d 804018b8\n"
+ "<5>[ 3378.544000] Hi : 00000000\n"
+ "<5>[ 3378.548000] Lo : 49bf8080\n"
+ "<5>[ 3378.552000] epc : 804010f0 lkdtm_do_action+0x68/0x3f8\n"
+ "<5>[ 3378.560000] Not tainted\n"
+ "<5>[ 3378.564000] ra : 804018b8 direct_entry+0x110/0x154\n"
+ "<5>[ 3378.568000] Status: 3100dc03 KERNEL EXL IE \n"
+ "<5>[ 3378.572000] Cause : 10800024\n"
+ "<5>[ 3378.576000] PrId : 0001a120 (MIPS interAptiv (multi))\n"
+ "<5>[ 3378.580000] Modules linked in: uinput cfg80211 nf_conntrack_ipv6 "
+ "nf_defrag_ipv6 ip6table_filter ip6_tables pcnet32 mii fuse "
+ "ppp_async ppp_generic slhc tun\n"
+ "<5>[ 3378.600000] Process dash (pid: 185, threadinfo=8ec4a000, "
+ "task=8fed5220, tls=77632490)\n"
+ "<5>[ 3378.608000] Stack : 00000006 ffffff9c 00000000 00000000 00000000 "
+ "00000000 8083454a 00000022\n"
+ "<5> 7765baa1 00001fee 80710000 8ef54000 8ec4bf08 00000002 "
+ "7765b6d4 00000004\n"
+ "<5> 7fffffff 00000002 7775438d 805e5158 7fffffff 00000002 "
+ "00000000 7785b507\n"
+ "<5> 806a96bc 00000004 8ef54000 8ec4bf08 00000002 804018b8 "
+ "80710000 806a98bc\n"
+ "<5> 00000002 00000020 00000004 8d515600 77756450 00000004 "
+ "8ec4bf08 802377e4\n"
+ "<5> ...\n"
+ "<5>[ 3378.652000] Call Trace:\n"
+ "<5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8\n"
+ "<5>[ 3378.660000] [<804018b8>] direct_entry+0x110/0x154\n"
+ "<5>[ 3378.664000] [<802377e4>] vfs_write+0xe0/0x1bc\n"
+ "<5>[ 3378.672000] [<80237f90>] SyS_write+0x78/0xf8\n"
+ "<5>[ 3378.676000] [<80111888>] handle_sys+0x128/0x14c\n"
+ "<5>[ 3378.680000] \n"
+ "<5>[ 3378.684000] \n"
+ "<5>Code: 3c04806b 0c1793aa 248494f0 <000c000d> 3c04806b 248494fc "
+ "0c04cc7f 2405017a 08100514 \n"
+ "<5>[ 3378.696000] ---[ end trace 75067432f24bbc93 ]---\n";
+ std::string signature;
+
+ collector_.set_arch(KernelCollector::kArchMips);
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
+ EXPECT_EQ("kernel-lkdtm_do_action-5E600A6B", signature);
+
+ ComputeKernelStackSignatureCommon();
+}
+
+TEST_F(KernelCollectorTest, ComputeKernelStackSignatureX86) {
+ const char kBugToPanic[] =
+ "<4>[ 6066.829029] [<79039d16>] ? run_timer_softirq+0x165/0x1e6\n"
+ "<4>[ 6066.829029] [<790340af>] ignore_old_stack+0xa6/0x143\n"
+ "<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+"
+ "0xa3/0xb5 [mac80211] SS:ESP 0068:7951febc\n"
+ "<0>[ 6066.829029] CR2: 00000000323038a7\n"
+ "<4>[ 6066.845422] ---[ end trace 12b058bb46c43500 ]---\n"
+ "<0>[ 6066.845747] Kernel panic - not syncing: Fatal exception "
+ "in interrupt\n"
+ "<0>[ 6066.846902] Call Trace:\n"
+ "<4>[ 6066.846902] [<7937a07b>] ? printk+0x14/0x19\n"
+ "<4>[ 6066.949779] [<79379fc1>] panic+0x3e/0xe4\n"
+ "<4>[ 6066.949971] [<7937c5c5>] oops_end+0x73/0x81\n"
+ "<4>[ 6066.950208] [<7901b260>] no_context+0x10d/0x117\n";
+ std::string signature;
+
+ collector_.set_arch(KernelCollector::kArchX86);
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
+ EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-DE253569", signature);
+
+ const char kPCButNoStack[] =
+ "<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+";
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kPCButNoStack, &signature, false));
+ EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-00000000", signature);
+
+ const char kBreakmeBug[] =
+ "<4>[ 180.492137] [<790970c6>] ? handle_mm_fault+0x67f/0x96d\n"
+ "<4>[ 180.492137] [<790dcdfe>] ? proc_reg_write+0x5f/0x73\n"
+ "<4>[ 180.492137] [<790e2224>] ? write_breakme+0x0/0x108\n"
+ "<4>[ 180.492137] [<790dcd9f>] ? proc_reg_write+0x0/0x73\n"
+ "<4>[ 180.492137] [<790ac0aa>] vfs_write+0x85/0xe4\n"
+ "<0>[ 180.492137] Code: c6 44 05 b2 00 89 d8 e8 0c ef 09 00 85 c0 75 "
+ "0b c7 00 00 00 00 00 e9 8e 00 00 00 ba e6 75 4b 79 89 d8 e8 f1 ee 09 "
+ "00 85 c0 75 04 <0f> 0b eb fe ba 58 47 49 79 89 d8 e8 dd ee 09 00 85 "
+ "c0 75 0a 68\n"
+ "<0>[ 180.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
+ "0068:aa3e9efc\n"
+ "<4>[ 180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
+ "<0>[ 180.502026] Kernel panic - not syncing: Fatal exception\n"
+ "<4>[ 180.502026] Call Trace:\n"
+ "<4>[ 180.502806] [<79379aba>] ? printk+0x14/0x1a\n"
+ "<4>[ 180.503033] [<79379a00>] panic+0x3e/0xe4\n"
+ "<4>[ 180.503287] [<7937c005>] oops_end+0x73/0x81\n"
+ "<4>[ 180.503520] [<790055dd>] die+0x58/0x5e\n"
+ "<4>[ 180.503538] [<7937b96c>] do_trap+0x8e/0xa7\n"
+ "<4>[ 180.503555] [<79003d70>] ? do_invalid_op+0x0/0x80\n";
+
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kBreakmeBug, &signature, false));
+ EXPECT_EQ("kernel-write_breakme-122AB3CD", signature);
+
+ const char kPCLineTooOld[] =
+ "<4>[ 174.492137] [<790970c6>] ignored_function+0x67f/0x96d\n"
+ "<4>[ 175.492137] [<790970c6>] ignored_function2+0x67f/0x96d\n"
+ "<0>[ 174.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
+ "0068:aa3e9efc\n"
+ "<4>[ 180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
+ "<4>[ 180.502026] Call Trace:\n"
+ "<0>[ 180.502026] Kernel panic - not syncing: Fatal exception\n"
+ "<4>[ 180.502806] [<79379aba>] printk+0x14/0x1a\n";
+
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kPCLineTooOld, &signature, false));
+ EXPECT_EQ("kernel-Fatal exception-ED4C84FE", signature);
+
+ // Panic without EIP line.
+ const char kExamplePanicOnly[] =
+ "<0>[ 87.485611] Kernel panic - not syncing: Testing panic\n"
+ "<4>[ 87.485630] Pid: 2825, comm: bash Tainted: G "
+ "C 2.6.32.23+drm33.10 #1\n"
+ "<4>[ 87.485639] Call Trace:\n"
+ "<4>[ 87.485660] [<8133f71d>] ? printk+0x14/0x17\n"
+ "<4>[ 87.485674] [<8133f663>] panic+0x3e/0xe4\n"
+ "<4>[ 87.485689] [<810d062e>] write_breakme+0xaa/0x124\n";
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kExamplePanicOnly,
+ &signature,
+ false));
+ EXPECT_EQ("kernel-Testing panic-E0FC3552", signature);
+
+ // Panic from hung task.
+ const char kHungTaskBreakMe[] =
+ "<3>[ 720.459157] INFO: task bash:2287 blocked blah blah\n"
+ "<5>[ 720.459282] Call Trace:\n"
+ "<5>[ 720.459307] [<810a457b>] ? __dentry_open+0x186/0x23e\n"
+ "<5>[ 720.459323] [<810b9c71>] ? mntput_no_expire+0x29/0xe2\n"
+ "<5>[ 720.459336] [<810b9d48>] ? mntput+0x1e/0x20\n"
+ "<5>[ 720.459350] [<810ad135>] ? path_put+0x1a/0x1d\n"
+ "<5>[ 720.459366] [<8137cacc>] schedule+0x4d/0x4f\n"
+ "<5>[ 720.459379] [<8137ccfb>] schedule_timeout+0x26/0xaf\n"
+ "<5>[ 720.459394] [<8102127e>] ? should_resched+0xd/0x27\n"
+ "<5>[ 720.459409] [<81174d1f>] ? _copy_from_user+0x3c/0x50\n"
+ "<5>[ 720.459423] [<8137cd9e>] "
+ "schedule_timeout_uninterruptible+0x1a/0x1c\n"
+ "<5>[ 720.459438] [<810dee63>] write_breakme+0xb3/0x178\n"
+ "<5>[ 720.459453] [<810dedb0>] ? meminfo_proc_show+0x2f2/0x2f2\n"
+ "<5>[ 720.459467] [<810d94ae>] proc_reg_write+0x6d/0x87\n"
+ "<5>[ 720.459481] [<810d9441>] ? proc_reg_poll+0x76/0x76\n"
+ "<5>[ 720.459493] [<810a5e9e>] vfs_write+0x79/0xa5\n"
+ "<5>[ 720.459505] [<810a6011>] sys_write+0x40/0x65\n"
+ "<5>[ 720.459519] [<8137e677>] sysenter_do_call+0x12/0x26\n"
+ "<0>[ 720.459530] Kernel panic - not syncing: hung_task: blocked tasks\n"
+ "<5>[ 720.459768] Pid: 31, comm: khungtaskd Tainted: "
+ "G C 3.0.8 #1\n"
+ "<5>[ 720.459998] Call Trace:\n"
+ "<5>[ 720.460140] [<81378a35>] panic+0x53/0x14a\n"
+ "<5>[ 720.460312] [<8105f875>] watchdog+0x15b/0x1a0\n"
+ "<5>[ 720.460495] [<8105f71a>] ? hung_task_panic+0x16/0x16\n"
+ "<5>[ 720.460693] [<81043af3>] kthread+0x67/0x6c\n"
+ "<5>[ 720.460862] [<81043a8c>] ? __init_kthread_worker+0x2d/0x2d\n"
+ "<5>[ 720.461106] [<8137eb9e>] kernel_thread_helper+0x6/0x10\n";
+
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kHungTaskBreakMe,
+ &signature,
+ false));
+
+ EXPECT_EQ("kernel-(HANG)-hung_task: blocked tasks-600B37EA", signature);
+
+ // Panic with all question marks in the last stack trace.
+ const char kUncertainStackTrace[] =
+ "<0>[56279.689669] ------------[ cut here ]------------\n"
+ "<2>[56279.689677] kernel BUG at /build/x86-alex/tmp/portage/"
+ "sys-kernel/chromeos-kernel-0.0.1-r516/work/chromeos-kernel-0.0.1/"
+ "kernel/timer.c:844!\n"
+ "<0>[56279.689683] invalid opcode: 0000 [#1] SMP \n"
+ "<0>[56279.689688] last sysfs file: /sys/power/state\n"
+ "<5>[56279.689692] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat "
+ "gobi usbnet tsl2583(C) industrialio(C) snd_hda_codec_realtek "
+ "snd_hda_intel i2c_dev snd_hda_codec snd_hwdep qcserial snd_pcm usb_wwan "
+ "i2c_i801 snd_timer nm10_gpio snd_page_alloc rtc_cmos fuse "
+ "nf_conntrack_ipv6 nf_defrag_ipv6 uvcvideo videodev ip6table_filter "
+ "ath9k ip6_tables ipv6 mac80211 ath9k_common ath9k_hw ath cfg80211 "
+ "xt_mark\n"
+ "<5>[56279.689731] \n"
+ "<5>[56279.689738] Pid: 24607, comm: powerd_suspend Tainted: G "
+ "WC 2.6.38.3+ #1 SAMSUNG ELECTRONICS CO., LTD. Alex/G100 \n"
+ "<5>[56279.689748] EIP: 0060:[<8103e3ea>] EFLAGS: 00210286 CPU: 3\n"
+ "<5>[56279.689758] EIP is at add_timer+0xd/0x1b\n"
+ "<5>[56279.689762] EAX: f5e00684 EBX: f5e003c0 ECX: 00000002 EDX: "
+ "00200246\n"
+ "<5>[56279.689767] ESI: f5e003c0 EDI: d28bc03c EBP: d2be5e40 ESP: "
+ "d2be5e40\n"
+ "<5>[56279.689772] DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068\n"
+ "<0>[56279.689778] Process powerd_suspend (pid: 24607, ti=d2be4000 "
+ "task=f5dc9b60 task.ti=d2be4000)\n"
+ "<0>[56279.689782] Stack:\n"
+ "<5>[56279.689785] d2be5e4c f8dccced f4ac02c0 d2be5e70 f8ddc752 "
+ "f5e003c0 f4ac0458 f4ac092c\n"
+ "<5>[56279.689797] f4ac043c f4ac02c0 f4ac0000 f4ac007c d2be5e7c "
+ "f8dd4a33 f4ac0164 d2be5e94\n"
+ "<5>[56279.689809] f87e0304 f69ff0cc f4ac0164 f87e02a4 f4ac0164 "
+ "d2be5eb0 81248968 00000000\n"
+ "<0>[56279.689821] Call Trace:\n"
+ "<5>[56279.689840] [<f8dccced>] ieee80211_sta_restart+0x25/0x8c "
+ "[mac80211]\n"
+ "<5>[56279.689854] [<f8ddc752>] ieee80211_reconfig+0x2e9/0x339 "
+ "[mac80211]\n"
+ "<5>[56279.689869] [<f8dd4a33>] ieee80211_aes_cmac+0x182d/0x184e "
+ "[mac80211]\n"
+ "<5>[56279.689883] [<f87e0304>] cfg80211_get_dev_from_info+0x29b/0x2c0 "
+ "[cfg80211]\n"
+ "<5>[56279.689895] [<f87e02a4>] ? "
+ "cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
+ "<5>[56279.689904] [<81248968>] legacy_resume+0x25/0x5d\n"
+ "<5>[56279.689910] [<812490ae>] device_resume+0xdd/0x110\n"
+ "<5>[56279.689917] [<812491c2>] dpm_resume_end+0xe1/0x271\n"
+ "<5>[56279.689925] [<81060481>] suspend_devices_and_enter+0x18b/0x1de\n"
+ "<5>[56279.689932] [<810605ba>] enter_state+0xe6/0x132\n"
+ "<5>[56279.689939] [<8105fd4b>] state_store+0x91/0x9d\n"
+ "<5>[56279.689945] [<8105fcba>] ? state_store+0x0/0x9d\n"
+ "<5>[56279.689953] [<81178fb1>] kobj_attr_store+0x16/0x22\n"
+ "<5>[56279.689961] [<810eea5e>] sysfs_write_file+0xc1/0xec\n"
+ "<5>[56279.689969] [<810af443>] vfs_write+0x8f/0x101\n"
+ "<5>[56279.689975] [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
+ "<5>[56279.689982] [<810af556>] sys_write+0x40/0x65\n"
+ "<5>[56279.689989] [<81002d57>] sysenter_do_call+0x12/0x26\n"
+ "<0>[56279.689993] Code: c1 d3 e2 4a 89 55 f4 f7 d2 21 f2 6a 00 31 c9 89 "
+ "d8 e8 6e fd ff ff 5a 8d 65 f8 5b 5e 5d c3 55 89 e5 3e 8d 74 26 00 83 38 "
+ "00 74 04 <0f> 0b eb fe 8b 50 08 e8 6f ff ff ff 5d c3 55 89 e5 3e 8d 74 "
+ "26 \n"
+ "<0>[56279.690009] EIP: [<8103e3ea>] add_timer+0xd/0x1b SS:ESP "
+ "0068:d2be5e40\n"
+ "<4>[56279.690113] ---[ end trace b71141bb67c6032a ]---\n"
+ "<7>[56279.694069] wlan0: deauthenticated from 00:00:00:00:00:01 "
+ "(Reason: 6)\n"
+ "<0>[56279.703465] Kernel panic - not syncing: Fatal exception\n"
+ "<5>[56279.703471] Pid: 24607, comm: powerd_suspend Tainted: G D "
+ "WC 2.6.38.3+ #1\n"
+ "<5>[56279.703475] Call Trace:\n"
+ "<5>[56279.703483] [<8136648c>] ? panic+0x55/0x152\n"
+ "<5>[56279.703491] [<810057fa>] ? oops_end+0x73/0x81\n"
+ "<5>[56279.703497] [<81005a44>] ? die+0xed/0xf5\n"
+ "<5>[56279.703503] [<810033cb>] ? do_trap+0x7a/0x80\n"
+ "<5>[56279.703509] [<8100369b>] ? do_invalid_op+0x0/0x80\n"
+ "<5>[56279.703515] [<81003711>] ? do_invalid_op+0x76/0x80\n"
+ "<5>[56279.703522] [<8103e3ea>] ? add_timer+0xd/0x1b\n"
+ "<5>[56279.703529] [<81025e23>] ? check_preempt_curr+0x2e/0x69\n"
+ "<5>[56279.703536] [<8102ef28>] ? ttwu_post_activation+0x5a/0x11b\n"
+ "<5>[56279.703543] [<8102fa8d>] ? try_to_wake_up+0x213/0x21d\n"
+ "<5>[56279.703550] [<81368b7f>] ? error_code+0x67/0x6c\n"
+ "<5>[56279.703557] [<8103e3ea>] ? add_timer+0xd/0x1b\n"
+ "<5>[56279.703577] [<f8dccced>] ? ieee80211_sta_restart+0x25/0x8c "
+ "[mac80211]\n"
+ "<5>[56279.703591] [<f8ddc752>] ? ieee80211_reconfig+0x2e9/0x339 "
+ "[mac80211]\n"
+ "<5>[56279.703605] [<f8dd4a33>] ? ieee80211_aes_cmac+0x182d/0x184e "
+ "[mac80211]\n"
+ "<5>[56279.703618] [<f87e0304>] ? "
+ "cfg80211_get_dev_from_info+0x29b/0x2c0 [cfg80211]\n"
+ "<5>[56279.703630] [<f87e02a4>] ? "
+ "cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
+ "<5>[56279.703637] [<81248968>] ? legacy_resume+0x25/0x5d\n"
+ "<5>[56279.703643] [<812490ae>] ? device_resume+0xdd/0x110\n"
+ "<5>[56279.703649] [<812491c2>] ? dpm_resume_end+0xe1/0x271\n"
+ "<5>[56279.703657] [<81060481>] ? "
+ "suspend_devices_and_enter+0x18b/0x1de\n"
+ "<5>[56279.703663] [<810605ba>] ? enter_state+0xe6/0x132\n"
+ "<5>[56279.703670] [<8105fd4b>] ? state_store+0x91/0x9d\n"
+ "<5>[56279.703676] [<8105fcba>] ? state_store+0x0/0x9d\n"
+ "<5>[56279.703683] [<81178fb1>] ? kobj_attr_store+0x16/0x22\n"
+ "<5>[56279.703690] [<810eea5e>] ? sysfs_write_file+0xc1/0xec\n"
+ "<5>[56279.703697] [<810af443>] ? vfs_write+0x8f/0x101\n"
+ "<5>[56279.703703] [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
+ "<5>[56279.703709] [<810af556>] ? sys_write+0x40/0x65\n"
+ "<5>[56279.703716] [<81002d57>] ? sysenter_do_call+0x12/0x26\n";
+
+ EXPECT_TRUE(
+ collector_.ComputeKernelStackSignature(kUncertainStackTrace,
+ &signature,
+ false));
+ // The first trace contains only uncertain entries and its hash is 00000000,
+ // so, if we used that, the signature would be kernel-add_timer-00000000.
+ // Instead we use the second-to-last trace for the hash.
+ EXPECT_EQ("kernel-add_timer-B5178878", signature);
+
+ ComputeKernelStackSignatureCommon();
+}
diff --git a/crash_reporter/kernel_collector_test.h b/crash_reporter/kernel_collector_test.h
new file mode 100644
index 0000000..75ac01e
--- /dev/null
+++ b/crash_reporter/kernel_collector_test.h
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
+#define CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
+
+#include "crash-reporter/kernel_collector.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+class KernelCollectorMock : public KernelCollector {
+ public:
+ MOCK_METHOD0(DumpDirMounted, bool());
+ MOCK_METHOD0(SetUpDBus, void());
+};
+
+#endif // CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
diff --git a/crash_reporter/kernel_log_collector.sh b/crash_reporter/kernel_log_collector.sh
new file mode 100644
index 0000000..d38479e
--- /dev/null
+++ b/crash_reporter/kernel_log_collector.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Usage example: "kernel_log_collector.sh XXX YYY"
+# This script searches logs in the /var/log/messages which have the keyword XXX.
+# And only those logs which are within the last YYY seconds of the latest log
+# that has the keyword XXX are printed.
+
+# Kernel log has the possible formats:
+# 2013-06-14T16:31:40.514513-07:00 localhost kernel: [ 2.682472] MSG MSG ...
+# 2013-06-19T20:38:58.661826+00:00 localhost kernel: [ 1.668092] MSG MSG ...
+
+search_key=$1
+time_duration=$2
+msg_pattern="^[0-9-]*T[0-9:.+-]* localhost kernel"
+
+die() {
+ echo "kernel_log_collector: $*" >&2
+ exit 1
+}
+
+get_timestamp() {
+ timestamp="$(echo $1 | cut -d " " -f 1)"
+ timestamp="$(date -d "${timestamp}" +%s)" || exit $?
+ echo "${timestamp}"
+}
+
+last_line=$(grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | tail -n 1)
+
+if [ -n "${last_line}" ]; then
+ if ! allowed_timestamp=$(get_timestamp "${last_line}"); then
+ die "coule not get timestamp from: ${last_line}"
+ fi
+ : $(( allowed_timestamp -= ${time_duration} ))
+ grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | while read line; do
+ if ! timestamp=$(get_timestamp "${line}"); then
+ die "could not get timestamp from: ${line}"
+ fi
+ if [ ${timestamp} -gt ${allowed_timestamp} ]; then
+ echo "${line}"
+ fi
+ done
+fi
+
+echo "END-OF-LOG"
+
diff --git a/crash_reporter/kernel_warning_collector.cc b/crash_reporter/kernel_warning_collector.cc
new file mode 100644
index 0000000..5dcd1f6
--- /dev/null
+++ b/crash_reporter/kernel_warning_collector.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/kernel_warning_collector.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+namespace {
+const char kExecName[] = "kernel-warning";
+const char kKernelWarningSignatureKey[] = "sig";
+const char kKernelWarningPath[] = "/var/run/kwarn/warning";
+const pid_t kKernelPid = 0;
+const uid_t kRootUid = 0;
+} // namespace
+
+using base::FilePath;
+using base::StringPrintf;
+
+KernelWarningCollector::KernelWarningCollector() {
+}
+
+KernelWarningCollector::~KernelWarningCollector() {
+}
+
+bool KernelWarningCollector::LoadKernelWarning(std::string *content,
+ std::string *signature) {
+ FilePath kernel_warning_path(kKernelWarningPath);
+ if (!base::ReadFileToString(kernel_warning_path, content)) {
+ LOG(ERROR) << "Could not open " << kKernelWarningPath;
+ return false;
+ }
+ // The signature is in the first line.
+ std::string::size_type end_position = content->find('\n');
+ if (end_position == std::string::npos) {
+ LOG(ERROR) << "unexpected kernel warning format";
+ return false;
+ }
+ *signature = content->substr(0, end_position);
+ return true;
+}
+
+bool KernelWarningCollector::Collect() {
+ std::string reason = "normal collection";
+ bool feedback = true;
+ if (IsDeveloperImage()) {
+ reason = "always collect from developer builds";
+ feedback = true;
+ } else if (!is_feedback_allowed_function_()) {
+ reason = "no user consent";
+ feedback = false;
+ }
+
+ LOG(INFO) << "Processing kernel warning: " << reason;
+
+ if (!feedback) {
+ return true;
+ }
+
+ std::string kernel_warning;
+ std::string warning_signature;
+ if (!LoadKernelWarning(&kernel_warning, &warning_signature)) {
+ return true;
+ }
+
+ FilePath root_crash_directory;
+ if (!GetCreatedCrashDirectoryByEuid(kRootUid, &root_crash_directory,
+ nullptr)) {
+ return true;
+ }
+
+ std::string dump_basename =
+ FormatDumpBasename(kExecName, time(nullptr), kKernelPid);
+ FilePath kernel_crash_path = root_crash_directory.Append(
+ StringPrintf("%s.kcrash", dump_basename.c_str()));
+
+ // We must use WriteNewFile instead of base::WriteFile as we
+ // do not want to write with root access to a symlink that an attacker
+ // might have created.
+ if (WriteNewFile(kernel_crash_path,
+ kernel_warning.data(),
+ kernel_warning.length()) !=
+ static_cast<int>(kernel_warning.length())) {
+ LOG(INFO) << "Failed to write kernel warning to "
+ << kernel_crash_path.value().c_str();
+ return true;
+ }
+
+ AddCrashMetaData(kKernelWarningSignatureKey, warning_signature);
+ WriteCrashMetaData(
+ root_crash_directory.Append(
+ StringPrintf("%s.meta", dump_basename.c_str())),
+ kExecName, kernel_crash_path.value());
+
+ LOG(INFO) << "Stored kernel warning into " << kernel_crash_path.value();
+ return true;
+}
diff --git a/crash_reporter/kernel_warning_collector.h b/crash_reporter/kernel_warning_collector.h
new file mode 100644
index 0000000..f326b23
--- /dev/null
+++ b/crash_reporter/kernel_warning_collector.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
+#define CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "crash-reporter/crash_collector.h"
+
+// Kernel warning collector.
+class KernelWarningCollector : public CrashCollector {
+ public:
+ KernelWarningCollector();
+
+ ~KernelWarningCollector() override;
+
+ // Collects warning.
+ bool Collect();
+
+ private:
+ friend class KernelWarningCollectorTest;
+ FRIEND_TEST(KernelWarningCollectorTest, CollectOK);
+
+ // Reads the full content of the kernel warn dump and its signature.
+ bool LoadKernelWarning(std::string *content, std::string *signature);
+
+ DISALLOW_COPY_AND_ASSIGN(KernelWarningCollector);
+};
+
+#endif // CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
diff --git a/crash_reporter/list_proxies.cc b/crash_reporter/list_proxies.cc
new file mode 100644
index 0000000..de6ef0a
--- /dev/null
+++ b/crash_reporter/list_proxies.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sysexits.h>
+#include <unistd.h> // for isatty()
+
+#include <string>
+#include <vector>
+
+#include <base/cancelable_callback.h>
+#include <base/command_line.h>
+#include <base/files/file_util.h>
+#include <base/memory/weak_ptr.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <chromeos/syslog_logging.h>
+
+#include "libcrosservice/dbus-proxies.h"
+
+using std::unique_ptr;
+
+namespace {
+
+const char kLibCrosProxyResolvedSignalInterface[] =
+ "org.chromium.CrashReporterLibcrosProxyResolvedInterface";
+const char kLibCrosProxyResolvedName[] = "ProxyResolved";
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+const char kNoProxy[] = "direct://";
+
+const int kTimeoutDefaultSeconds = 5;
+
+const char kHelp[] = "help";
+const char kQuiet[] = "quiet";
+const char kTimeout[] = "timeout";
+const char kVerbose[] = "verbose";
+// Help message to show when the --help command line switch is specified.
+const char kHelpMessage[] =
+ "Chromium OS Crash helper: proxy lister\n"
+ "\n"
+ "Available Switches:\n"
+ " --quiet Only print the proxies\n"
+ " --verbose Print additional messages even when not run from a TTY\n"
+ " --timeout=N Set timeout for browser resolving proxies (default is 5)\n"
+ " --help Show this help.\n";
+
+// Copied from src/update_engine/chrome_browser_proxy_resolver.cc
+// Parses the browser's answer for resolved proxies. It returns a
+// list of strings, each of which is a resolved proxy.
+std::vector<std::string> ParseProxyString(const std::string& input) {
+ std::vector<std::string> ret;
+ // Some of this code taken from
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
+ base::StringTokenizer entry_tok(input, ";");
+ while (entry_tok.GetNext()) {
+ std::string token = entry_tok.token();
+ base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
+
+ // Start by finding the first space (if any).
+ std::string::iterator space;
+ for (space = token.begin(); space != token.end(); ++space) {
+ if (IsAsciiWhitespace(*space)) {
+ break;
+ }
+ }
+
+ std::string scheme = std::string(token.begin(), space);
+ base::StringToLowerASCII(&scheme);
+ // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
+ if (scheme == "socks")
+ scheme += "4";
+ else if (scheme == "proxy")
+ scheme = "http";
+ else if (scheme != "https" &&
+ scheme != "socks4" &&
+ scheme != "socks5" &&
+ scheme != "direct")
+ continue; // Invalid proxy scheme
+
+ std::string host_and_port = std::string(space, token.end());
+ base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
+ if (scheme != "direct" && host_and_port.empty())
+ continue; // Must supply host/port when non-direct proxy used.
+ ret.push_back(scheme + "://" + host_and_port);
+ }
+ if (ret.empty() || *ret.rbegin() != kNoProxy)
+ ret.push_back(kNoProxy);
+ return ret;
+}
+
+// A class for interfacing with Chrome to resolve proxies for a given source
+// url. The class is initialized with the given source url to check, the
+// signal interface and name that Chrome will reply to, and how long to wait
+// for the resolve request to timeout. Once initialized, the Run() function
+// must be called, which blocks on the D-Bus call to Chrome. The call returns
+// after either the timeout or the proxy has been resolved. The resolved
+// proxies can then be accessed through the proxies() function.
+class ProxyResolver : public chromeos::DBusDaemon {
+ public:
+ ProxyResolver(const std::string& source_url,
+ const std::string& signal_interface,
+ const std::string& signal_name,
+ base::TimeDelta timeout)
+ : source_url_(source_url),
+ signal_interface_(signal_interface),
+ signal_name_(signal_name),
+ timeout_(timeout),
+ weak_ptr_factory_(this),
+ timeout_callback_(base::Bind(&ProxyResolver::HandleBrowserTimeout,
+ weak_ptr_factory_.GetWeakPtr())) {}
+
+ ~ProxyResolver() override {}
+
+ const std::vector<std::string>& proxies() {
+ return proxies_;
+ }
+
+ int Run() override {
+ // Add task for if the browser proxy call times out.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ timeout_callback_.callback(),
+ timeout_);
+
+ return chromeos::DBusDaemon::Run();
+ }
+
+ protected:
+ // If the browser times out, quit the run loop.
+ void HandleBrowserTimeout() {
+ LOG(ERROR) << "Timeout while waiting for browser to resolve proxy";
+ Quit();
+ }
+
+ // If the signal handler connects successfully, call the browser's
+ // ResolveNetworkProxy D-Bus method. Otherwise, don't do anything and let
+ // the timeout task quit the run loop.
+ void HandleDBusSignalConnected(const std::string& interface,
+ const std::string& signal,
+ bool success) {
+ if (!success) {
+ LOG(ERROR) << "Could not connect to signal " << interface << "."
+ << signal;
+ timeout_callback_.Cancel();
+ Quit();
+ return;
+ }
+
+ chromeos::ErrorPtr error;
+ call_proxy_->ResolveNetworkProxy(source_url_,
+ signal_interface_,
+ signal_name_,
+ &error);
+
+ if (error) {
+ LOG(ERROR) << "Call to ResolveNetworkProxy failed: "
+ << error->GetMessage();
+ timeout_callback_.Cancel();
+ Quit();
+ }
+ }
+
+ // Handle incoming ProxyResolved signal.
+ void HandleProxyResolvedSignal(const std::string& source_url,
+ const std::string& proxy_info,
+ const std::string& error_message) {
+ timeout_callback_.Cancel();
+ proxies_ = ParseProxyString(proxy_info);
+ LOG(INFO) << "Found proxies via browser signal: "
+ << JoinString(proxies_, 'x');
+
+ Quit();
+ }
+
+ int OnInit() override {
+ int return_code = chromeos::DBusDaemon::OnInit();
+ if (return_code != EX_OK)
+ return return_code;
+
+ // Initialize D-Bus proxies.
+ call_proxy_.reset(
+ new org::chromium::LibCrosServiceInterfaceProxy(bus_,
+ kLibCrosServiceName));
+ signal_proxy_.reset(
+ new org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy(
+ bus_,
+ kLibCrosServiceName));
+
+ // Set up the D-Bus signal handler.
+ // TODO(crbug.com/446115): Update ResolveNetworkProxy call to use an
+ // asynchronous return value rather than a return signal.
+ signal_proxy_->RegisterProxyResolvedSignalHandler(
+ base::Bind(&ProxyResolver::HandleProxyResolvedSignal,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&ProxyResolver::HandleDBusSignalConnected,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ return EX_OK;
+ }
+
+ private:
+ unique_ptr<org::chromium::LibCrosServiceInterfaceProxy> call_proxy_;
+ unique_ptr<org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy>
+ signal_proxy_;
+
+ const std::string source_url_;
+ const std::string signal_interface_;
+ const std::string signal_name_;
+ base::TimeDelta timeout_;
+
+ std::vector<std::string> proxies_;
+ base::WeakPtrFactory<ProxyResolver> weak_ptr_factory_;
+
+ base::CancelableClosure timeout_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
+};
+
+static bool ShowBrowserProxies(std::string url, base::TimeDelta timeout) {
+ // Initialize and run the proxy resolver to watch for signals.
+ ProxyResolver resolver(url,
+ kLibCrosProxyResolvedSignalInterface,
+ kLibCrosProxyResolvedName,
+ timeout);
+ resolver.Run();
+
+ std::vector<std::string> proxies = resolver.proxies();
+
+ // If proxies is empty, then the timeout was reached waiting for the proxy
+ // resolved signal. If no proxies are defined, proxies will be populated
+ // with "direct://".
+ if (proxies.empty())
+ return false;
+
+ for (const auto& proxy : proxies) {
+ printf("%s\n", proxy.c_str());
+ }
+ return true;
+}
+
+} // namespace
+
+int main(int argc, char *argv[]) {
+ base::CommandLine::Init(argc, argv);
+ base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
+
+ if (cl->HasSwitch(kHelp)) {
+ LOG(INFO) << kHelpMessage;
+ return 0;
+ }
+
+ bool quiet = cl->HasSwitch(kQuiet);
+ bool verbose = cl->HasSwitch(kVerbose);
+
+ int timeout = kTimeoutDefaultSeconds;
+ std::string str_timeout = cl->GetSwitchValueASCII(kTimeout);
+ if (!str_timeout.empty() && !base::StringToInt(str_timeout, &timeout)) {
+ LOG(ERROR) << "Invalid timeout value: " << str_timeout;
+ return 1;
+ }
+
+ // Default to logging to syslog.
+ int init_flags = chromeos::kLogToSyslog;
+ // Log to stderr if a TTY (and "-quiet" wasn't passed), or if "-verbose"
+ // was passed.
+
+ if ((!quiet && isatty(STDERR_FILENO)) || verbose)
+ init_flags |= chromeos::kLogToStderr;
+ chromeos::InitLog(init_flags);
+
+ std::string url;
+ base::CommandLine::StringVector urls = cl->GetArgs();
+ if (!urls.empty()) {
+ url = urls[0];
+ LOG(INFO) << "Resolving proxies for URL: " << url;
+ } else {
+ LOG(INFO) << "Resolving proxies without URL";
+ }
+
+ if (!ShowBrowserProxies(url, base::TimeDelta::FromSeconds(timeout))) {
+ LOG(ERROR) << "Error resolving proxies via the browser";
+ LOG(INFO) << "Assuming direct proxy";
+ printf("%s\n", kNoProxy);
+ }
+
+ return 0;
+}
diff --git a/crash_reporter/testrunner.cc b/crash_reporter/testrunner.cc
new file mode 100644
index 0000000..d45bbf8
--- /dev/null
+++ b/crash_reporter/testrunner.cc
@@ -0,0 +1,11 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <chromeos/test_helpers.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+ SetUpTests(&argc, argv, true);
+ return RUN_ALL_TESTS();
+}
diff --git a/crash_reporter/udev_collector.cc b/crash_reporter/udev_collector.cc
new file mode 100644
index 0000000..908bbc9
--- /dev/null
+++ b/crash_reporter/udev_collector.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/udev_collector.h"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/process.h>
+
+using base::FilePath;
+
+namespace {
+
+const char kCollectUdevSignature[] = "crash_reporter-udev-collection";
+const char kGzipPath[] = "/bin/gzip";
+const char kUdevExecName[] = "udev";
+const char kUdevSignatureKey[] = "sig";
+const char kUdevSubsystemDevCoredump[] = "devcoredump";
+const char kDefaultDevCoredumpDirectory[] = "/sys/class/devcoredump";
+const char kDevCoredumpFilePrefixFormat[] = "devcoredump_%s";
+
+} // namespace
+
+UdevCollector::UdevCollector()
+ : dev_coredump_directory_(kDefaultDevCoredumpDirectory) {}
+
+UdevCollector::~UdevCollector() {}
+
+bool UdevCollector::HandleCrash(const std::string &udev_event) {
+ if (IsDeveloperImage()) {
+ LOG(INFO) << "developer image - collect udev crash info.";
+ } else if (is_feedback_allowed_function_()) {
+ LOG(INFO) << "Consent given - collect udev crash info.";
+ } else {
+ LOG(INFO) << "Ignoring - Non-developer image and no consent given.";
+ return false;
+ }
+
+ // Process the udev event string.
+ // First get all the key-value pairs.
+ std::vector<std::pair<std::string, std::string>> udev_event_keyval;
+ base::SplitStringIntoKeyValuePairs(udev_event, '=', ':', &udev_event_keyval);
+ std::vector<std::pair<std::string, std::string>>::const_iterator iter;
+ std::map<std::string, std::string> udev_event_map;
+ for (iter = udev_event_keyval.begin();
+ iter != udev_event_keyval.end();
+ ++iter) {
+ udev_event_map[iter->first] = iter->second;
+ }
+
+ // Make sure the crash directory exists, or create it if it doesn't.
+ FilePath crash_directory;
+ if (!GetCreatedCrashDirectoryByEuid(0, &crash_directory, nullptr)) {
+ LOG(ERROR) << "Could not get crash directory.";
+ return false;
+ }
+
+ if (udev_event_map["SUBSYSTEM"] == kUdevSubsystemDevCoredump) {
+ int instance_number;
+ if (!base::StringToInt(udev_event_map["KERNEL_NUMBER"], &instance_number)) {
+ LOG(ERROR) << "Invalid kernel number: "
+ << udev_event_map["KERNEL_NUMBER"];
+ return false;
+ }
+ return ProcessDevCoredump(crash_directory, instance_number);
+ }
+
+ return ProcessUdevCrashLogs(crash_directory,
+ udev_event_map["ACTION"],
+ udev_event_map["KERNEL"],
+ udev_event_map["SUBSYSTEM"]);
+}
+
+bool UdevCollector::ProcessUdevCrashLogs(const FilePath& crash_directory,
+ const std::string& action,
+ const std::string& kernel,
+ const std::string& subsystem) {
+ // Construct the basename string for crash_reporter_logs.conf:
+ // "crash_reporter-udev-collection-[action]-[name]-[subsystem]"
+ // If a udev field is not provided, "" is used in its place, e.g.:
+ // "crash_reporter-udev-collection-[action]--[subsystem]"
+ // Hence, "" is used as a wildcard name string.
+ // TODO(sque, crosbug.com/32238): Implement wildcard checking.
+ std::string basename = action + "-" + kernel + "-" + subsystem;
+ std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
+ basename;
+
+ // Create the destination path.
+ std::string log_file_name =
+ FormatDumpBasename(basename, time(nullptr), 0);
+ FilePath crash_path = GetCrashPath(crash_directory, log_file_name, "log");
+
+ // Handle the crash.
+ bool result = GetLogContents(log_config_path_, udev_log_name, crash_path);
+ if (!result) {
+ LOG(ERROR) << "Error reading udev log info " << udev_log_name;
+ return false;
+ }
+
+ // Compress the output using gzip.
+ chromeos::ProcessImpl gzip_process;
+ gzip_process.AddArg(kGzipPath);
+ gzip_process.AddArg(crash_path.value());
+ int process_result = gzip_process.Run();
+ FilePath crash_path_zipped = FilePath(crash_path.value() + ".gz");
+ // If the zip file was not created, use the uncompressed file.
+ if (process_result != 0 || !base::PathExists(crash_path_zipped))
+ LOG(ERROR) << "Could not create zip file " << crash_path_zipped.value();
+ else
+ crash_path = crash_path_zipped;
+
+ std::string exec_name = std::string(kUdevExecName) + "-" + subsystem;
+ AddCrashMetaData(kUdevSignatureKey, udev_log_name);
+ WriteCrashMetaData(GetCrashPath(crash_directory, log_file_name, "meta"),
+ exec_name, crash_path.value());
+ return true;
+}
+
+bool UdevCollector::ProcessDevCoredump(const FilePath& crash_directory,
+ int instance_number) {
+ FilePath coredump_path =
+ FilePath(base::StringPrintf("%s/devcd%d/data",
+ dev_coredump_directory_.c_str(),
+ instance_number));
+ if (!base::PathExists(coredump_path)) {
+ LOG(ERROR) << "Device coredump file " << coredump_path.value()
+ << " does not exist";
+ return false;
+ }
+
+ // Add coredump file to the crash directory.
+ if (!AppendDevCoredump(crash_directory, coredump_path, instance_number)) {
+ ClearDevCoredump(coredump_path);
+ return false;
+ }
+
+ // Clear the coredump data to allow generation of future device coredumps
+ // without having to wait for the 5-minutes timeout.
+ return ClearDevCoredump(coredump_path);
+}
+
+bool UdevCollector::AppendDevCoredump(const FilePath& crash_directory,
+ const FilePath& coredump_path,
+ int instance_number) {
+ // Retrieve the driver name of the failing device.
+ std::string driver_name = GetFailingDeviceDriverName(instance_number);
+ if (driver_name.empty()) {
+ LOG(ERROR) << "Failed to obtain driver name for instance: "
+ << instance_number;
+ return false;
+ }
+
+ std::string coredump_prefix =
+ base::StringPrintf(kDevCoredumpFilePrefixFormat, driver_name.c_str());
+
+ std::string dump_basename = FormatDumpBasename(coredump_prefix,
+ time(nullptr),
+ instance_number);
+ FilePath core_path = GetCrashPath(crash_directory, dump_basename, "devcore");
+ FilePath log_path = GetCrashPath(crash_directory, dump_basename, "log");
+ FilePath meta_path = GetCrashPath(crash_directory, dump_basename, "meta");
+
+ // Collect coredump data.
+ if (!base::CopyFile(coredump_path, core_path)) {
+ LOG(ERROR) << "Failed to copy device coredumpm file from "
+ << coredump_path.value() << " to " << core_path.value();
+ return false;
+ }
+
+ // Collect additional logs if one is specified in the config file.
+ std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
+ kUdevSubsystemDevCoredump + '-' + driver_name;
+ bool result = GetLogContents(log_config_path_, udev_log_name, log_path);
+ if (result) {
+ AddCrashMetaUploadFile("logs", log_path.value());
+ }
+
+ WriteCrashMetaData(meta_path, coredump_prefix, core_path.value());
+
+ return true;
+}
+
+bool UdevCollector::ClearDevCoredump(const FilePath& coredump_path) {
+ if (!base::WriteFile(coredump_path, "0", 1)) {
+ LOG(ERROR) << "Failed to delete the coredump data file "
+ << coredump_path.value();
+ return false;
+ }
+ return true;
+}
+
+std::string UdevCollector::GetFailingDeviceDriverName(int instance_number) {
+ FilePath failing_uevent_path =
+ FilePath(base::StringPrintf("%s/devcd%d/failing_device/uevent",
+ dev_coredump_directory_.c_str(),
+ instance_number));
+ if (!base::PathExists(failing_uevent_path)) {
+ LOG(ERROR) << "Failing uevent path " << failing_uevent_path.value()
+ << " does not exist";
+ return "";
+ }
+
+ std::string uevent_content;
+ if (!base::ReadFileToString(failing_uevent_path, &uevent_content)) {
+ LOG(ERROR) << "Failed to read uevent file " << failing_uevent_path.value();
+ return "";
+ }
+
+ // Parse uevent file contents as key-value pairs.
+ std::vector<std::pair<std::string, std::string>> uevent_keyval;
+ base::SplitStringIntoKeyValuePairs(uevent_content, '=', '\n', &uevent_keyval);
+ std::vector<std::pair<std::string, std::string>>::const_iterator iter;
+ for (iter = uevent_keyval.begin();
+ iter != uevent_keyval.end();
+ ++iter) {
+ if (iter->first == "DRIVER") {
+ return iter->second;
+ }
+ }
+
+ return "";
+}
diff --git a/crash_reporter/udev_collector.h b/crash_reporter/udev_collector.h
new file mode 100644
index 0000000..1689dd3
--- /dev/null
+++ b/crash_reporter/udev_collector.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_UDEV_COLLECTOR_H_
+#define CRASH_REPORTER_UDEV_COLLECTOR_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "crash-reporter/crash_collector.h"
+
+// Udev crash collector.
+class UdevCollector : public CrashCollector {
+ public:
+ UdevCollector();
+
+ ~UdevCollector() override;
+
+ // The udev event string should be formatted as follows:
+ // "ACTION=[action]:KERNEL=[name]:SUBSYSTEM=[subsystem]"
+ // The values don't have to be in any particular order. One or more of them
+ // could be omitted, in which case it would be treated as a wildcard (*).
+ bool HandleCrash(const std::string& udev_event);
+
+ protected:
+ std::string dev_coredump_directory_;
+
+ private:
+ friend class UdevCollectorTest;
+
+ // Process udev crash logs, collecting log files according to the config
+ // file (crash_reporter_logs.conf).
+ bool ProcessUdevCrashLogs(const base::FilePath& crash_directory,
+ const std::string& action,
+ const std::string& kernel,
+ const std::string& subsystem);
+ // Process device coredump, collecting device coredump file.
+ // |instance_number| is the kernel number of the virtual device for the device
+ // coredump instance.
+ bool ProcessDevCoredump(const base::FilePath& crash_directory,
+ int instance_number);
+ // Copy device coredump file to crash directory, and perform necessary
+ // coredump file management.
+ bool AppendDevCoredump(const base::FilePath& crash_directory,
+ const base::FilePath& coredump_path,
+ int instance_number);
+ // Clear the device coredump file by performing a dummy write to it.
+ bool ClearDevCoredump(const base::FilePath& coredump_path);
+ // Return the driver name of the device that generates the coredump.
+ std::string GetFailingDeviceDriverName(int instance_number);
+
+ // Mutator for unit testing.
+ void set_log_config_path(const std::string& path) {
+ log_config_path_ = base::FilePath(path);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(UdevCollector);
+};
+
+#endif // CRASH_REPORTER_UDEV_COLLECTOR_H_
diff --git a/crash_reporter/udev_collector_test.cc b/crash_reporter/udev_collector_test.cc
new file mode 100644
index 0000000..08d9b2c
--- /dev/null
+++ b/crash_reporter/udev_collector_test.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/syslog_logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "crash-reporter/udev_collector.h"
+
+using base::FilePath;
+
+namespace {
+
+// Dummy log config file name.
+const char kLogConfigFileName[] = "log_config_file";
+
+// Dummy directory for storing device coredumps.
+const char kDevCoredumpDirectory[] = "devcoredump";
+
+// A bunch of random rules to put into the dummy log config file.
+const char kLogConfigFileContents[] =
+ "crash_reporter-udev-collection-change-card0-drm=echo change card0 drm\n"
+ "crash_reporter-udev-collection-add-state0-cpu=echo change state0 cpu\n"
+ "crash_reporter-udev-collection-devcoredump-iwlwifi=echo devcoredump\n"
+ "cros_installer=echo not for udev";
+
+const char kCrashLogFilePattern[] = "*.log.gz";
+const char kDevCoredumpFilePattern[] = "*.devcore";
+
+// Dummy content for device coredump data file.
+const char kDevCoredumpDataContents[] = "coredump";
+
+// Content for failing device's uevent file.
+const char kFailingDeviceUeventContents[] = "DRIVER=iwlwifi\n";
+
+void CountCrash() {}
+
+bool s_consent_given = true;
+
+bool IsMetrics() {
+ return s_consent_given;
+}
+
+// Returns the number of files found in the given path that matches the
+// specified file name pattern.
+int GetNumFiles(const FilePath& path, const std::string& file_pattern) {
+ base::FileEnumerator enumerator(path, false, base::FileEnumerator::FILES,
+ file_pattern);
+ int num_files = 0;
+ for (FilePath file_path = enumerator.Next();
+ !file_path.value().empty();
+ file_path = enumerator.Next()) {
+ num_files++;
+ }
+ return num_files;
+}
+
+} // namespace
+
+class UdevCollectorMock : public UdevCollector {
+ public:
+ MOCK_METHOD0(SetUpDBus, void());
+};
+
+class UdevCollectorTest : public ::testing::Test {
+ protected:
+ base::ScopedTempDir temp_dir_generator_;
+
+ void HandleCrash(const std::string &udev_event) {
+ collector_.HandleCrash(udev_event);
+ }
+
+ void GenerateDevCoredump(const std::string& device_name) {
+ // Generate coredump data file.
+ ASSERT_TRUE(CreateDirectory(
+ FilePath(base::StringPrintf("%s/%s",
+ collector_.dev_coredump_directory_.c_str(),
+ device_name.c_str()))));
+ FilePath data_path =
+ FilePath(base::StringPrintf("%s/%s/data",
+ collector_.dev_coredump_directory_.c_str(),
+ device_name.c_str()));
+ ASSERT_EQ(strlen(kDevCoredumpDataContents),
+ base::WriteFile(data_path,
+ kDevCoredumpDataContents,
+ strlen(kDevCoredumpDataContents)));
+ // Generate uevent file for failing device.
+ ASSERT_TRUE(CreateDirectory(
+ FilePath(base::StringPrintf("%s/%s/failing_device",
+ collector_.dev_coredump_directory_.c_str(),
+ device_name.c_str()))));
+ FilePath uevent_path =
+ FilePath(base::StringPrintf("%s/%s/failing_device/uevent",
+ collector_.dev_coredump_directory_.c_str(),
+ device_name.c_str()));
+ ASSERT_EQ(strlen(kFailingDeviceUeventContents),
+ base::WriteFile(uevent_path,
+ kFailingDeviceUeventContents,
+ strlen(kFailingDeviceUeventContents)));
+ }
+
+ private:
+ void SetUp() override {
+ s_consent_given = true;
+
+ EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+ collector_.Initialize(CountCrash, IsMetrics);
+
+ ASSERT_TRUE(temp_dir_generator_.CreateUniqueTempDir());
+
+ FilePath log_config_path =
+ temp_dir_generator_.path().Append(kLogConfigFileName);
+ collector_.log_config_path_ = log_config_path;
+ collector_.ForceCrashDirectory(temp_dir_generator_.path());
+
+ FilePath dev_coredump_path =
+ temp_dir_generator_.path().Append(kDevCoredumpDirectory);
+ collector_.dev_coredump_directory_ = dev_coredump_path.value();
+
+ // Write to a dummy log config file.
+ ASSERT_EQ(strlen(kLogConfigFileContents),
+ base::WriteFile(log_config_path,
+ kLogConfigFileContents,
+ strlen(kLogConfigFileContents)));
+
+ chromeos::ClearLog();
+ }
+
+ UdevCollectorMock collector_;
+};
+
+TEST_F(UdevCollectorTest, TestNoConsent) {
+ s_consent_given = false;
+ HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
+ EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+}
+
+TEST_F(UdevCollectorTest, TestNoMatch) {
+ // No rule should match this.
+ HandleCrash("ACTION=change:KERNEL=foo:SUBSYSTEM=bar");
+ EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+}
+
+TEST_F(UdevCollectorTest, TestMatches) {
+ // Try multiple udev events in sequence. The number of log files generated
+ // should increase.
+ HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
+ EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+ HandleCrash("ACTION=add:KERNEL=state0:SUBSYSTEM=cpu");
+ EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
+}
+
+TEST_F(UdevCollectorTest, TestDevCoredump) {
+ GenerateDevCoredump("devcd0");
+ HandleCrash("ACTION=add:KERNEL_NUMBER=0:SUBSYSTEM=devcoredump");
+ EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(),
+ kDevCoredumpFilePattern));
+ GenerateDevCoredump("devcd1");
+ HandleCrash("ACTION=add:KERNEL_NUMBER=1:SUBSYSTEM=devcoredump");
+ EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(),
+ kDevCoredumpFilePattern));
+}
+
+// TODO(sque, crosbug.com/32238) - test wildcard cases, multiple identical udev
+// events.
diff --git a/crash_reporter/unclean_shutdown_collector.cc b/crash_reporter/unclean_shutdown_collector.cc
new file mode 100644
index 0000000..e8273b5
--- /dev/null
+++ b/crash_reporter/unclean_shutdown_collector.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/unclean_shutdown_collector.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+
+static const char kUncleanShutdownFile[] =
+ "/var/lib/crash_reporter/pending_clean_shutdown";
+
+// Files created by power manager used for crash reporting.
+static const char kPowerdTracePath[] = "/var/lib/power_manager";
+// Presence of this file indicates that the system was suspended
+static const char kPowerdSuspended[] = "powerd_suspended";
+
+using base::FilePath;
+
+UncleanShutdownCollector::UncleanShutdownCollector()
+ : unclean_shutdown_file_(kUncleanShutdownFile),
+ powerd_trace_path_(kPowerdTracePath),
+ powerd_suspended_file_(powerd_trace_path_.Append(kPowerdSuspended)) {
+}
+
+UncleanShutdownCollector::~UncleanShutdownCollector() {
+}
+
+bool UncleanShutdownCollector::Enable() {
+ FilePath file_path(unclean_shutdown_file_);
+ base::CreateDirectory(file_path.DirName());
+ if (base::WriteFile(file_path, "", 0) != 0) {
+ LOG(ERROR) << "Unable to create shutdown check file";
+ return false;
+ }
+ return true;
+}
+
+bool UncleanShutdownCollector::DeleteUncleanShutdownFiles() {
+ if (!base::DeleteFile(FilePath(unclean_shutdown_file_), false)) {
+ LOG(ERROR) << "Failed to delete unclean shutdown file "
+ << unclean_shutdown_file_;
+ return false;
+ }
+ // Delete power manager state file if it exists.
+ base::DeleteFile(powerd_suspended_file_, false);
+ return true;
+}
+
+bool UncleanShutdownCollector::Collect() {
+ FilePath unclean_file_path(unclean_shutdown_file_);
+ if (!base::PathExists(unclean_file_path)) {
+ return false;
+ }
+ LOG(WARNING) << "Last shutdown was not clean";
+ if (DeadBatteryCausedUncleanShutdown()) {
+ DeleteUncleanShutdownFiles();
+ return false;
+ }
+ DeleteUncleanShutdownFiles();
+
+ if (is_feedback_allowed_function_()) {
+ count_crash_function_();
+ }
+ return true;
+}
+
+bool UncleanShutdownCollector::Disable() {
+ LOG(INFO) << "Clean shutdown signalled";
+ return DeleteUncleanShutdownFiles();
+}
+
+bool UncleanShutdownCollector::DeadBatteryCausedUncleanShutdown() {
+ // Check for case of battery running out while suspended.
+ if (base::PathExists(powerd_suspended_file_)) {
+ LOG(INFO) << "Unclean shutdown occurred while suspended. Not counting "
+ << "toward unclean shutdown statistic.";
+ return true;
+ }
+ return false;
+}
diff --git a/crash_reporter/unclean_shutdown_collector.h b/crash_reporter/unclean_shutdown_collector.h
new file mode 100644
index 0000000..d30a0b2
--- /dev/null
+++ b/crash_reporter/unclean_shutdown_collector.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
+#define CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "crash-reporter/crash_collector.h"
+
+// Unclean shutdown collector.
+class UncleanShutdownCollector : public CrashCollector {
+ public:
+ UncleanShutdownCollector();
+ ~UncleanShutdownCollector() override;
+
+ // Enable collection - signal that a boot has started.
+ bool Enable();
+
+ // Collect if there is was an unclean shutdown. Returns true if
+ // there was, false otherwise.
+ bool Collect();
+
+ // Disable collection - signal that the system has been shutdown cleanly.
+ bool Disable();
+
+ private:
+ friend class UncleanShutdownCollectorTest;
+ FRIEND_TEST(UncleanShutdownCollectorTest, EnableCannotWrite);
+ FRIEND_TEST(UncleanShutdownCollectorTest, CollectDeadBatterySuspended);
+
+ bool DeleteUncleanShutdownFiles();
+
+ // Check for unclean shutdown due to battery running out by analyzing powerd
+ // trace files.
+ bool DeadBatteryCausedUncleanShutdown();
+
+ const char *unclean_shutdown_file_;
+ base::FilePath powerd_trace_path_;
+ base::FilePath powerd_suspended_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(UncleanShutdownCollector);
+};
+
+#endif // CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
diff --git a/crash_reporter/unclean_shutdown_collector_test.cc b/crash_reporter/unclean_shutdown_collector_test.cc
new file mode 100644
index 0000000..f5e1b32
--- /dev/null
+++ b/crash_reporter/unclean_shutdown_collector_test.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/unclean_shutdown_collector.h"
+
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <chromeos/syslog_logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using ::chromeos::FindLog;
+
+namespace {
+
+int s_crashes = 0;
+bool s_metrics = true;
+
+const char kTestDirectory[] = "test";
+const char kTestSuspended[] = "test/suspended";
+const char kTestUnclean[] = "test/unclean";
+
+void CountCrash() {
+ ++s_crashes;
+}
+
+bool IsMetrics() {
+ return s_metrics;
+}
+
+} // namespace
+
+class UncleanShutdownCollectorMock : public UncleanShutdownCollector {
+ public:
+ MOCK_METHOD0(SetUpDBus, void());
+};
+
+class UncleanShutdownCollectorTest : public ::testing::Test {
+ void SetUp() {
+ s_crashes = 0;
+
+ EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+ collector_.Initialize(CountCrash,
+ IsMetrics);
+ rmdir(kTestDirectory);
+ test_unclean_ = FilePath(kTestUnclean);
+ collector_.unclean_shutdown_file_ = kTestUnclean;
+ base::DeleteFile(test_unclean_, true);
+ // Set up an alternate power manager state file as well
+ collector_.powerd_suspended_file_ = FilePath(kTestSuspended);
+ chromeos::ClearLog();
+ }
+
+ protected:
+ void WriteStringToFile(const FilePath &file_path,
+ const char *data) {
+ ASSERT_EQ(strlen(data), base::WriteFile(file_path, data, strlen(data)));
+ }
+
+ UncleanShutdownCollectorMock collector_;
+ FilePath test_unclean_;
+};
+
+TEST_F(UncleanShutdownCollectorTest, EnableWithoutParent) {
+ ASSERT_TRUE(collector_.Enable());
+ ASSERT_TRUE(base::PathExists(test_unclean_));
+}
+
+TEST_F(UncleanShutdownCollectorTest, EnableWithParent) {
+ mkdir(kTestDirectory, 0777);
+ ASSERT_TRUE(collector_.Enable());
+ ASSERT_TRUE(base::PathExists(test_unclean_));
+}
+
+TEST_F(UncleanShutdownCollectorTest, EnableCannotWrite) {
+ collector_.unclean_shutdown_file_ = "/bad/path";
+ ASSERT_FALSE(collector_.Enable());
+ ASSERT_TRUE(FindLog("Unable to create shutdown check file"));
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectTrue) {
+ ASSERT_TRUE(collector_.Enable());
+ ASSERT_TRUE(base::PathExists(test_unclean_));
+ ASSERT_TRUE(collector_.Collect());
+ ASSERT_FALSE(base::PathExists(test_unclean_));
+ ASSERT_EQ(1, s_crashes);
+ ASSERT_TRUE(FindLog("Last shutdown was not clean"));
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectFalse) {
+ ASSERT_FALSE(collector_.Collect());
+ ASSERT_EQ(0, s_crashes);
+}
+
+TEST_F(UncleanShutdownCollectorTest, CollectDeadBatterySuspended) {
+ ASSERT_TRUE(collector_.Enable());
+ ASSERT_TRUE(base::PathExists(test_unclean_));
+ base::WriteFile(collector_.powerd_suspended_file_, "", 0);
+ ASSERT_FALSE(collector_.Collect());
+ ASSERT_FALSE(base::PathExists(test_unclean_));
+ ASSERT_FALSE(base::PathExists(collector_.powerd_suspended_file_));
+ ASSERT_EQ(0, s_crashes);
+ ASSERT_TRUE(FindLog("Unclean shutdown occurred while suspended."));
+}
+
+TEST_F(UncleanShutdownCollectorTest, Disable) {
+ ASSERT_TRUE(collector_.Enable());
+ ASSERT_TRUE(base::PathExists(test_unclean_));
+ ASSERT_TRUE(collector_.Disable());
+ ASSERT_FALSE(base::PathExists(test_unclean_));
+ ASSERT_FALSE(collector_.Collect());
+}
+
+TEST_F(UncleanShutdownCollectorTest, DisableWhenNotEnabled) {
+ ASSERT_TRUE(collector_.Disable());
+}
+
+TEST_F(UncleanShutdownCollectorTest, CantDisable) {
+ mkdir(kTestDirectory, 0700);
+ if (mkdir(kTestUnclean, 0700)) {
+ ASSERT_EQ(EEXIST, errno)
+ << "Error while creating directory '" << kTestUnclean
+ << "': " << strerror(errno);
+ }
+ ASSERT_EQ(0, base::WriteFile(test_unclean_.Append("foo"), "", 0))
+ << "Error while creating empty file '"
+ << test_unclean_.Append("foo").value() << "': " << strerror(errno);
+ ASSERT_FALSE(collector_.Disable());
+ rmdir(kTestUnclean);
+}
diff --git a/crash_reporter/user_collector.cc b/crash_reporter/user_collector.cc
new file mode 100644
index 0000000..302b130
--- /dev/null
+++ b/crash_reporter/user_collector.cc
@@ -0,0 +1,673 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/user_collector.h"
+
+#include <bits/wordsize.h>
+#include <elf.h>
+#include <fcntl.h>
+#include <grp.h> // For struct group.
+#include <pcrecpp.h>
+#include <pwd.h> // For struct passwd.
+#include <stdint.h>
+#include <sys/types.h> // For getpwuid_r, getgrnam_r, WEXITSTATUS.
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/stl_util.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/process.h>
+#include <chromeos/syslog_logging.h>
+
+static const char kCollectionErrorSignature[] =
+ "crash_reporter-user-collection";
+// This procfs file is used to cause kernel core file writing to
+// instead pipe the core file into a user space process. See
+// core(5) man page.
+static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern";
+static const char kCorePipeLimitFile[] = "/proc/sys/kernel/core_pipe_limit";
+// Set core_pipe_limit to 4 so that we can catch a few unrelated concurrent
+// crashes, but finite to avoid infinitely recursing on crash handling.
+static const char kCorePipeLimit[] = "4";
+static const char kCoreToMinidumpConverterPath[] = "/usr/bin/core2md";
+
+static const char kStatePrefix[] = "State:\t";
+
+// Define an otherwise invalid value that represents an unknown UID.
+static const uid_t kUnknownUid = -1;
+
+const char *UserCollector::kUserId = "Uid:\t";
+const char *UserCollector::kGroupId = "Gid:\t";
+
+using base::FilePath;
+using base::StringPrintf;
+
+UserCollector::UserCollector()
+ : generate_diagnostics_(false),
+ core_pattern_file_(kCorePatternFile),
+ core_pipe_limit_file_(kCorePipeLimitFile),
+ initialized_(false) {
+}
+
+void UserCollector::Initialize(
+ UserCollector::CountCrashFunction count_crash_function,
+ const std::string &our_path,
+ UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
+ bool generate_diagnostics,
+ bool core2md_failure,
+ bool directory_failure,
+ const std::string &filter_in) {
+ CrashCollector::Initialize(count_crash_function,
+ is_feedback_allowed_function);
+ our_path_ = our_path;
+ initialized_ = true;
+ generate_diagnostics_ = generate_diagnostics;
+ core2md_failure_ = core2md_failure;
+ directory_failure_ = directory_failure;
+ filter_in_ = filter_in;
+}
+
+UserCollector::~UserCollector() {
+}
+
+std::string UserCollector::GetErrorTypeSignature(ErrorType error_type) const {
+ switch (error_type) {
+ case kErrorSystemIssue:
+ return "system-issue";
+ case kErrorReadCoreData:
+ return "read-core-data";
+ case kErrorUnusableProcFiles:
+ return "unusable-proc-files";
+ case kErrorInvalidCoreFile:
+ return "invalid-core-file";
+ case kErrorUnsupported32BitCoreFile:
+ return "unsupported-32bit-core-file";
+ case kErrorCore2MinidumpConversion:
+ return "core2md-conversion";
+ default:
+ return "";
+ }
+}
+
+// Return the string that should be used for the kernel's core_pattern file.
+// Note that if you change the format of the enabled pattern, you'll probably
+// also need to change the ParseCrashAttributes() function below, the
+// user_collector_test.cc unittest, and the logging_UserCrash.py autotest.
+std::string UserCollector::GetPattern(bool enabled) const {
+ if (enabled) {
+ // Combine the four crash attributes into one parameter to try to reduce
+ // the size of the invocation line for crash_reporter, since the kernel
+ // has a fixed-sized (128B) buffer for it (before parameter expansion).
+ // Note that the kernel does not support quoted arguments in core_pattern.
+ return StringPrintf("|%s --user=%%P:%%s:%%u:%%e", our_path_.c_str());
+ } else {
+ return "core";
+ }
+}
+
+bool UserCollector::SetUpInternal(bool enabled) {
+ CHECK(initialized_);
+ LOG(INFO) << (enabled ? "Enabling" : "Disabling") << " user crash handling";
+
+ if (base::WriteFile(FilePath(core_pipe_limit_file_), kCorePipeLimit,
+ strlen(kCorePipeLimit)) !=
+ static_cast<int>(strlen(kCorePipeLimit))) {
+ PLOG(ERROR) << "Unable to write " << core_pipe_limit_file_;
+ return false;
+ }
+ std::string pattern = GetPattern(enabled);
+ if (base::WriteFile(FilePath(core_pattern_file_), pattern.c_str(),
+ pattern.length()) != static_cast<int>(pattern.length())) {
+ PLOG(ERROR) << "Unable to write " << core_pattern_file_;
+ return false;
+ }
+ return true;
+}
+
+bool UserCollector::GetFirstLineWithPrefix(
+ const std::vector<std::string> &lines,
+ const char *prefix, std::string *line) {
+ std::vector<std::string>::const_iterator line_iterator;
+ for (line_iterator = lines.begin(); line_iterator != lines.end();
+ ++line_iterator) {
+ if (line_iterator->find(prefix) == 0) {
+ *line = *line_iterator;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool UserCollector::GetIdFromStatus(
+ const char *prefix, IdKind kind,
+ const std::vector<std::string> &status_lines, int *id) {
+ // From fs/proc/array.c:task_state(), this file contains:
+ // \nUid:\t<uid>\t<euid>\t<suid>\t<fsuid>\n
+ std::string id_line;
+ if (!GetFirstLineWithPrefix(status_lines, prefix, &id_line)) {
+ return false;
+ }
+ std::string id_substring = id_line.substr(strlen(prefix), std::string::npos);
+ std::vector<std::string> ids;
+ base::SplitString(id_substring, '\t', &ids);
+ if (ids.size() != kIdMax || kind < 0 || kind >= kIdMax) {
+ return false;
+ }
+ const char *number = ids[kind].c_str();
+ char *end_number = nullptr;
+ *id = strtol(number, &end_number, 10);
+ if (*end_number != '\0') {
+ return false;
+ }
+ return true;
+}
+
+bool UserCollector::GetStateFromStatus(
+ const std::vector<std::string> &status_lines, std::string *state) {
+ std::string state_line;
+ if (!GetFirstLineWithPrefix(status_lines, kStatePrefix, &state_line)) {
+ return false;
+ }
+ *state = state_line.substr(strlen(kStatePrefix), std::string::npos);
+ return true;
+}
+
+void UserCollector::EnqueueCollectionErrorLog(pid_t pid,
+ ErrorType error_type,
+ const std::string &exec) {
+ FilePath crash_path;
+ LOG(INFO) << "Writing conversion problems as separate crash report.";
+ if (!GetCreatedCrashDirectoryByEuid(0, &crash_path, nullptr)) {
+ LOG(ERROR) << "Could not even get log directory; out of space?";
+ return;
+ }
+ AddCrashMetaData("sig", kCollectionErrorSignature);
+ AddCrashMetaData("error_type", GetErrorTypeSignature(error_type));
+ std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
+ std::string error_log = chromeos::GetLog();
+ FilePath diag_log_path = GetCrashPath(crash_path, dump_basename, "diaglog");
+ if (GetLogContents(FilePath(log_config_path_), kCollectionErrorSignature,
+ diag_log_path)) {
+ // We load the contents of diag_log into memory and append it to
+ // the error log. We cannot just append to files because we need
+ // to always create new files to prevent attack.
+ std::string diag_log_contents;
+ base::ReadFileToString(diag_log_path, &diag_log_contents);
+ error_log.append(diag_log_contents);
+ base::DeleteFile(diag_log_path, false);
+ }
+ FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
+ FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
+ // We must use WriteNewFile instead of base::WriteFile as we do
+ // not want to write with root access to a symlink that an attacker
+ // might have created.
+ if (WriteNewFile(log_path, error_log.data(), error_log.length()) < 0) {
+ LOG(ERROR) << "Error writing new file " << log_path.value();
+ return;
+ }
+ WriteCrashMetaData(meta_path, exec, log_path.value());
+}
+
+bool UserCollector::CopyOffProcFiles(pid_t pid,
+ const FilePath &container_dir) {
+ if (!base::CreateDirectory(container_dir)) {
+ PLOG(ERROR) << "Could not create " << container_dir.value().c_str();
+ return false;
+ }
+ FilePath process_path = GetProcessPath(pid);
+ if (!base::PathExists(process_path)) {
+ LOG(ERROR) << "Path " << process_path.value() << " does not exist";
+ return false;
+ }
+ static const char *proc_files[] = {
+ "auxv",
+ "cmdline",
+ "environ",
+ "maps",
+ "status"
+ };
+ for (unsigned i = 0; i < arraysize(proc_files); ++i) {
+ if (!base::CopyFile(process_path.Append(proc_files[i]),
+ container_dir.Append(proc_files[i]))) {
+ LOG(ERROR) << "Could not copy " << proc_files[i] << " file";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool UserCollector::ValidateProcFiles(const FilePath &container_dir) const {
+ // Check if the maps file is empty, which could be due to the crashed
+ // process being reaped by the kernel before finishing a core dump.
+ int64_t file_size = 0;
+ if (!base::GetFileSize(container_dir.Append("maps"), &file_size)) {
+ LOG(ERROR) << "Could not get the size of maps file";
+ return false;
+ }
+ if (file_size == 0) {
+ LOG(ERROR) << "maps file is empty";
+ return false;
+ }
+ return true;
+}
+
+UserCollector::ErrorType UserCollector::ValidateCoreFile(
+ const FilePath &core_path) const {
+ int fd = HANDLE_EINTR(open(core_path.value().c_str(), O_RDONLY));
+ if (fd < 0) {
+ PLOG(ERROR) << "Could not open core file " << core_path.value();
+ return kErrorInvalidCoreFile;
+ }
+
+ char e_ident[EI_NIDENT];
+ bool read_ok = base::ReadFromFD(fd, e_ident, sizeof(e_ident));
+ IGNORE_EINTR(close(fd));
+ if (!read_ok) {
+ LOG(ERROR) << "Could not read header of core file";
+ return kErrorInvalidCoreFile;
+ }
+
+ if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
+ e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) {
+ LOG(ERROR) << "Invalid core file";
+ return kErrorInvalidCoreFile;
+ }
+
+#if __WORDSIZE == 64
+ // TODO(benchan, mkrebs): Remove this check once core2md can
+ // handles both 32-bit and 64-bit ELF on a 64-bit platform.
+ if (e_ident[EI_CLASS] == ELFCLASS32) {
+ LOG(ERROR) << "Conversion of 32-bit core file on 64-bit platform is "
+ << "currently not supported";
+ return kErrorUnsupported32BitCoreFile;
+ }
+#endif
+
+ return kErrorNone;
+}
+
+bool UserCollector::GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
+ FilePath *crash_file_path,
+ bool *out_of_capacity) {
+ FilePath process_path = GetProcessPath(pid);
+ std::string status;
+ if (directory_failure_) {
+ LOG(ERROR) << "Purposefully failing to create spool directory";
+ return false;
+ }
+
+ uid_t uid;
+ if (base::ReadFileToString(process_path.Append("status"), &status)) {
+ std::vector<std::string> status_lines;
+ base::SplitString(status, '\n', &status_lines);
+
+ std::string process_state;
+ if (!GetStateFromStatus(status_lines, &process_state)) {
+ LOG(ERROR) << "Could not find process state in status file";
+ return false;
+ }
+ LOG(INFO) << "State of crashed process [" << pid << "]: " << process_state;
+
+ // Get effective UID of crashing process.
+ int id;
+ if (!GetIdFromStatus(kUserId, kIdEffective, status_lines, &id)) {
+ LOG(ERROR) << "Could not find euid in status file";
+ return false;
+ }
+ uid = id;
+ } else if (supplied_ruid != kUnknownUid) {
+ LOG(INFO) << "Using supplied UID " << supplied_ruid
+ << " for crashed process [" << pid
+ << "] due to error reading status file";
+ uid = supplied_ruid;
+ } else {
+ LOG(ERROR) << "Could not read status file and kernel did not supply UID";
+ LOG(INFO) << "Path " << process_path.value() << " DirectoryExists: "
+ << base::DirectoryExists(process_path);
+ return false;
+ }
+
+ if (!GetCreatedCrashDirectoryByEuid(uid, crash_file_path, out_of_capacity)) {
+ LOG(ERROR) << "Could not create crash directory";
+ return false;
+ }
+ return true;
+}
+
+bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) {
+ // Copy off all stdin to a core file.
+ FilePath stdin_path("/dev/fd/0");
+ if (base::CopyFile(stdin_path, core_path)) {
+ return true;
+ }
+
+ PLOG(ERROR) << "Could not write core file";
+ // If the file system was full, make sure we remove any remnants.
+ base::DeleteFile(core_path, false);
+ return false;
+}
+
+bool UserCollector::RunCoreToMinidump(const FilePath &core_path,
+ const FilePath &procfs_directory,
+ const FilePath &minidump_path,
+ const FilePath &temp_directory) {
+ FilePath output_path = temp_directory.Append("output");
+ chromeos::ProcessImpl core2md;
+ core2md.RedirectOutput(output_path.value());
+ core2md.AddArg(kCoreToMinidumpConverterPath);
+ core2md.AddArg(core_path.value());
+ core2md.AddArg(procfs_directory.value());
+
+ if (!core2md_failure_) {
+ core2md.AddArg(minidump_path.value());
+ } else {
+ // To test how core2md errors are propagaged, cause an error
+ // by forgetting a required argument.
+ }
+
+ int errorlevel = core2md.Run();
+
+ std::string output;
+ base::ReadFileToString(output_path, &output);
+ if (errorlevel != 0) {
+ LOG(ERROR) << "Problem during " << kCoreToMinidumpConverterPath
+ << " [result=" << errorlevel << "]: " << output;
+ return false;
+ }
+
+ if (!base::PathExists(minidump_path)) {
+ LOG(ERROR) << "Minidump file " << minidump_path.value()
+ << " was not created";
+ return false;
+ }
+ return true;
+}
+
+UserCollector::ErrorType UserCollector::ConvertCoreToMinidump(
+ pid_t pid,
+ const FilePath &container_dir,
+ const FilePath &core_path,
+ const FilePath &minidump_path) {
+ // If proc files are unuable, we continue to read the core file from stdin,
+ // but only skip the core-to-minidump conversion, so that we may still use
+ // the core file for debugging.
+ bool proc_files_usable =
+ CopyOffProcFiles(pid, container_dir) && ValidateProcFiles(container_dir);
+
+ if (!CopyStdinToCoreFile(core_path)) {
+ return kErrorReadCoreData;
+ }
+
+ if (!proc_files_usable) {
+ LOG(INFO) << "Skipped converting core file to minidump due to "
+ << "unusable proc files";
+ return kErrorUnusableProcFiles;
+ }
+
+ ErrorType error = ValidateCoreFile(core_path);
+ if (error != kErrorNone) {
+ return error;
+ }
+
+ if (!RunCoreToMinidump(core_path,
+ container_dir, // procfs directory
+ minidump_path,
+ container_dir)) { // temporary directory
+ return kErrorCore2MinidumpConversion;
+ }
+
+ LOG(INFO) << "Stored minidump to " << minidump_path.value();
+ return kErrorNone;
+}
+
+UserCollector::ErrorType UserCollector::ConvertAndEnqueueCrash(
+ pid_t pid, const std::string &exec, uid_t supplied_ruid,
+ bool *out_of_capacity) {
+ FilePath crash_path;
+ if (!GetCreatedCrashDirectory(pid, supplied_ruid, &crash_path,
+ out_of_capacity)) {
+ LOG(ERROR) << "Unable to find/create process-specific crash path";
+ return kErrorSystemIssue;
+ }
+
+ // Directory like /tmp/crash_reporter/1234 which contains the
+ // procfs entries and other temporary files used during conversion.
+ FilePath container_dir(StringPrintf("/tmp/crash_reporter/%d", pid));
+ // Delete a pre-existing directory from crash reporter that may have
+ // been left around for diagnostics from a failed conversion attempt.
+ // If we don't, existing files can cause forking to fail.
+ base::DeleteFile(container_dir, true);
+ std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
+ FilePath core_path = GetCrashPath(crash_path, dump_basename, "core");
+ FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
+ FilePath minidump_path = GetCrashPath(crash_path, dump_basename, "dmp");
+ FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
+
+ if (GetLogContents(FilePath(log_config_path_), exec, log_path))
+ AddCrashMetaData("log", log_path.value());
+
+ ErrorType error_type =
+ ConvertCoreToMinidump(pid, container_dir, core_path, minidump_path);
+ if (error_type != kErrorNone) {
+ LOG(INFO) << "Leaving core file at " << core_path.value()
+ << " due to conversion error";
+ return error_type;
+ }
+
+ // Here we commit to sending this file. We must not return false
+ // after this point or we will generate a log report as well as a
+ // crash report.
+ WriteCrashMetaData(meta_path,
+ exec,
+ minidump_path.value());
+
+ if (!IsDeveloperImage()) {
+ base::DeleteFile(core_path, false);
+ } else {
+ LOG(INFO) << "Leaving core file at " << core_path.value()
+ << " due to developer image";
+ }
+
+ base::DeleteFile(container_dir, true);
+ return kErrorNone;
+}
+
+bool UserCollector::ParseCrashAttributes(const std::string &crash_attributes,
+ pid_t *pid, int *signal, uid_t *uid,
+ std::string *kernel_supplied_name) {
+ pcrecpp::RE re("(\\d+):(\\d+):(\\d+):(.*)");
+ if (re.FullMatch(crash_attributes, pid, signal, uid, kernel_supplied_name))
+ return true;
+
+ LOG(INFO) << "Falling back to parsing crash attributes '"
+ << crash_attributes << "' without UID";
+ pcrecpp::RE re_without_uid("(\\d+):(\\d+):(.*)");
+ *uid = kUnknownUid;
+ return re_without_uid.FullMatch(crash_attributes, pid, signal,
+ kernel_supplied_name);
+}
+
+// Returns true if the given executable name matches that of Chrome. This
+// includes checks for threads that Chrome has renamed.
+static bool IsChromeExecName(const std::string &exec) {
+ static const char *kChromeNames[] = {
+ "chrome",
+ // These are additional thread names seen in http://crash/
+ "MediaPipeline",
+ // These come from the use of base::PlatformThread::SetName() directly
+ "CrBrowserMain", "CrRendererMain", "CrUtilityMain", "CrPPAPIMain",
+ "CrPPAPIBrokerMain", "CrPluginMain", "CrWorkerMain", "CrGpuMain",
+ "BrokerEvent", "CrVideoRenderer", "CrShutdownDetector",
+ "UsbEventHandler", "CrNaClMain", "CrServiceMain",
+ // These thread names come from the use of base::Thread
+ "Gamepad polling thread", "Chrome_InProcGpuThread",
+ "Chrome_DragDropThread", "Renderer::FILE", "VC manager",
+ "VideoCaptureModuleImpl", "JavaBridge", "VideoCaptureManagerThread",
+ "Geolocation", "Geolocation_wifi_provider",
+ "Device orientation polling thread", "Chrome_InProcRendererThread",
+ "NetworkChangeNotifier", "Watchdog", "inotify_reader",
+ "cf_iexplore_background_thread", "BrowserWatchdog",
+ "Chrome_HistoryThread", "Chrome_SyncThread", "Chrome_ShellDialogThread",
+ "Printing_Worker", "Chrome_SafeBrowsingThread", "SimpleDBThread",
+ "D-Bus thread", "AudioThread", "NullAudioThread", "V4L2Thread",
+ "ChromotingClientDecodeThread", "Profiling_Flush",
+ "worker_thread_ticker", "AudioMixerAlsa", "AudioMixerCras",
+ "FakeAudioRecordingThread", "CaptureThread",
+ "Chrome_WebSocketproxyThread", "ProcessWatcherThread",
+ "Chrome_CameraThread", "import_thread", "NaCl_IOThread",
+ "Chrome_CloudPrintJobPrintThread", "Chrome_CloudPrintProxyCoreThread",
+ "DaemonControllerFileIO", "ChromotingMainThread",
+ "ChromotingEncodeThread", "ChromotingDesktopThread",
+ "ChromotingIOThread", "ChromotingFileIOThread",
+ "Chrome_libJingle_WorkerThread", "Chrome_ChildIOThread",
+ "GLHelperThread", "RemotingHostPlugin",
+ // "PAC thread #%d", // not easy to check because of "%d"
+ "Chrome_DBThread", "Chrome_WebKitThread", "Chrome_FileThread",
+ "Chrome_FileUserBlockingThread", "Chrome_ProcessLauncherThread",
+ "Chrome_CacheThread", "Chrome_IOThread", "Cache Thread", "File Thread",
+ "ServiceProcess_IO", "ServiceProcess_File",
+ "extension_crash_uploader", "gpu-process_crash_uploader",
+ "plugin_crash_uploader", "renderer_crash_uploader",
+ // These come from the use of webkit_glue::WebThreadImpl
+ "Compositor", "Browser Compositor",
+ // "WorkerPool/%d", // not easy to check because of "%d"
+ // These come from the use of base::Watchdog
+ "Startup watchdog thread Watchdog", "Shutdown watchdog thread Watchdog",
+ // These come from the use of AudioDeviceThread::Start
+ "AudioDevice", "AudioInputDevice", "AudioOutputDevice",
+ // These come from the use of MessageLoopFactory::GetMessageLoop
+ "GpuVideoDecoder", "RtcVideoDecoderThread", "PipelineThread",
+ "AudioDecoderThread", "VideoDecoderThread",
+ // These come from the use of MessageLoopFactory::GetMessageLoopProxy
+ "CaptureVideoDecoderThread", "CaptureVideoDecoder",
+ // These come from the use of base::SimpleThread
+ "LocalInputMonitor/%d", // "%d" gets lopped off for kernel-supplied
+ // These come from the use of base::DelegateSimpleThread
+ "ipc_channel_nacl reader thread/%d", "plugin_audio_input_thread/%d",
+ "plugin_audio_thread/%d",
+ // These come from the use of base::SequencedWorkerPool
+ "BrowserBlockingWorker%d/%d", // "%d" gets lopped off for kernel-supplied
+ };
+ static std::set<std::string> chrome_names;
+
+ // Initialize a set of chrome names, for efficient lookup
+ if (chrome_names.empty()) {
+ for (size_t i = 0; i < arraysize(kChromeNames); i++) {
+ std::string check_name(kChromeNames[i]);
+ chrome_names.insert(check_name);
+ // When checking a kernel-supplied name, it should be truncated to 15
+ // chars. See PR_SET_NAME in
+ // http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html,
+ // although that page misleads by saying "16 bytes".
+ chrome_names.insert("supplied_" + std::string(check_name, 0, 15));
+ }
+ }
+
+ return ContainsKey(chrome_names, exec);
+}
+
+bool UserCollector::ShouldDump(bool has_owner_consent,
+ bool is_developer,
+ bool handle_chrome_crashes,
+ const std::string &exec,
+ std::string *reason) {
+ reason->clear();
+
+ // Treat Chrome crashes as if the user opted-out. We stop counting Chrome
+ // crashes towards user crashes, so user crashes really mean non-Chrome
+ // user-space crashes.
+ if (!handle_chrome_crashes && IsChromeExecName(exec)) {
+ *reason = "ignoring call by kernel - chrome crash; "
+ "waiting for chrome to call us directly";
+ return false;
+ }
+
+ // For developer builds, we always want to keep the crash reports unless
+ // we're testing the crash facilities themselves. This overrides
+ // feedback. Crash sending still obeys consent.
+ if (is_developer) {
+ *reason = "developer build - not testing - always dumping";
+ return true;
+ }
+
+ if (!has_owner_consent) {
+ *reason = "ignoring - no consent";
+ return false;
+ }
+
+ *reason = "handling";
+ return true;
+}
+
+bool UserCollector::HandleCrash(const std::string &crash_attributes,
+ const char *force_exec) {
+ CHECK(initialized_);
+ pid_t pid = 0;
+ int signal = 0;
+ uid_t supplied_ruid = kUnknownUid;
+ std::string kernel_supplied_name;
+
+ if (!ParseCrashAttributes(crash_attributes, &pid, &signal, &supplied_ruid,
+ &kernel_supplied_name)) {
+ LOG(ERROR) << "Invalid parameter: --user=" << crash_attributes;
+ return false;
+ }
+
+ std::string exec;
+ if (force_exec) {
+ exec.assign(force_exec);
+ } else if (!GetExecutableBaseNameFromPid(pid, &exec)) {
+ // If we cannot find the exec name, use the kernel supplied name.
+ // We don't always use the kernel's since it truncates the name to
+ // 16 characters.
+ exec = StringPrintf("supplied_%s", kernel_supplied_name.c_str());
+ }
+
+ // Allow us to test the crash reporting mechanism successfully even if
+ // other parts of the system crash.
+ if (!filter_in_.empty() &&
+ (filter_in_ == "none" ||
+ filter_in_ != exec)) {
+ // We use a different format message to make it more obvious in tests
+ // which crashes are test generated and which are real.
+ LOG(WARNING) << "Ignoring crash from " << exec << "[" << pid << "] while "
+ << "filter_in=" << filter_in_ << ".";
+ return true;
+ }
+
+ std::string reason;
+ bool dump = ShouldDump(is_feedback_allowed_function_(),
+ IsDeveloperImage(),
+ ShouldHandleChromeCrashes(),
+ exec,
+ &reason);
+
+ LOG(WARNING) << "Received crash notification for " << exec << "[" << pid
+ << "] sig " << signal << ", user " << supplied_ruid
+ << " (" << reason << ")";
+
+ if (dump) {
+ count_crash_function_();
+
+ if (generate_diagnostics_) {
+ bool out_of_capacity = false;
+ ErrorType error_type =
+ ConvertAndEnqueueCrash(pid, exec, supplied_ruid, &out_of_capacity);
+ if (error_type != kErrorNone) {
+ if (!out_of_capacity)
+ EnqueueCollectionErrorLog(pid, error_type, exec);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/crash_reporter/user_collector.h b/crash_reporter/user_collector.h
new file mode 100644
index 0000000..aac94cb
--- /dev/null
+++ b/crash_reporter/user_collector.h
@@ -0,0 +1,192 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_USER_COLLECTOR_H_
+#define CRASH_REPORTER_USER_COLLECTOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "crash-reporter/crash_collector.h"
+
+class SystemLogging;
+
+// User crash collector.
+class UserCollector : public CrashCollector {
+ public:
+ UserCollector();
+
+ // Initialize the user crash collector for detection of crashes,
+ // given a crash counting function, the path to this executable,
+ // metrics collection enabled oracle, and system logger facility.
+ // Crash detection/reporting is not enabled until Enable is called.
+ // |generate_diagnostics| is used to indicate whether or not to try
+ // to generate a minidump from crashes.
+ void Initialize(CountCrashFunction count_crash,
+ const std::string &our_path,
+ IsFeedbackAllowedFunction is_metrics_allowed,
+ bool generate_diagnostics,
+ bool core2md_failure,
+ bool directory_failure,
+ const std::string &filter_in);
+
+ ~UserCollector() override;
+
+ // Enable collection.
+ bool Enable() { return SetUpInternal(true); }
+
+ // Disable collection.
+ bool Disable() { return SetUpInternal(false); }
+
+ // Handle a specific user crash. Returns true on success.
+ bool HandleCrash(const std::string &crash_attributes,
+ const char *force_exec);
+
+ // Set (override the default) core file pattern.
+ void set_core_pattern_file(const std::string &pattern) {
+ core_pattern_file_ = pattern;
+ }
+
+ // Set (override the default) core pipe limit file.
+ void set_core_pipe_limit_file(const std::string &path) {
+ core_pipe_limit_file_ = path;
+ }
+
+ private:
+ friend class UserCollectorTest;
+ FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPath);
+ FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPid);
+ FRIEND_TEST(UserCollectorTest, CopyOffProcFilesOK);
+ FRIEND_TEST(UserCollectorTest, GetExecutableBaseNameFromPid);
+ FRIEND_TEST(UserCollectorTest, GetFirstLineWithPrefix);
+ FRIEND_TEST(UserCollectorTest, GetIdFromStatus);
+ FRIEND_TEST(UserCollectorTest, GetStateFromStatus);
+ FRIEND_TEST(UserCollectorTest, GetProcessPath);
+ FRIEND_TEST(UserCollectorTest, GetSymlinkTarget);
+ FRIEND_TEST(UserCollectorTest, GetUserInfoFromName);
+ FRIEND_TEST(UserCollectorTest, ParseCrashAttributes);
+ FRIEND_TEST(UserCollectorTest, ShouldDumpChromeOverridesDeveloperImage);
+ FRIEND_TEST(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent);
+ FRIEND_TEST(UserCollectorTest, ShouldDumpUseConsentProductionImage);
+ FRIEND_TEST(UserCollectorTest, ValidateProcFiles);
+ FRIEND_TEST(UserCollectorTest, ValidateCoreFile);
+
+ // Enumeration to pass to GetIdFromStatus. Must match the order
+ // that the kernel lists IDs in the status file.
+ enum IdKind {
+ kIdReal = 0, // uid and gid
+ kIdEffective = 1, // euid and egid
+ kIdSet = 2, // suid and sgid
+ kIdFileSystem = 3, // fsuid and fsgid
+ kIdMax
+ };
+
+ enum ErrorType {
+ kErrorNone,
+ kErrorSystemIssue,
+ kErrorReadCoreData,
+ kErrorUnusableProcFiles,
+ kErrorInvalidCoreFile,
+ kErrorUnsupported32BitCoreFile,
+ kErrorCore2MinidumpConversion,
+ };
+
+ static const int kForkProblem = 255;
+
+ // Returns an error type signature for a given |error_type| value,
+ // which is reported to the crash server along with the
+ // crash_reporter-user-collection signature.
+ std::string GetErrorTypeSignature(ErrorType error_type) const;
+
+ std::string GetPattern(bool enabled) const;
+ bool SetUpInternal(bool enabled);
+
+ // Returns, via |line|, the first line in |lines| that starts with |prefix|.
+ // Returns true if a line is found, or false otherwise.
+ bool GetFirstLineWithPrefix(const std::vector<std::string> &lines,
+ const char *prefix, std::string *line);
+
+ // Returns the identifier of |kind|, via |id|, found in |status_lines| on
+ // the line starting with |prefix|. |status_lines| contains the lines in
+ // the status file. Returns true if the identifier can be determined.
+ bool GetIdFromStatus(const char *prefix,
+ IdKind kind,
+ const std::vector<std::string> &status_lines,
+ int *id);
+
+ // Returns the process state, via |state|, found in |status_lines|, which
+ // contains the lines in the status file. Returns true if the process state
+ // can be determined.
+ bool GetStateFromStatus(const std::vector<std::string> &status_lines,
+ std::string *state);
+
+ void LogCollectionError(const std::string &error_message);
+ void EnqueueCollectionErrorLog(pid_t pid, ErrorType error_type,
+ const std::string &exec_name);
+
+ bool CopyOffProcFiles(pid_t pid, const base::FilePath &container_dir);
+
+ // Validates the proc files at |container_dir| and returns true if they
+ // are usable for the core-to-minidump conversion later. For instance, if
+ // a process is reaped by the kernel before the copying of its proc files
+ // takes place, some proc files like /proc/<pid>/maps may contain nothing
+ // and thus become unusable.
+ bool ValidateProcFiles(const base::FilePath &container_dir) const;
+
+ // Validates the core file at |core_path| and returns kErrorNone if
+ // the file contains the ELF magic bytes and an ELF class that matches the
+ // platform (i.e. 32-bit ELF on a 32-bit platform or 64-bit ELF on a 64-bit
+ // platform), which is due to the limitation in core2md. It returns an error
+ // type otherwise.
+ ErrorType ValidateCoreFile(const base::FilePath &core_path) const;
+
+ // Determines the crash directory for given pid based on pid's owner,
+ // and creates the directory if necessary with appropriate permissions.
+ // Returns true whether or not directory needed to be created, false on
+ // any failure.
+ bool GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
+ base::FilePath *crash_file_path,
+ bool *out_of_capacity);
+ bool CopyStdinToCoreFile(const base::FilePath &core_path);
+ bool RunCoreToMinidump(const base::FilePath &core_path,
+ const base::FilePath &procfs_directory,
+ const base::FilePath &minidump_path,
+ const base::FilePath &temp_directory);
+ ErrorType ConvertCoreToMinidump(pid_t pid,
+ const base::FilePath &container_dir,
+ const base::FilePath &core_path,
+ const base::FilePath &minidump_path);
+ ErrorType ConvertAndEnqueueCrash(pid_t pid, const std::string &exec_name,
+ uid_t supplied_ruid, bool *out_of_capacity);
+ bool ParseCrashAttributes(const std::string &crash_attributes,
+ pid_t *pid, int *signal, uid_t *uid,
+ std::string *kernel_supplied_name);
+
+ bool ShouldDump(bool has_owner_consent,
+ bool is_developer,
+ bool handle_chrome_crashes,
+ const std::string &exec,
+ std::string *reason);
+
+ bool generate_diagnostics_;
+ std::string core_pattern_file_;
+ std::string core_pipe_limit_file_;
+ std::string our_path_;
+ bool initialized_;
+
+ bool core2md_failure_;
+ bool directory_failure_;
+ std::string filter_in_;
+
+ static const char *kUserId;
+ static const char *kGroupId;
+
+ DISALLOW_COPY_AND_ASSIGN(UserCollector);
+};
+
+#endif // CRASH_REPORTER_USER_COLLECTOR_H_
diff --git a/crash_reporter/user_collector_test.cc b/crash_reporter/user_collector_test.cc
new file mode 100644
index 0000000..823d8b4
--- /dev/null
+++ b/crash_reporter/user_collector_test.cc
@@ -0,0 +1,553 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/user_collector.h"
+
+#include <bits/wordsize.h>
+#include <elf.h>
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_split.h>
+#include <chromeos/syslog_logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using base::FilePath;
+using chromeos::FindLog;
+
+namespace {
+
+int s_crashes = 0;
+bool s_metrics = false;
+
+const char kFilePath[] = "/my/path";
+
+// Keep in sync with UserCollector::ShouldDump.
+const char kChromeIgnoreMsg[] =
+ "ignoring call by kernel - chrome crash; "
+ "waiting for chrome to call us directly";
+
+void CountCrash() {
+ ++s_crashes;
+}
+
+bool IsMetrics() {
+ return s_metrics;
+}
+
+} // namespace
+
+class UserCollectorMock : public UserCollector {
+ public:
+ MOCK_METHOD0(SetUpDBus, void());
+};
+
+class UserCollectorTest : public ::testing::Test {
+ void SetUp() {
+ s_crashes = 0;
+
+ EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
+
+ collector_.Initialize(CountCrash,
+ kFilePath,
+ IsMetrics,
+ false,
+ false,
+ false,
+ "");
+ base::DeleteFile(FilePath("test"), true);
+ mkdir("test", 0777);
+ collector_.set_core_pattern_file("test/core_pattern");
+ collector_.set_core_pipe_limit_file("test/core_pipe_limit");
+ pid_ = getpid();
+ chromeos::ClearLog();
+ }
+
+ protected:
+ void ExpectFileEquals(const char *golden,
+ const FilePath &file_path) {
+ std::string contents;
+ EXPECT_TRUE(base::ReadFileToString(file_path, &contents));
+ EXPECT_EQ(golden, contents);
+ }
+
+ std::vector<std::string> SplitLines(const std::string &lines) const {
+ std::vector<std::string> result;
+ base::SplitString(lines, '\n', &result);
+ return result;
+ }
+
+ UserCollectorMock collector_;
+ pid_t pid_;
+};
+
+TEST_F(UserCollectorTest, EnableOK) {
+ ASSERT_TRUE(collector_.Enable());
+ ExpectFileEquals("|/my/path --user=%P:%s:%u:%e",
+ FilePath("test/core_pattern"));
+ ExpectFileEquals("4", FilePath("test/core_pipe_limit"));
+ ASSERT_EQ(s_crashes, 0);
+ EXPECT_TRUE(FindLog("Enabling user crash handling"));
+}
+
+TEST_F(UserCollectorTest, EnableNoPatternFileAccess) {
+ collector_.set_core_pattern_file("/does_not_exist");
+ ASSERT_FALSE(collector_.Enable());
+ ASSERT_EQ(s_crashes, 0);
+ EXPECT_TRUE(FindLog("Enabling user crash handling"));
+ EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
+}
+
+TEST_F(UserCollectorTest, EnableNoPipeLimitFileAccess) {
+ collector_.set_core_pipe_limit_file("/does_not_exist");
+ ASSERT_FALSE(collector_.Enable());
+ ASSERT_EQ(s_crashes, 0);
+ // Core pattern should not be written if we cannot access the pipe limit
+ // or otherwise we may set a pattern that results in infinite recursion.
+ ASSERT_FALSE(base::PathExists(FilePath("test/core_pattern")));
+ EXPECT_TRUE(FindLog("Enabling user crash handling"));
+ EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
+}
+
+TEST_F(UserCollectorTest, DisableOK) {
+ ASSERT_TRUE(collector_.Disable());
+ ExpectFileEquals("core", FilePath("test/core_pattern"));
+ ASSERT_EQ(s_crashes, 0);
+ EXPECT_TRUE(FindLog("Disabling user crash handling"));
+}
+
+TEST_F(UserCollectorTest, DisableNoFileAccess) {
+ collector_.set_core_pattern_file("/does_not_exist");
+ ASSERT_FALSE(collector_.Disable());
+ ASSERT_EQ(s_crashes, 0);
+ EXPECT_TRUE(FindLog("Disabling user crash handling"));
+ EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
+}
+
+TEST_F(UserCollectorTest, ParseCrashAttributes) {
+ pid_t pid;
+ int signal;
+ uid_t uid;
+ std::string exec_name;
+ EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:1000:foobar",
+ &pid, &signal, &uid, &exec_name));
+ EXPECT_EQ(123456, pid);
+ EXPECT_EQ(11, signal);
+ EXPECT_EQ(1000, uid);
+ EXPECT_EQ("foobar", exec_name);
+ EXPECT_TRUE(collector_.ParseCrashAttributes("4321:6:barfoo",
+ &pid, &signal, &uid, &exec_name));
+ EXPECT_EQ(4321, pid);
+ EXPECT_EQ(6, signal);
+ EXPECT_EQ(-1, uid);
+ EXPECT_EQ("barfoo", exec_name);
+
+ EXPECT_FALSE(collector_.ParseCrashAttributes("123456:11",
+ &pid, &signal, &uid, &exec_name));
+
+ EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:exec:extra",
+ &pid, &signal, &uid, &exec_name));
+ EXPECT_EQ("exec:extra", exec_name);
+
+ EXPECT_FALSE(collector_.ParseCrashAttributes("12345p:11:foobar",
+ &pid, &signal, &uid, &exec_name));
+
+ EXPECT_FALSE(collector_.ParseCrashAttributes("123456:1 :foobar",
+ &pid, &signal, &uid, &exec_name));
+
+ EXPECT_FALSE(collector_.ParseCrashAttributes("123456::foobar",
+ &pid, &signal, &uid, &exec_name));
+}
+
+TEST_F(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent) {
+ std::string reason;
+ EXPECT_TRUE(collector_.ShouldDump(false, true, false,
+ "chrome-wm", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping", reason);
+
+ // When running a crash test, behave as normal.
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "chrome-wm", &reason));
+ EXPECT_EQ("ignoring - no consent", reason);
+}
+
+TEST_F(UserCollectorTest, ShouldDumpChromeOverridesDeveloperImage) {
+ std::string reason;
+ // When running a crash test, behave as normal.
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "chrome", &reason));
+ EXPECT_EQ(kChromeIgnoreMsg, reason);
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "supplied_Compositor", &reason));
+ EXPECT_EQ(kChromeIgnoreMsg, reason);
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "supplied_PipelineThread", &reason));
+ EXPECT_EQ(kChromeIgnoreMsg, reason);
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "Chrome_ChildIOThread", &reason));
+ EXPECT_EQ(kChromeIgnoreMsg, reason);
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "supplied_Chrome_ChildIOT", &reason));
+ EXPECT_EQ(kChromeIgnoreMsg, reason);
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "supplied_ChromotingClien", &reason));
+ EXPECT_EQ(kChromeIgnoreMsg, reason);
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "supplied_LocalInputMonit", &reason));
+ EXPECT_EQ(kChromeIgnoreMsg, reason);
+
+ // When running a developer image, test that chrome crashes are handled
+ // when the "handle_chrome_crashes" flag is set.
+ EXPECT_TRUE(collector_.ShouldDump(false, true, true,
+ "chrome", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping",
+ reason);
+ EXPECT_TRUE(collector_.ShouldDump(false, true, true,
+ "supplied_Compositor", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping",
+ reason);
+ EXPECT_TRUE(collector_.ShouldDump(false, true, true,
+ "supplied_PipelineThread", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping",
+ reason);
+ EXPECT_TRUE(collector_.ShouldDump(false, true, true,
+ "Chrome_ChildIOThread", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping",
+ reason);
+ EXPECT_TRUE(collector_.ShouldDump(false, true, true,
+ "supplied_Chrome_ChildIOT", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping",
+ reason);
+ EXPECT_TRUE(collector_.ShouldDump(false, true, true,
+ "supplied_ChromotingClien", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping",
+ reason);
+ EXPECT_TRUE(collector_.ShouldDump(false, true, true,
+ "supplied_LocalInputMonit", &reason));
+ EXPECT_EQ("developer build - not testing - always dumping",
+ reason);
+}
+
+TEST_F(UserCollectorTest, ShouldDumpUseConsentProductionImage) {
+ std::string result;
+ EXPECT_FALSE(collector_.ShouldDump(false, false, false,
+ "chrome-wm", &result));
+ EXPECT_EQ("ignoring - no consent", result);
+
+ EXPECT_TRUE(collector_.ShouldDump(true, false, false,
+ "chrome-wm", &result));
+ EXPECT_EQ("handling", result);
+}
+
+TEST_F(UserCollectorTest, HandleCrashWithoutConsent) {
+ s_metrics = false;
+ collector_.HandleCrash("20:10:ignored", "foobar");
+ EXPECT_TRUE(FindLog(
+ "Received crash notification for foobar[20] sig 10"));
+ ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(UserCollectorTest, HandleNonChromeCrashWithConsent) {
+ s_metrics = true;
+ collector_.HandleCrash("5:2:ignored", "chromeos-wm");
+ EXPECT_TRUE(FindLog(
+ "Received crash notification for chromeos-wm[5] sig 2"));
+ ASSERT_EQ(s_crashes, 1);
+}
+
+TEST_F(UserCollectorTest, HandleChromeCrashWithConsent) {
+ s_metrics = true;
+ collector_.HandleCrash("5:2:ignored", "chrome");
+ EXPECT_TRUE(FindLog(
+ "Received crash notification for chrome[5] sig 2"));
+ EXPECT_TRUE(FindLog(kChromeIgnoreMsg));
+ ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(UserCollectorTest, HandleSuppliedChromeCrashWithConsent) {
+ s_metrics = true;
+ collector_.HandleCrash("0:2:chrome", nullptr);
+ EXPECT_TRUE(FindLog(
+ "Received crash notification for supplied_chrome[0] sig 2"));
+ EXPECT_TRUE(FindLog(kChromeIgnoreMsg));
+ ASSERT_EQ(s_crashes, 0);
+}
+
+TEST_F(UserCollectorTest, GetProcessPath) {
+ FilePath path = collector_.GetProcessPath(100);
+ ASSERT_EQ("/proc/100", path.value());
+}
+
+TEST_F(UserCollectorTest, GetSymlinkTarget) {
+ FilePath result;
+ ASSERT_FALSE(collector_.GetSymlinkTarget(FilePath("/does_not_exist"),
+ &result));
+ ASSERT_TRUE(FindLog(
+ "Readlink failed on /does_not_exist with 2"));
+ std::string long_link;
+ for (int i = 0; i < 50; ++i)
+ long_link += "0123456789";
+ long_link += "/gold";
+
+ for (size_t len = 1; len <= long_link.size(); ++len) {
+ std::string this_link;
+ static const char kLink[] = "test/this_link";
+ this_link.assign(long_link.c_str(), len);
+ ASSERT_EQ(len, this_link.size());
+ unlink(kLink);
+ ASSERT_EQ(0, symlink(this_link.c_str(), kLink));
+ ASSERT_TRUE(collector_.GetSymlinkTarget(FilePath(kLink), &result));
+ ASSERT_EQ(this_link, result.value());
+ }
+}
+
+TEST_F(UserCollectorTest, GetExecutableBaseNameFromPid) {
+ std::string base_name;
+ EXPECT_FALSE(collector_.GetExecutableBaseNameFromPid(0, &base_name));
+ EXPECT_TRUE(FindLog(
+ "Readlink failed on /proc/0/exe with 2"));
+ EXPECT_TRUE(FindLog(
+ "GetSymlinkTarget failed - Path /proc/0 DirectoryExists: 0"));
+ EXPECT_TRUE(FindLog("stat /proc/0/exe failed: -1 2"));
+
+ chromeos::ClearLog();
+ pid_t my_pid = getpid();
+ EXPECT_TRUE(collector_.GetExecutableBaseNameFromPid(my_pid, &base_name));
+ EXPECT_FALSE(FindLog("Readlink failed"));
+ EXPECT_EQ("crash_reporter_test", base_name);
+}
+
+TEST_F(UserCollectorTest, GetFirstLineWithPrefix) {
+ std::vector<std::string> lines;
+ std::string line;
+
+ EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
+ EXPECT_EQ("", line);
+
+ lines.push_back("Name:\tls");
+ lines.push_back("State:\tR (running)");
+ lines.push_back(" Foo:\t1000");
+
+ line.clear();
+ EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
+ EXPECT_EQ(lines[0], line);
+
+ line.clear();
+ EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "State:", &line));
+ EXPECT_EQ(lines[1], line);
+
+ line.clear();
+ EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Foo:", &line));
+ EXPECT_EQ("", line);
+
+ line.clear();
+ EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, " Foo:", &line));
+ EXPECT_EQ(lines[2], line);
+
+ line.clear();
+ EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Bar:", &line));
+ EXPECT_EQ("", line);
+}
+
+TEST_F(UserCollectorTest, GetIdFromStatus) {
+ int id = 1;
+ EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdEffective,
+ SplitLines("nothing here"),
+ &id));
+ EXPECT_EQ(id, 1);
+
+ // Not enough parameters.
+ EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdReal,
+ SplitLines("line 1\nUid:\t1\n"),
+ &id));
+
+ const std::vector<std::string> valid_contents =
+ SplitLines("\nUid:\t1\t2\t3\t4\nGid:\t5\t6\t7\t8\n");
+ EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdReal,
+ valid_contents,
+ &id));
+ EXPECT_EQ(1, id);
+
+ EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdEffective,
+ valid_contents,
+ &id));
+ EXPECT_EQ(2, id);
+
+ EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdFileSystem,
+ valid_contents,
+ &id));
+ EXPECT_EQ(4, id);
+
+ EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+ UserCollector::kIdEffective,
+ valid_contents,
+ &id));
+ EXPECT_EQ(6, id);
+
+ EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+ UserCollector::kIdSet,
+ valid_contents,
+ &id));
+ EXPECT_EQ(7, id);
+
+ EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+ UserCollector::IdKind(5),
+ valid_contents,
+ &id));
+ EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
+ UserCollector::IdKind(-1),
+ valid_contents,
+ &id));
+
+ // Fail if junk after number
+ EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdReal,
+ SplitLines("Uid:\t1f\t2\t3\t4\n"),
+ &id));
+ EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdReal,
+ SplitLines("Uid:\t1\t2\t3\t4\n"),
+ &id));
+ EXPECT_EQ(1, id);
+
+ // Fail if more than 4 numbers.
+ EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
+ UserCollector::kIdReal,
+ SplitLines("Uid:\t1\t2\t3\t4\t5\n"),
+ &id));
+}
+
+TEST_F(UserCollectorTest, GetStateFromStatus) {
+ std::string state;
+ EXPECT_FALSE(collector_.GetStateFromStatus(SplitLines("nothing here"),
+ &state));
+ EXPECT_EQ("", state);
+
+ EXPECT_TRUE(collector_.GetStateFromStatus(SplitLines("State:\tR (running)"),
+ &state));
+ EXPECT_EQ("R (running)", state);
+
+ EXPECT_TRUE(collector_.GetStateFromStatus(
+ SplitLines("Name:\tls\nState:\tZ (zombie)\n"), &state));
+ EXPECT_EQ("Z (zombie)", state);
+}
+
+TEST_F(UserCollectorTest, GetUserInfoFromName) {
+ gid_t gid = 100;
+ uid_t uid = 100;
+ EXPECT_TRUE(collector_.GetUserInfoFromName("root", &uid, &gid));
+ EXPECT_EQ(0, uid);
+ EXPECT_EQ(0, gid);
+}
+
+TEST_F(UserCollectorTest, CopyOffProcFilesBadPath) {
+ // Try a path that is not writable.
+ ASSERT_FALSE(collector_.CopyOffProcFiles(pid_, FilePath("/bad/path")));
+ EXPECT_TRUE(FindLog("Could not create /bad/path"));
+}
+
+TEST_F(UserCollectorTest, CopyOffProcFilesBadPid) {
+ FilePath container_path("test/container");
+ ASSERT_FALSE(collector_.CopyOffProcFiles(0, container_path));
+ EXPECT_TRUE(FindLog("Path /proc/0 does not exist"));
+}
+
+TEST_F(UserCollectorTest, CopyOffProcFilesOK) {
+ FilePath container_path("test/container");
+ ASSERT_TRUE(collector_.CopyOffProcFiles(pid_, container_path));
+ EXPECT_FALSE(FindLog("Could not copy"));
+ static struct {
+ const char *name;
+ bool exists;
+ } expectations[] = {
+ { "auxv", true },
+ { "cmdline", true },
+ { "environ", true },
+ { "maps", true },
+ { "mem", false },
+ { "mounts", false },
+ { "sched", false },
+ { "status", true }
+ };
+ for (unsigned i = 0; i < sizeof(expectations)/sizeof(expectations[0]); ++i) {
+ EXPECT_EQ(expectations[i].exists,
+ base::PathExists(
+ container_path.Append(expectations[i].name)));
+ }
+}
+
+TEST_F(UserCollectorTest, ValidateProcFiles) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath container_dir = temp_dir.path();
+
+ // maps file not exists (i.e. GetFileSize fails)
+ EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
+
+ // maps file is empty
+ FilePath maps_file = container_dir.Append("maps");
+ ASSERT_EQ(0, base::WriteFile(maps_file, nullptr, 0));
+ ASSERT_TRUE(base::PathExists(maps_file));
+ EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
+
+ // maps file is not empty
+ const char data[] = "test data";
+ ASSERT_EQ(sizeof(data), base::WriteFile(maps_file, data, sizeof(data)));
+ ASSERT_TRUE(base::PathExists(maps_file));
+ EXPECT_TRUE(collector_.ValidateProcFiles(container_dir));
+}
+
+TEST_F(UserCollectorTest, ValidateCoreFile) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath container_dir = temp_dir.path();
+ FilePath core_file = container_dir.Append("core");
+
+ // Core file does not exist
+ EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
+ collector_.ValidateCoreFile(core_file));
+ char e_ident[EI_NIDENT];
+ e_ident[EI_MAG0] = ELFMAG0;
+ e_ident[EI_MAG1] = ELFMAG1;
+ e_ident[EI_MAG2] = ELFMAG2;
+ e_ident[EI_MAG3] = ELFMAG3;
+#if __WORDSIZE == 32
+ e_ident[EI_CLASS] = ELFCLASS32;
+#elif __WORDSIZE == 64
+ e_ident[EI_CLASS] = ELFCLASS64;
+#else
+#error Unknown/unsupported value of __WORDSIZE.
+#endif
+
+ // Core file has the expected header
+ ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
+ EXPECT_EQ(UserCollector::kErrorNone,
+ collector_.ValidateCoreFile(core_file));
+
+#if __WORDSIZE == 64
+ // 32-bit core file on 64-bit platform
+ e_ident[EI_CLASS] = ELFCLASS32;
+ ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
+ EXPECT_EQ(UserCollector::kErrorUnsupported32BitCoreFile,
+ collector_.ValidateCoreFile(core_file));
+ e_ident[EI_CLASS] = ELFCLASS64;
+#endif
+
+ // Invalid core files
+ ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident) - 1));
+ EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
+ collector_.ValidateCoreFile(core_file));
+
+ e_ident[EI_MAG0] = 0;
+ ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
+ EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
+ collector_.ValidateCoreFile(core_file));
+}
diff --git a/crash_reporter/warn_collector.l b/crash_reporter/warn_collector.l
new file mode 100644
index 0000000..691ef99
--- /dev/null
+++ b/crash_reporter/warn_collector.l
@@ -0,0 +1,322 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This flex program reads /var/log/messages as it grows and saves kernel
+ * warnings to files. It keeps track of warnings it has seen (based on
+ * file/line only, ignoring differences in the stack trace), and reports only
+ * the first warning of each kind, but maintains a count of all warnings by
+ * using their hashes as buckets in a UMA sparse histogram. It also invokes
+ * the crash collector, which collects the warnings and prepares them for later
+ * shipment to the crash server.
+ */
+
+%{
+#include <fcntl.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <sys/inotify.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "metrics/c_metrics_library.h"
+
+int WarnStart(void);
+void WarnEnd(void);
+void WarnInput(char *buf, yy_size_t *result, size_t max_size);
+
+#define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)
+
+%}
+
+/* Define a few useful regular expressions. */
+
+D [0-9]
+PREFIX .*" kernel: [ "*{D}+"."{D}+"]"
+CUT_HERE {PREFIX}" ------------[ cut here".*
+WARNING {PREFIX}" WARNING: at "
+END_TRACE {PREFIX}" ---[ end trace".*
+
+/* Use exclusive start conditions. */
+%x PRE_WARN WARN
+
+%%
+ /* The scanner itself. */
+
+^{CUT_HERE}\n{WARNING} BEGIN(PRE_WARN);
+.|\n /* ignore all other input in state 0 */
+<PRE_WARN>[^ ]+.[^ ]+\n if (WarnStart()) {
+ /* yytext is
+ "file:line func+offset/offset()\n" */
+ BEGIN(WARN); ECHO;
+ } else {
+ BEGIN(0);
+ }
+
+ /* Assume the warning ends at the "end trace" line */
+<WARN>^{END_TRACE}\n ECHO; BEGIN(0); WarnEnd();
+<WARN>^.*\n ECHO;
+
+%%
+
+#define HASH_BITMAP_SIZE (1 << 15) /* size in bits */
+#define HASH_BITMAP_MASK (HASH_BITMAP_SIZE - 1)
+
+const char warn_hist_name[] = "Platform.KernelWarningHashes";
+uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
+CMetricsLibrary metrics_library;
+
+const char *prog_name; /* the name of this program */
+int yyin_fd; /* instead of FILE *yyin to avoid buffering */
+int i_fd; /* for inotify, to detect file changes */
+int testing; /* 1 if running test */
+int filter; /* 1 when using as filter (for development) */
+int fifo; /* 1 when reading from fifo (for devel) */
+int draining; /* 1 when draining renamed log file */
+
+const char *msg_path = "/var/log/messages";
+const char warn_dump_dir[] = "/var/run/kwarn";
+const char *warn_dump_path = "/var/run/kwarn/warning";
+const char *crash_reporter_command;
+
+__attribute__((__format__(__printf__, 1, 2)))
+static void Die(const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ fprintf(stderr, "%s: ", prog_name);
+ vfprintf(stderr, format, ap);
+ exit(1);
+}
+
+static void RunCrashReporter(void) {
+ int status = system(crash_reporter_command);
+ if (status != 0)
+ Die("%s exited with status %d\n", crash_reporter_command, status);
+}
+
+static uint32_t StringHash(const char *string) {
+ uint32_t hash = 0;
+ while (*string != '\0') {
+ hash = (hash << 5) + hash + *string++;
+ }
+ return hash;
+}
+
+/* We expect only a handful of different warnings per boot session, so the
+ * probability of a collision is very low, and statistically it won't matter
+ * (unless warnings with the same hash also happens in tandem, which is even
+ * rarer).
+ */
+static int HashSeen(uint32_t hash) {
+ int word_index = (hash & HASH_BITMAP_MASK) / 32;
+ int bit_index = (hash & HASH_BITMAP_MASK) % 32;
+ return hash_bitmap[word_index] & 1 << bit_index;
+}
+
+static void SetHashSeen(uint32_t hash) {
+ int word_index = (hash & HASH_BITMAP_MASK) / 32;
+ int bit_index = (hash & HASH_BITMAP_MASK) % 32;
+ hash_bitmap[word_index] |= 1 << bit_index;
+}
+
+int WarnStart(void) {
+ uint32_t hash;
+ char *spacep;
+
+ if (filter)
+ return 1;
+
+ hash = StringHash(yytext);
+ if (!(testing || fifo || filter)) {
+ CMetricsLibrarySendSparseToUMA(metrics_library, warn_hist_name, (int) hash);
+ }
+ if (HashSeen(hash))
+ return 0;
+ SetHashSeen(hash);
+
+ yyout = fopen(warn_dump_path, "w");
+ if (yyout == NULL)
+ Die("fopen %s failed: %s\n", warn_dump_path, strerror(errno));
+ spacep = index(yytext, ' ');
+ if (spacep == NULL || spacep[1] == '\0')
+ spacep = "unknown-function";
+ fprintf(yyout, "%08x-%s\n", hash, spacep + 1);
+ return 1;
+}
+
+void WarnEnd(void) {
+ if (filter)
+ return;
+ fclose(yyout);
+ yyout = stdout; /* for debugging */
+ RunCrashReporter();
+}
+
+static void WarnOpenInput(const char *path) {
+ yyin_fd = open(path, O_RDONLY);
+ if (yyin_fd < 0)
+ Die("could not open %s: %s\n", path, strerror(errno));
+ if (!fifo) {
+ /* Go directly to the end of the file. We don't want to parse the same
+ * warnings multiple times on reboot/restart. We might miss some
+ * warnings, but so be it---it's too hard to keep track reliably of the
+ * last parsed position in the syslog.
+ */
+ if (lseek(yyin_fd, 0, SEEK_END) < 0)
+ Die("could not lseek %s: %s\n", path, strerror(errno));
+ /* Set up notification of file growth and rename. */
+ i_fd = inotify_init();
+ if (i_fd < 0)
+ Die("inotify_init: %s\n", strerror(errno));
+ if (inotify_add_watch(i_fd, path, IN_MODIFY | IN_MOVE_SELF) < 0)
+ Die("inotify_add_watch: %s\n", strerror(errno));
+ }
+}
+
+/* We replace the default YY_INPUT() for the following reasons:
+ *
+ * 1. We want to read data as soon as it becomes available, but the default
+ * YY_INPUT() uses buffered I/O.
+ *
+ * 2. We want to block on end of input and wait for the file to grow.
+ *
+ * 3. We want to detect log rotation, and reopen the input file as needed.
+ */
+void WarnInput(char *buf, yy_size_t *result, size_t max_size) {
+ while (1) {
+ ssize_t ret = read(yyin_fd, buf, max_size);
+ if (ret < 0)
+ Die("read: %s", strerror(errno));
+ *result = ret;
+ if (*result > 0 || fifo || filter)
+ return;
+ if (draining) {
+ /* Assume we're done with this log, and move to next
+ * log. Rsyslogd may keep writing to the old log file
+ * for a while, but we don't care since we don't have
+ * to be exact.
+ */
+ close(yyin_fd);
+ if (YYSTATE == WARN) {
+ /* Be conservative in case we lose the warn
+ * terminator during the switch---or we may
+ * collect personally identifiable information.
+ */
+ WarnEnd();
+ }
+ BEGIN(0); /* see above comment */
+ sleep(1); /* avoid race with log rotator */
+ WarnOpenInput(msg_path);
+ draining = 0;
+ continue;
+ }
+ /* Nothing left to read, so we must wait. */
+ struct inotify_event event;
+ while (1) {
+ int n = read(i_fd, &event, sizeof(event));
+ if (n <= 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ Die("inotify: %s\n", strerror(errno));
+ } else
+ break;
+ }
+ if (event.mask & IN_MOVE_SELF) {
+ /* The file has been renamed. Before switching
+ * to the new one, we process any remaining
+ * content of this file.
+ */
+ draining = 1;
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ int result;
+ struct passwd *user;
+ prog_name = argv[0];
+
+ if (argc == 2 && strcmp(argv[1], "--test") == 0)
+ testing = 1;
+ else if (argc == 2 && strcmp(argv[1], "--filter") == 0)
+ filter = 1;
+ else if (argc == 2 && strcmp(argv[1], "--fifo") == 0) {
+ fifo = 1;
+ } else if (argc != 1) {
+ fprintf(stderr,
+ "usage: %s [single-flag]\n"
+ "flags (for testing only):\n"
+ "--fifo\tinput is fifo \"fifo\", output is stdout\n"
+ "--filter\tinput is stdin, output is stdout\n"
+ "--test\trun self-test\n",
+ prog_name);
+ exit(1);
+ }
+
+ metrics_library = CMetricsLibraryNew();
+ CMetricsLibraryInit(metrics_library);
+
+ crash_reporter_command = testing ?
+ "./warn_collector_test_reporter.sh" :
+ "/sbin/crash_reporter --kernel_warning";
+
+ /* When filtering with --filter (for development) use stdin for input.
+ * Otherwise read input from a file or a fifo.
+ */
+ yyin_fd = fileno(stdin);
+ if (testing) {
+ msg_path = "messages";
+ warn_dump_path = "warning";
+ }
+ if (fifo) {
+ msg_path = "fifo";
+ }
+ if (!filter) {
+ WarnOpenInput(msg_path);
+ }
+
+ /* Create directory for dump file. Still need to be root here. */
+ unlink(warn_dump_path);
+ if (!testing && !fifo && !filter) {
+ rmdir(warn_dump_dir);
+ result = mkdir(warn_dump_dir, 0755);
+ if (result < 0)
+ Die("could not create %s: %s\n",
+ warn_dump_dir, strerror(errno));
+ }
+
+ if (0) {
+ /* TODO(semenzato): put this back in once we decide it's safe
+ * to make /var/spool/crash rwxrwxrwx root, or use a different
+ * owner and setuid for the crash reporter as well.
+ */
+
+ /* Get low privilege uid, gid. */
+ user = getpwnam("chronos");
+ if (user == NULL)
+ Die("getpwnam failed\n");
+
+ /* Change dump directory ownership. */
+ if (chown(warn_dump_dir, user->pw_uid, user->pw_gid) < 0)
+ Die("chown: %s\n", strerror(errno));
+
+ /* Drop privileges. */
+ if (setuid(user->pw_uid) < 0) {
+ Die("setuid: %s\n", strerror(errno));
+ }
+ }
+
+ /* Go! */
+ return yylex();
+}
+
+/* Flex should really know not to generate these functions.
+ */
+void UnusedFunctionWarningSuppressor(void) {
+ yyunput(0, 0);
+ (void) input();
+}
diff --git a/crash_reporter/warn_collector_test.c b/crash_reporter/warn_collector_test.c
new file mode 100644
index 0000000..7e25d01
--- /dev/null
+++ b/crash_reporter/warn_collector_test.c
@@ -0,0 +1,14 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Test driver for the warn_collector daemon.
+ */
+#include <stdlib.h>
+
+int main(int ac, char **av) {
+ int status = system("exec \"${SRC}\"/warn_collector_test.sh");
+ return status < 0 ? EXIT_FAILURE : WEXITSTATUS(status);
+}
diff --git a/crash_reporter/warn_collector_test.sh b/crash_reporter/warn_collector_test.sh
new file mode 100755
index 0000000..d9bb6f9
--- /dev/null
+++ b/crash_reporter/warn_collector_test.sh
@@ -0,0 +1,79 @@
+#! /bin/bash
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Test for warn_collector. Run the warn collector in the background, emulate
+# the kernel by appending lines to the log file "messages", and observe the log
+# of the (fake) crash reporter each time is run by the warn collector daemon.
+
+set -e
+
+fail() {
+ printf '[ FAIL ] %b\n' "$*"
+ exit 1
+}
+
+if [[ -z ${SYSROOT} ]]; then
+ fail "SYSROOT must be set for this test to work"
+fi
+: ${OUT:=${PWD}}
+cd "${OUT}"
+PATH=${OUT}:${PATH}
+TESTLOG="${OUT}/warn-test-log"
+
+echo "Testing: $(which warn_collector)"
+
+cleanup() {
+ # Kill daemon (if started) on exit
+ kill %
+}
+
+check_log() {
+ local n_expected=$1
+ if [[ ! -f ${TESTLOG} ]]; then
+ fail "${TESTLOG} was not created"
+ fi
+ if [[ $(wc -l < "${TESTLOG}") -ne ${n_expected} ]]; then
+ fail "expected ${n_expected} lines in ${TESTLOG}, found this instead:
+$(<"${TESTLOG}")"
+ fi
+ if egrep -qv '^[0-9a-f]{8}' "${TESTLOG}"; then
+ fail "found bad lines in ${TESTLOG}:
+$(<"${TESTLOG}")"
+ fi
+}
+
+rm -f "${TESTLOG}"
+cp "${SRC}/warn_collector_test_reporter.sh" .
+cp "${SRC}/TEST_WARNING" .
+cp TEST_WARNING messages
+
+# Start the collector daemon. With the --test option, the daemon reads input
+# from ./messages, writes the warning into ./warning, and invokes
+# ./warn_collector_test_reporter.sh to report the warning.
+warn_collector --test &
+trap cleanup EXIT
+
+# After a while, check that the first warning has been collected.
+sleep 1
+check_log 1
+
+# Add the same warning to messages, verify that it is NOT collected
+cat TEST_WARNING >> messages
+sleep 1
+check_log 1
+
+# Add a slightly different warning to messages, check that it is collected.
+sed s/intel_dp.c/intel_xx.c/ < TEST_WARNING >> messages
+sleep 1
+check_log 2
+
+# Emulate log rotation, add a warning, and check.
+mv messages messages.1
+sed s/intel_dp.c/intel_xy.c/ < TEST_WARNING > messages
+sleep 2
+check_log 3
+
+# Success!
+exit 0
diff --git a/crash_reporter/warn_collector_test_reporter.sh b/crash_reporter/warn_collector_test_reporter.sh
new file mode 100755
index 0000000..d8f3fad
--- /dev/null
+++ b/crash_reporter/warn_collector_test_reporter.sh
@@ -0,0 +1,16 @@
+#! /bin/sh
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Replacement for the crash reporter, for testing. Log the first line of the
+# "warning" file, which by convention contains the warning hash, and remove the
+# file.
+
+set -e
+
+exec 1>> warn-test-log
+exec 2>> warn-test-log
+
+head -1 warning
+rm warning
diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c
index 42f25c7..f467f81 100644
--- a/fs_mgr/fs_mgr.c
+++ b/fs_mgr/fs_mgr.c
@@ -162,10 +162,10 @@
} else if (!strcmp(fs_type, "f2fs")) {
char *f2fs_fsck_argv[] = {
F2FS_FSCK_BIN,
- "-f",
+ "-a",
blk_device
};
- INFO("Running %s -f %s\n", F2FS_FSCK_BIN, blk_device);
+ INFO("Running %s -a %s\n", F2FS_FSCK_BIN, blk_device);
ret = android_fork_execvp_ext(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv,
&status, true, LOG_KLOG | LOG_FILE,
diff --git a/gpttool/Android.mk b/gpttool/Android.mk
deleted file mode 100644
index 64ad945..0000000
--- a/gpttool/Android.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-ifeq ($(HOST_OS),linux)
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := gpttool.c
-LOCAL_STATIC_LIBRARIES := libz
-LOCAL_CFLAGS := -Werror
-
-LOCAL_MODULE := gpttool
-
-include $(BUILD_HOST_EXECUTABLE)
-
-endif
diff --git a/gpttool/gpttool.c b/gpttool/gpttool.c
deleted file mode 100644
index 398362f..0000000
--- a/gpttool/gpttool.c
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
-** Copyright 2011, 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 <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <zlib.h>
-
-#include <linux/fs.h>
-
-typedef unsigned char u8;
-typedef unsigned short u16;
-typedef unsigned int u32;
-typedef unsigned long long u64;
-
-const u8 partition_type_uuid[16] = {
- 0xa2, 0xa0, 0xd0, 0xeb, 0xe5, 0xb9, 0x33, 0x44,
- 0x87, 0xc0, 0x68, 0xb6, 0xb7, 0x26, 0x99, 0xc7,
-};
-
-
-#define EFI_VERSION 0x00010000
-#define EFI_MAGIC "EFI PART"
-#define EFI_ENTRIES 128
-#define EFI_NAMELEN 36
-
-struct efi_header {
- u8 magic[8];
-
- u32 version;
- u32 header_sz;
-
- u32 crc32;
- u32 reserved;
-
- u64 header_lba;
- u64 backup_lba;
- u64 first_lba;
- u64 last_lba;
-
- u8 volume_uuid[16];
-
- u64 entries_lba;
-
- u32 entries_count;
- u32 entries_size;
- u32 entries_crc32;
-} __attribute__((packed));
-
-struct efi_entry {
- u8 type_uuid[16];
- u8 uniq_uuid[16];
- u64 first_lba;
- u64 last_lba;
- u64 attr;
- u16 name[EFI_NAMELEN];
-};
-
-struct ptable {
- u8 mbr[512];
- union {
- struct efi_header header;
- u8 block[512];
- };
- struct efi_entry entry[EFI_ENTRIES];
-};
-
-void get_uuid(u8 *uuid)
-{
- int fd;
- fd = open("/dev/urandom", O_RDONLY);
- read(fd, uuid, 16);
- close(fd);
-}
-
-void init_mbr(u8 *mbr, u32 blocks)
-{
- mbr[0x1be] = 0x00; // nonbootable
- mbr[0x1bf] = 0xFF; // bogus CHS
- mbr[0x1c0] = 0xFF;
- mbr[0x1c1] = 0xFF;
-
- mbr[0x1c2] = 0xEE; // GPT partition
- mbr[0x1c3] = 0xFF; // bogus CHS
- mbr[0x1c4] = 0xFF;
- mbr[0x1c5] = 0xFF;
-
- mbr[0x1c6] = 0x01; // start
- mbr[0x1c7] = 0x00;
- mbr[0x1c8] = 0x00;
- mbr[0x1c9] = 0x00;
-
- memcpy(mbr + 0x1ca, &blocks, sizeof(u32));
-
- mbr[0x1fe] = 0x55;
- mbr[0x1ff] = 0xaa;
-}
-
-int add_ptn(struct ptable *ptbl, u64 first, u64 last, const char *name)
-{
- struct efi_header *hdr = &ptbl->header;
- struct efi_entry *entry = ptbl->entry;
- unsigned n;
-
- if (first < 34) {
- fprintf(stderr,"partition '%s' overlaps partition table\n", name);
- return -1;
- }
-
- if (last > hdr->last_lba) {
- fprintf(stderr,"partition '%s' does not fit on disk\n", name);
- return -1;
- }
- for (n = 0; n < EFI_ENTRIES; n++, entry++) {
- if (entry->type_uuid[0])
- continue;
- memcpy(entry->type_uuid, partition_type_uuid, 16);
- get_uuid(entry->uniq_uuid);
- entry->first_lba = first;
- entry->last_lba = last;
- for (n = 0; (n < EFI_NAMELEN) && *name; n++)
- entry->name[n] = *name++;
- return 0;
- }
- fprintf(stderr,"out of partition table entries\n");
- return -1;
-}
-
-int usage(void)
-{
- fprintf(stderr,
- "usage: gpttool write <disk> [ <partition> ]*\n"
- " gpttool read <disk>\n"
- " gpttool test [ <partition> ]*\n"
- "\n"
- "partition: [<name>]:<size>[kmg] | @<file-of-partitions>\n"
- );
- return 0;
-}
-
-void show(struct ptable *ptbl)
-{
- struct efi_entry *entry = ptbl->entry;
- unsigned n, m;
- char name[EFI_NAMELEN + 1];
-
- fprintf(stderr,"ptn start block end block name\n");
- fprintf(stderr,"---- ------------- ------------- --------------------\n");
-
- for (n = 0; n < EFI_ENTRIES; n++, entry++) {
- if (entry->type_uuid[0] == 0)
- break;
- for (m = 0; m < EFI_NAMELEN; m++) {
- name[m] = entry->name[m] & 127;
- }
- name[m] = 0;
- fprintf(stderr,"#%03d %13lld %13lld %s\n",
- n + 1, entry->first_lba, entry->last_lba, name);
- }
-}
-
-u64 find_next_lba(struct ptable *ptbl)
-{
- struct efi_entry *entry = ptbl->entry;
- unsigned n;
- u64 a = 0;
- for (n = 0; n < EFI_ENTRIES; n++, entry++) {
- if ((entry->last_lba + 1) > a)
- a = entry->last_lba + 1;
- }
- return a;
-}
-
-u64 next_lba = 0;
-
-u64 parse_size(char *sz)
-{
- int l = strlen(sz);
- u64 n = strtoull(sz, 0, 10);
- if (l) {
- switch(sz[l-1]){
- case 'k':
- case 'K':
- n *= 1024;
- break;
- case 'm':
- case 'M':
- n *= (1024 * 1024);
- break;
- case 'g':
- case 'G':
- n *= (1024 * 1024 * 1024);
- break;
- }
- }
- return n;
-}
-
-int parse_ptn(struct ptable *ptbl, char *x)
-{
- char *y = strchr(x, ':');
- u64 sz;
-
- if (!y) {
- fprintf(stderr,"invalid partition entry: %s\n", x);
- return -1;
- }
- *y++ = 0;
-
- if (*y == 0) {
- sz = ptbl->header.last_lba - next_lba;
- } else {
- sz = parse_size(y);
- if (sz & 511) {
- fprintf(stderr,"partition size must be multiple of 512\n");
- return -1;
- }
- sz /= 512;
- }
-
- if (sz == 0) {
- fprintf(stderr,"zero size partitions not allowed\n");
- return -1;
- }
-
- if (x[0] && add_ptn(ptbl, next_lba, next_lba + sz - 1, x))
- return -1;
-
- next_lba = next_lba + sz;
- return 0;
-}
-
-int main(int argc, char **argv)
-{
- struct ptable ptbl;
- struct efi_header *hdr = &ptbl.header;
- u32 n;
- u64 sz;
- int fd;
- const char *device;
- int real_disk = 0;
-
- if (argc < 2)
- return usage();
-
- if (!strcmp(argv[1], "write")) {
- if (argc < 3)
- return usage();
- device = argv[2];
- argc -= 2;
- argv += 2;
- real_disk = 1;
- } else if (!strcmp(argv[1], "test")) {
- argc -= 1;
- argv += 1;
- real_disk = 0;
- sz = 2097152 * 16;
- fprintf(stderr,"< simulating 16GB disk >\n\n");
- } else {
- return usage();
- }
-
- if (real_disk) {
- if (!strcmp(device, "/dev/sda") ||
- !strcmp(device, "/dev/sdb")) {
- fprintf(stderr,"error: refusing to partition sda or sdb\n");
- return -1;
- }
-
- fd = open(device, O_RDWR);
- if (fd < 0) {
- fprintf(stderr,"error: cannot open '%s'\n", device);
- return -1;
- }
- if (ioctl(fd, BLKGETSIZE64, &sz)) {
- fprintf(stderr,"error: cannot query block device size\n");
- return -1;
- }
- sz /= 512;
- fprintf(stderr,"blocks %lld\n", sz);
- }
-
- memset(&ptbl, 0, sizeof(ptbl));
-
- init_mbr(ptbl.mbr, sz - 1);
-
- memcpy(hdr->magic, EFI_MAGIC, sizeof(hdr->magic));
- hdr->version = EFI_VERSION;
- hdr->header_sz = sizeof(struct efi_header);
- hdr->header_lba = 1;
- hdr->backup_lba = sz - 1;
- hdr->first_lba = 34;
- hdr->last_lba = sz - 1;
- get_uuid(hdr->volume_uuid);
- hdr->entries_lba = 2;
- hdr->entries_count = 128;
- hdr->entries_size = sizeof(struct efi_entry);
-
- while (argc > 1) {
- if (argv[1][0] == '@') {
- char line[256], *p;
- FILE *f;
- f = fopen(argv[1] + 1, "r");
- if (!f) {
- fprintf(stderr,"cannot read partitions from '%s\n", argv[1]);
- return -1;
- }
- while (fgets(line, sizeof(line), f)) {
- p = line + strlen(line);
- while (p > line) {
- p--;
- if (*p > ' ')
- break;
- *p = 0;
- }
- p = line;
- while (*p && (*p <= ' '))
- p++;
- if (*p == '#')
- continue;
- if (*p == 0)
- continue;
- if (parse_ptn(&ptbl, p))
- return -1;
- }
- fclose(f);
- } else {
- if (parse_ptn(&ptbl, argv[1]))
- return -1;
- }
- argc--;
- argv++;
- }
-
- n = crc32(0, Z_NULL, 0);
- n = crc32(n, (void*) ptbl.entry, sizeof(ptbl.entry));
- hdr->entries_crc32 = n;
-
- n = crc32(0, Z_NULL, 0);
- n = crc32(n, (void*) &ptbl.header, sizeof(ptbl.header));
- hdr->crc32 = n;
-
- show(&ptbl);
-
- if (real_disk) {
- write(fd, &ptbl, sizeof(ptbl));
- fsync(fd);
-
- if (ioctl(fd, BLKRRPART, 0)) {
- fprintf(stderr,"could not re-read partition table\n");
- }
- close(fd);
- }
- return 0;
-}
diff --git a/include/cutils/android_reboot.h b/include/cutils/android_reboot.h
index 85e1b7e..a3861a0 100644
--- a/include/cutils/android_reboot.h
+++ b/include/cutils/android_reboot.h
@@ -17,6 +17,8 @@
#ifndef __CUTILS_ANDROID_REBOOT_H__
#define __CUTILS_ANDROID_REBOOT_H__
+#include <mntent.h>
+
__BEGIN_DECLS
/* Commands */
@@ -28,6 +30,9 @@
#define ANDROID_RB_PROPERTY "sys.powerctl"
int android_reboot(int cmd, int flags, const char *arg);
+int android_reboot_with_callback(
+ int cmd, int flags, const char *arg,
+ void (*cb_on_remount)(const struct mntent*));
__END_DECLS
diff --git a/include/cutils/sockets.h b/include/cutils/sockets.h
index f8076ca..07d1351 100644
--- a/include/cutils/sockets.h
+++ b/include/cutils/sockets.h
@@ -77,7 +77,7 @@
extern int socket_loopback_client(int port, int type);
extern int socket_network_client(const char *host, int port, int type);
extern int socket_network_client_timeout(const char *host, int port, int type,
- int timeout);
+ int timeout, int* getaddrinfo_error);
extern int socket_loopback_server(int port, int type);
extern int socket_local_server(const char *name, int namespaceId, int type);
extern int socket_local_server_bind(int s, const char *name, int namespaceId);
diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h
index 02fe2b5..0c071ca 100644
--- a/include/private/android_filesystem_config.h
+++ b/include/private/android_filesystem_config.h
@@ -77,6 +77,7 @@
#define AID_SDCARD_ALL 1035 /* access all users external storage */
#define AID_LOGD 1036 /* log daemon */
#define AID_SHARED_RELRO 1037 /* creator of shared GNU RELRO files */
+#define AID_DBUS 1038 /* dbus-daemon IPC broker process */
#define AID_SHELL 2000 /* adb and debug shell user */
#define AID_CACHE 2001 /* cache access */
@@ -168,6 +169,7 @@
{ "sdcard_all", AID_SDCARD_ALL, },
{ "logd", AID_LOGD, },
{ "shared_relro", AID_SHARED_RELRO, },
+ { "dbus", AID_DBUS, },
{ "shell", AID_SHELL, },
{ "cache", AID_CACHE, },
diff --git a/init/bootchart.cpp b/init/bootchart.cpp
index 81b20fa..e5b153a 100644
--- a/init/bootchart.cpp
+++ b/init/bootchart.cpp
@@ -77,8 +77,8 @@
return;
}
- char fingerprint[PROP_VALUE_MAX];
- if (property_get("ro.build.fingerprint", fingerprint) == -1) {
+ std::string fingerprint = property_get("ro.build.fingerprint");
+ if (fingerprint.empty()) {
return;
}
@@ -92,7 +92,7 @@
fprintf(out, "version = Android init 0.8 " __TIME__ "\n");
fprintf(out, "title = Boot chart for Android (%s)\n", date);
fprintf(out, "system.uname = %s %s %s %s\n", uts.sysname, uts.release, uts.version, uts.machine);
- fprintf(out, "system.release = %s\n", fingerprint);
+ fprintf(out, "system.release = %s\n", fingerprint.c_str());
// TODO: use /proc/cpuinfo "model name" line for x86, "Processor" line for arm.
fprintf(out, "system.cpu = %s\n", uts.machine);
fprintf(out, "system.kernel.options = %s\n", kernel_cmdline.c_str());
diff --git a/init/builtins.cpp b/init/builtins.cpp
index ca31c50..d05f046 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -16,7 +16,9 @@
#include <errno.h>
#include <fcntl.h>
+#include <mntent.h>
#include <net/if.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -38,6 +40,7 @@
#include <base/stringprintf.h>
#include <cutils/partition_utils.h>
#include <cutils/android_reboot.h>
+#include <logwrap/logwrap.h>
#include <private/android_filesystem_config.h>
#include "init.h"
@@ -49,6 +52,8 @@
#include "log.h"
#define chmod DO_NOT_USE_CHMOD_USE_FCHMODAT_SYMLINK_NOFOLLOW
+#define UNMOUNT_CHECK_MS 5000
+#define UNMOUNT_CHECK_TIMES 10
int add_environment(const char *name, const char *value);
@@ -57,14 +62,8 @@
static int insmod(const char *filename, char *options)
{
- char filename_val[PROP_VALUE_MAX];
- if (expand_props(filename_val, filename, sizeof(filename_val)) == -1) {
- ERROR("insmod: cannot expand '%s'\n", filename);
- return -EINVAL;
- }
-
std::string module;
- if (!read_file(filename_val, &module)) {
+ if (!read_file(filename, &module)) {
return -1;
}
@@ -109,6 +108,67 @@
}
}
+static void unmount_and_fsck(const struct mntent *entry)
+{
+ if (strcmp(entry->mnt_type, "f2fs") && strcmp(entry->mnt_type, "ext4"))
+ return;
+
+ /* First, lazily unmount the directory. This unmount request finishes when
+ * all processes that open a file or directory in |entry->mnt_dir| exit.
+ */
+ TEMP_FAILURE_RETRY(umount2(entry->mnt_dir, MNT_DETACH));
+
+ /* Next, kill all processes except init, kthreadd, and kthreadd's
+ * children to finish the lazy unmount. Killing all processes here is okay
+ * because this callback function is only called right before reboot().
+ * It might be cleaner to selectively kill processes that actually use
+ * |entry->mnt_dir| rather than killing all, probably by reusing a function
+ * like killProcessesWithOpenFiles() in vold/, but the selinux policy does
+ * not allow init to scan /proc/<pid> files which the utility function
+ * heavily relies on. The policy does not allow the process to execute
+ * killall/pkill binaries either. Note that some processes might
+ * automatically restart after kill(), but that is not really a problem
+ * because |entry->mnt_dir| is no longer visible to such new processes.
+ */
+ service_for_each(service_stop);
+ TEMP_FAILURE_RETRY(kill(-1, SIGKILL));
+
+ int count = 0;
+ while (count++ < UNMOUNT_CHECK_TIMES) {
+ int fd = TEMP_FAILURE_RETRY(open(entry->mnt_fsname, O_RDONLY | O_EXCL));
+ if (fd >= 0) {
+ /* |entry->mnt_dir| has sucessfully been unmounted. */
+ close(fd);
+ break;
+ } else if (errno == EBUSY) {
+ /* Some processes using |entry->mnt_dir| are still alive. Wait for a
+ * while then retry.
+ */
+ TEMP_FAILURE_RETRY(
+ usleep(UNMOUNT_CHECK_MS * 1000 / UNMOUNT_CHECK_TIMES));
+ continue;
+ } else {
+ /* Cannot open the device. Give up. */
+ return;
+ }
+ }
+
+ int st;
+ if (!strcmp(entry->mnt_type, "f2fs")) {
+ const char *f2fs_argv[] = {
+ "/system/bin/fsck.f2fs", "-f", entry->mnt_fsname,
+ };
+ android_fork_execvp_ext(ARRAY_SIZE(f2fs_argv), (char **)f2fs_argv,
+ &st, true, LOG_KLOG, true, NULL);
+ } else if (!strcmp(entry->mnt_type, "ext4")) {
+ const char *ext4_argv[] = {
+ "/system/bin/e2fsck", "-f", "-y", entry->mnt_fsname,
+ };
+ android_fork_execvp_ext(ARRAY_SIZE(ext4_argv), (char **)ext4_argv,
+ &st, true, LOG_KLOG, true, NULL);
+ }
+}
+
int do_class_start(int nargs, char **args)
{
/* Starting a class does not start services
@@ -406,7 +466,7 @@
if (nargs != 2) {
return -1;
}
-
+ const char* fstabfile = args[1];
/*
* Call fs_mgr_mount_all() to mount all filesystems. We fork(2) and
* do the call in the child to provide protection to the main init
@@ -430,7 +490,7 @@
} else if (pid == 0) {
/* child, call fs_mgr_mount_all() */
klog_set_level(6); /* So we can see what fs_mgr_mount_all() does */
- fstab = fs_mgr_read_fstab(args[1]);
+ fstab = fs_mgr_read_fstab(fstabfile);
child_ret = fs_mgr_mount_all(fstab);
fs_mgr_free_fstab(fstab);
if (child_ret == -1) {
@@ -500,15 +560,7 @@
{
const char *name = args[1];
const char *value = args[2];
- char prop_val[PROP_VALUE_MAX];
- int ret;
-
- ret = expand_props(prop_val, value, sizeof(prop_val));
- if (ret) {
- ERROR("cannot expand '%s' while assigning to '%s'\n", value, name);
- return -EINVAL;
- }
- property_set(name, prop_val);
+ property_set(name, value);
return 0;
}
@@ -554,21 +606,16 @@
int do_powerctl(int nargs, char **args)
{
- char command[PROP_VALUE_MAX];
- int res;
+ const char* command = args[1];
int len = 0;
int cmd = 0;
const char *reboot_target;
-
- res = expand_props(command, args[1], sizeof(command));
- if (res) {
- ERROR("powerctl: cannot expand '%s'\n", args[1]);
- return -EINVAL;
- }
+ void (*callback_on_ro_remount)(const struct mntent*) = NULL;
if (strncmp(command, "shutdown", 8) == 0) {
cmd = ANDROID_RB_POWEROFF;
len = 8;
+ callback_on_ro_remount = unmount_and_fsck;
} else if (strncmp(command, "reboot", 6) == 0) {
cmd = ANDROID_RB_RESTART2;
len = 6;
@@ -586,18 +633,13 @@
return -EINVAL;
}
- return android_reboot(cmd, 0, reboot_target);
+ return android_reboot_with_callback(cmd, 0, reboot_target,
+ callback_on_ro_remount);
}
int do_trigger(int nargs, char **args)
{
- char prop_val[PROP_VALUE_MAX];
- int res = expand_props(prop_val, args[1], sizeof(prop_val));
- if (res) {
- ERROR("trigger: cannot expand '%s'\n", args[1]);
- return -EINVAL;
- }
- action_for_each_trigger(prop_val, action_add_queue_tail);
+ action_for_each_trigger(args[1], action_add_queue_tail);
return 0;
}
@@ -652,13 +694,7 @@
{
const char *path = args[1];
const char *value = args[2];
-
- char expanded_value[256];
- if (expand_props(expanded_value, value, sizeof(expanded_value))) {
- ERROR("cannot expand '%s' while writing to '%s'\n", value, path);
- return -EINVAL;
- }
- return write_file(path, expanded_value);
+ return write_file(path, value);
}
int do_copy(int nargs, char **args)
@@ -781,18 +817,12 @@
}
int do_loglevel(int nargs, char **args) {
- int log_level;
- char log_level_str[PROP_VALUE_MAX] = "";
if (nargs != 2) {
ERROR("loglevel: missing argument\n");
return -EINVAL;
}
- if (expand_props(log_level_str, args[1], sizeof(log_level_str))) {
- ERROR("loglevel: cannot expand '%s'\n", args[1]);
- return -EINVAL;
- }
- log_level = atoi(log_level_str);
+ int log_level = atoi(args[1]);
if (log_level < KLOG_ERROR_LEVEL || log_level > KLOG_DEBUG_LEVEL) {
ERROR("loglevel: invalid log level'%d'\n", log_level);
return -EINVAL;
@@ -845,9 +875,8 @@
return -1;
}
- char prop_value[PROP_VALUE_MAX] = {0};
- property_get("ro.crypto.type", prop_value);
- if (strcmp(prop_value, "file")) {
+ std::string prop_value = property_get("ro.crypto.type");
+ if (prop_value != "file") {
return 0;
}
diff --git a/init/init.cpp b/init/init.cpp
index 4d62c87..4be16ea 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -76,7 +76,7 @@
static struct command *cur_command = NULL;
static int have_console;
-static char console_name[PROP_VALUE_MAX] = "/dev/console";
+static std::string console_name = "/dev/console";
static time_t process_needs_restart;
static const char *ENV[32];
@@ -160,7 +160,7 @@
static void open_console()
{
int fd;
- if ((fd = open(console_name, O_RDWR)) < 0) {
+ if ((fd = open(console_name.c_str(), O_RDWR)) < 0) {
fd = open("/dev/null", O_RDWR);
}
ioctl(fd, TIOCSCTTY, 0);
@@ -570,25 +570,37 @@
}
-void build_triggers_string(char *name_str, int length, struct action *cur_action) {
+std::string build_triggers_string(struct action *cur_action) {
+ std::string result;
struct listnode *node;
struct trigger *cur_trigger;
list_for_each(node, &cur_action->triggers) {
cur_trigger = node_to_item(node, struct trigger, nlist);
if (node != cur_action->triggers.next) {
- strlcat(name_str, " " , length);
+ result.push_back(' ');
}
- strlcat(name_str, cur_trigger->name , length);
+ result += cur_trigger->name;
}
+ return result;
+}
+
+bool expand_command_arguments(int nargs, char** args, std::vector<std::string>* expanded_args) {
+ std::vector<std::string>& strs = *expanded_args;
+ strs.resize(nargs);
+ strs[0] = args[0];
+ for (int i = 1; i < nargs; ++i) {
+ if (expand_props(args[i], &strs[i]) == -1) {
+ ERROR("%s: cannot expand '%s'\n", args[0], args[i]);
+ return false;
+ }
+ }
+ return true;
}
void execute_one_command() {
Timer t;
- char cmd_str[256] = "";
- char name_str[256] = "";
-
if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
cur_action = action_remove_queue_head();
cur_command = NULL;
@@ -596,9 +608,8 @@
return;
}
- build_triggers_string(name_str, sizeof(name_str), cur_action);
-
- INFO("processing action %p (%s)\n", cur_action, name_str);
+ std::string trigger_name = build_triggers_string(cur_action);
+ INFO("processing action %p (%s)\n", cur_action, trigger_name.c_str());
cur_command = get_first_command(cur_action);
} else {
cur_command = get_next_command(cur_action, cur_command);
@@ -607,23 +618,35 @@
if (!cur_command) {
return;
}
-
- int result = cur_command->func(cur_command->nargs, cur_command->args);
+ int result = 0;
+ std::vector<std::string> arg_strs;
+ if (!expand_command_arguments(cur_command->nargs, cur_command->args, &arg_strs)) {
+ result = -EINVAL;
+ }
+ if (result == 0) {
+ std::vector<char*> args;
+ for (auto& s : arg_strs) {
+ args.push_back(&s[0]);
+ }
+ result = cur_command->func(args.size(), &args[0]);
+ }
if (klog_get_level() >= KLOG_INFO_LEVEL) {
- for (int i = 0; i < cur_command->nargs; i++) {
- strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));
- if (i < cur_command->nargs - 1) {
- strlcat(cmd_str, " ", sizeof(cmd_str));
+ std::string cmd_str;
+ for (int i = 0; i < cur_command->nargs; ++i) {
+ if (i > 0) {
+ cmd_str.push_back(' ');
}
+ cmd_str += cur_command->args[i];
}
- char source[256];
+ std::string trigger_name = build_triggers_string(cur_action);
+
+ std::string source;
if (cur_command->filename) {
- snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line);
- } else {
- *source = '\0';
+ source = android::base::StringPrintf(" (%s:%d)", cur_command->filename, cur_command->line);
}
+
INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
- cmd_str, cur_action ? name_str : "", source, result, t.duration());
+ cmd_str.c_str(), trigger_name.c_str(), source.c_str(), result, t.duration());
}
}
@@ -727,12 +750,12 @@
static int console_init_action(int nargs, char **args)
{
- char console[PROP_VALUE_MAX];
- if (property_get("ro.boot.console", console) > 0) {
- snprintf(console_name, sizeof(console_name), "/dev/%s", console);
+ std::string console = property_get("ro.boot.console");
+ if (!console.empty()) {
+ console_name = "/dev/" + console;
}
- int fd = open(console_name, O_RDWR | O_CLOEXEC);
+ int fd = open(console_name.c_str(), O_RDWR | O_CLOEXEC);
if (fd >= 0)
have_console = 1;
close(fd);
@@ -793,9 +816,8 @@
{ "ro.boot.revision", "ro.revision", "0", },
};
for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
- char value[PROP_VALUE_MAX];
- int rc = property_get(prop_map[i].src_prop, value);
- property_set(prop_map[i].dst_prop, (rc > 0) ? value : prop_map[i].default_value);
+ std::string value = property_get(prop_map[i].src_prop);
+ property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
}
}
@@ -1035,7 +1057,7 @@
property_load_boot_defaults();
start_property_service();
- init_parse_config_file("/init.rc");
+ init_parse_config("/init.rc");
action_for_each_trigger("early-init", action_add_queue_tail);
@@ -1054,8 +1076,8 @@
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
- char bootmode[PROP_VALUE_MAX];
- if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
+ std::string bootmode = property_get("ro.bootmode");
+ if (bootmode == "charger") {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
diff --git a/init/init.h b/init/init.h
index c166969..d2b2dfb 100644
--- a/init/init.h
+++ b/init/init.h
@@ -138,7 +138,7 @@
extern struct selabel_handle *sehandle;
extern struct selabel_handle *sehandle_prop;
-void build_triggers_string(char *name_str, int length, struct action *cur_action);
+std::string build_triggers_string(struct action *cur_action);
void handle_control_message(const char *msg, const char *arg);
@@ -161,5 +161,6 @@
void zap_stdio(void);
void register_epoll_handler(int fd, void (*fn)());
+bool expand_command_arguments(int nargs, char** args, std::vector<std::string>* expanded_args);
#endif /* _INIT_INIT_H */
diff --git a/init/init_parser.cpp b/init/init_parser.cpp
index 956ed25..460f5ac 100644
--- a/init/init_parser.cpp
+++ b/init/init_parser.cpp
@@ -15,6 +15,7 @@
*/
#include <ctype.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -32,6 +33,7 @@
#include "property_service.h"
#include "util.h"
+#include <base/stringprintf.h>
#include <cutils/iosched_policy.h>
#include <cutils/list.h>
@@ -95,9 +97,8 @@
list_for_each(node, &action_list) {
action* act = node_to_item(node, struct action, alist);
INFO("on ");
- char name_str[256] = "";
- build_triggers_string(name_str, sizeof(name_str), act);
- INFO("%s", name_str);
+ std::string trigger_name = build_triggers_string(act);
+ INFO("%s", trigger_name.c_str());
INFO("\n");
struct listnode* node2;
@@ -216,27 +217,12 @@
static void parse_line_no_op(struct parse_state*, int, char**) {
}
-static int push_chars(char **dst, int *len, const char *chars, int cnt)
-{
- if (cnt > *len)
- return -1;
-
- memcpy(*dst, chars, cnt);
- *dst += cnt;
- *len -= cnt;
-
- return 0;
-}
-
-int expand_props(char *dst, const char *src, int dst_size)
-{
- char *dst_ptr = dst;
+int expand_props(const char *src, std::string *dst) {
const char *src_ptr = src;
- int ret = 0;
- int left = dst_size - 1;
- if (!src || !dst || dst_size == 0)
+ if (!src || !dst) {
return -1;
+ }
/* - variables can either be $x.y or ${x.y}, in case they are only part
* of the string.
@@ -244,104 +230,75 @@
* - no nested property expansion, i.e. ${foo.${bar}} is not supported,
* bad things will happen
*/
- while (*src_ptr && left > 0) {
- char *c;
- char prop[PROP_NAME_MAX + 1];
- char prop_val[PROP_VALUE_MAX];
- int prop_len = 0;
- int prop_val_len;
+ while (*src_ptr) {
+ const char *c;
c = strchr(src_ptr, '$');
if (!c) {
- while (left-- > 0 && *src_ptr)
- *(dst_ptr++) = *(src_ptr++);
+ dst->append(src_ptr);
break;
}
- memset(prop, 0, sizeof(prop));
-
- ret = push_chars(&dst_ptr, &left, src_ptr, c - src_ptr);
- if (ret < 0)
- goto err_nospace;
+ dst->append(src_ptr, c);
c++;
if (*c == '$') {
- *(dst_ptr++) = *(c++);
+ dst->push_back(*(c++));
src_ptr = c;
- left--;
continue;
} else if (*c == '\0') {
break;
}
+ std::string prop_name;
if (*c == '{') {
c++;
- while (*c && *c != '}' && prop_len < PROP_NAME_MAX)
- prop[prop_len++] = *(c++);
- if (*c != '}') {
- /* failed to find closing brace, abort. */
- if (prop_len == PROP_NAME_MAX)
- ERROR("prop name too long during expansion of '%s'\n",
- src);
- else if (*c == '\0')
- ERROR("unexpected end of string in '%s', looking for }\n",
- src);
+ const char* end = strchr(c, '}');
+ if (!end) {
+ // failed to find closing brace, abort.
+ ERROR("unexpected end of string in '%s', looking for }\n", src);
goto err;
}
- prop[prop_len] = '\0';
- c++;
- } else if (*c) {
- while (*c && prop_len < PROP_NAME_MAX)
- prop[prop_len++] = *(c++);
- if (prop_len == PROP_NAME_MAX && *c != '\0') {
- ERROR("prop name too long in '%s'\n", src);
- goto err;
- }
- prop[prop_len] = '\0';
+ prop_name = std::string(c, end);
+ c = end + 1;
+ } else {
+ prop_name = c;
ERROR("using deprecated syntax for specifying property '%s', use ${name} instead\n",
- prop);
+ c);
+ c += prop_name.size();
}
- if (prop_len == 0) {
+ if (prop_name.empty()) {
ERROR("invalid zero-length prop name in '%s'\n", src);
goto err;
}
- prop_val_len = property_get(prop, prop_val);
- if (!prop_val_len) {
+ std::string prop_val = property_get(prop_name.c_str());
+ if (prop_val.empty()) {
ERROR("property '%s' doesn't exist while expanding '%s'\n",
- prop, src);
+ prop_name.c_str(), src);
goto err;
}
- ret = push_chars(&dst_ptr, &left, prop_val, prop_val_len);
- if (ret < 0)
- goto err_nospace;
+ dst->append(prop_val);
src_ptr = c;
continue;
}
- *dst_ptr = '\0';
return 0;
-
-err_nospace:
- ERROR("destination buffer overflow while expanding '%s'\n", src);
err:
return -1;
}
static void parse_import(struct parse_state *state, int nargs, char **args)
{
- struct listnode *import_list = (listnode*) state->priv;
- char conf_file[PATH_MAX];
- int ret;
-
if (nargs != 2) {
ERROR("single argument needed for import\n");
return;
}
- ret = expand_props(conf_file, args[1], sizeof(conf_file));
+ std::string conf_file;
+ int ret = expand_props(args[1], &conf_file);
if (ret) {
ERROR("error while handling import on line '%d' in '%s'\n",
state->line, state->filename);
@@ -349,7 +306,9 @@
}
struct import* import = (struct import*) calloc(1, sizeof(struct import));
- import->filename = strdup(conf_file);
+ import->filename = strdup(conf_file.c_str());
+
+ struct listnode *import_list = (listnode*) state->priv;
list_add_tail(import_list, &import->list);
INFO("Added '%s' to import list\n", import->filename);
}
@@ -428,15 +387,15 @@
parser_done:
list_for_each(node, &import_list) {
struct import* import = node_to_item(node, struct import, list);
- if (!init_parse_config_file(import->filename)) {
+ if (!init_parse_config(import->filename)) {
ERROR("could not import file '%s' from '%s': %s\n",
import->filename, fn, strerror(errno));
}
}
}
-bool init_parse_config_file(const char* path) {
- INFO("Parsing %s...\n", path);
+static bool init_parse_config_file(const char* path) {
+ INFO("Parsing file %s...\n", path);
Timer t;
std::string data;
if (!read_file(path, &data)) {
@@ -451,6 +410,34 @@
return true;
}
+static bool init_parse_config_dir(const char* path) {
+ INFO("Parsing directory %s...\n", path);
+ std::unique_ptr<DIR, int(*)(DIR*)> config_dir(opendir(path), closedir);
+ if (!config_dir) {
+ ERROR("Could not import directory '%s'\n", path);
+ return false;
+ }
+ dirent* current_file;
+ while ((current_file = readdir(config_dir.get()))) {
+ std::string current_path =
+ android::base::StringPrintf("%s/%s", path, current_file->d_name);
+ // Ignore directories and only process regular files.
+ if (current_file->d_type == DT_REG) {
+ if (!init_parse_config_file(current_path.c_str())) {
+ ERROR("could not import file '%s'\n", current_path.c_str());
+ }
+ }
+ }
+ return true;
+}
+
+bool init_parse_config(const char* path) {
+ if (is_dir(path)) {
+ return init_parse_config_dir(path);
+ }
+ return init_parse_config_file(path);
+}
+
static int valid_name(const char *name)
{
if (strlen(name) > 16) {
@@ -586,17 +573,13 @@
} else {
const char* equals = strchr(test, '=');
if (equals) {
- char prop_name[PROP_NAME_MAX + 1];
- char value[PROP_VALUE_MAX];
int length = equals - test;
if (length <= PROP_NAME_MAX) {
- int ret;
- memcpy(prop_name, test, length);
- prop_name[length] = 0;
+ std::string prop_name(test, length);
+ std::string value = property_get(prop_name.c_str());
/* does the property exist, and match the trigger value? */
- ret = property_get(prop_name, value);
- if (ret > 0 && (!strcmp(equals + 1, value) ||
+ if (!value.empty() && (!strcmp(equals + 1, value.c_str()) ||
!strcmp(equals + 1, "*"))) {
continue;
}
diff --git a/init/init_parser.h b/init/init_parser.h
index 90f880f..1ebb1ef 100644
--- a/init/init_parser.h
+++ b/init/init_parser.h
@@ -17,6 +17,8 @@
#ifndef _INIT_INIT_PARSER_H_
#define _INIT_INIT_PARSER_H_
+#include <string>
+
#define INIT_PARSER_MAXARGS 64
struct action;
@@ -31,8 +33,8 @@
void queue_all_property_triggers();
void queue_builtin_action(int (*func)(int nargs, char **args), const char *name);
-bool init_parse_config_file(const char* path);
-int expand_props(char *dst, const char *src, int len);
+bool init_parse_config(const char* path);
+int expand_props(const char *src, std::string *dst);
service* make_exec_oneshot_service(int argc, char** argv);
diff --git a/init/keychords.cpp b/init/keychords.cpp
index 10d9573..c4ebdf9 100644
--- a/init/keychords.cpp
+++ b/init/keychords.cpp
@@ -64,19 +64,18 @@
static void handle_keychord() {
struct service *svc;
- char adb_enabled[PROP_VALUE_MAX];
int ret;
__u16 id;
- // Only handle keychords if adb is enabled.
- property_get("init.svc.adbd", adb_enabled);
ret = read(keychord_fd, &id, sizeof(id));
if (ret != sizeof(id)) {
ERROR("could not read keychord id\n");
return;
}
- if (!strcmp(adb_enabled, "running")) {
+ // Only handle keychords if adb is enabled.
+ std::string adb_enabled = property_get("init.svc.adbd");
+ if (adb_enabled == "running") {
svc = service_find_by_keychord(id);
if (svc) {
INFO("Starting service %s from keychord\n", svc->name);
diff --git a/init/log.h b/init/log.h
index b804d1f..c5c30af 100644
--- a/init/log.h
+++ b/init/log.h
@@ -20,8 +20,11 @@
#include <cutils/klog.h>
#define ERROR(x...) init_klog_write(KLOG_ERROR_LEVEL, x)
+#define WARNING(x...) init_klog_write(KLOG_WARNING_LEVEL, x)
#define NOTICE(x...) init_klog_write(KLOG_NOTICE_LEVEL, x)
#define INFO(x...) init_klog_write(KLOG_INFO_LEVEL, x)
+#define DEBUG(x...) init_klog_write(KLOG_DEBUG_LEVEL, x)
+#define VERBOSE(x...) init_klog_write(KLOG_DEBUG_LEVEL, x)
void init_klog_write(int level, const char* fmt, ...) __printflike(2, 3);
int selinux_klog_callback(int level, const char* fmt, ...) __printflike(2, 3);
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 5b7a1cb..aa939a5 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -141,9 +141,10 @@
return check_mac_perms(name, sctx);
}
-int __property_get(const char *name, char *value)
-{
- return __system_property_get(name, value);
+std::string property_get(const char* name) {
+ char value[PROP_VALUE_MAX] = {0};
+ __system_property_get(name, value);
+ return value;
}
static void write_persistent_property(const char *name, const char *value)
@@ -491,9 +492,8 @@
static void load_override_properties() {
if (ALLOW_LOCAL_PROP_OVERRIDE) {
- char debuggable[PROP_VALUE_MAX];
- int ret = property_get("ro.debuggable", debuggable);
- if (ret && (strcmp(debuggable, "1") == 0)) {
+ std::string debuggable = property_get("ro.debuggable");
+ if (debuggable == "1") {
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL);
}
}
@@ -511,19 +511,17 @@
}
void load_recovery_id_prop() {
- char fstab_filename[PROP_VALUE_MAX + sizeof(FSTAB_PREFIX)];
- char propbuf[PROP_VALUE_MAX];
- int ret = property_get("ro.hardware", propbuf);
- if (!ret) {
+ std::string ro_hardware = property_get("ro.hardware");
+ if (ro_hardware.empty()) {
ERROR("ro.hardware not set - unable to load recovery id\n");
return;
}
- snprintf(fstab_filename, sizeof(fstab_filename), FSTAB_PREFIX "%s", propbuf);
+ std::string fstab_filename = FSTAB_PREFIX + ro_hardware;
- std::unique_ptr<fstab, void(*)(fstab*)> tab(fs_mgr_read_fstab(fstab_filename),
+ std::unique_ptr<fstab, void(*)(fstab*)> tab(fs_mgr_read_fstab(fstab_filename.c_str()),
fs_mgr_free_fstab);
if (!tab) {
- ERROR("unable to read fstab %s: %s\n", fstab_filename, strerror(errno));
+ ERROR("unable to read fstab %s: %s\n", fstab_filename.c_str(), strerror(errno));
return;
}
diff --git a/init/property_service.h b/init/property_service.h
index a27053d..51d7404 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -19,6 +19,7 @@
#include <stddef.h>
#include <sys/system_properties.h>
+#include <string>
extern void property_init(void);
extern void property_load_boot_defaults(void);
@@ -26,30 +27,9 @@
extern void load_all_props(void);
extern void start_property_service(void);
void get_property_workspace(int *fd, int *sz);
-extern int __property_get(const char *name, char *value);
+std::string property_get(const char* name);
extern int property_set(const char *name, const char *value);
extern bool properties_initialized();
-#ifndef __clang__
-extern void __property_get_size_error()
- __attribute__((__error__("property_get called with too small buffer")));
-#else
-extern void __property_get_size_error();
-#endif
-
-static inline
-__attribute__ ((always_inline))
-__attribute__ ((gnu_inline))
-#ifndef __clang__
-__attribute__ ((artificial))
-#endif
-int property_get(const char *name, char *value)
-{
- size_t value_len = __builtin_object_size(value, 0);
- if (value_len != PROP_VALUE_MAX)
- __property_get_size_error();
-
- return __property_get(name, value);
-}
#endif /* _INIT_PROPERTY_H */
diff --git a/init/readme.txt b/init/readme.txt
index 4dda340..5a758d7 100644
--- a/init/readme.txt
+++ b/init/readme.txt
@@ -197,8 +197,11 @@
ifup <interface>
Bring the network interface <interface> online.
-import <filename>
+import <path>
Parse an init config file, extending the current configuration.
+ If <path> is a directory, each file in the directory is parsed as
+ a config file. It is not recursive, nested directories will
+ not be parsed.
insmod <path>
Install the module at <path>
diff --git a/init/signal_handler.cpp b/init/signal_handler.cpp
index 39a466d..6893163 100644
--- a/init/signal_handler.cpp
+++ b/init/signal_handler.cpp
@@ -136,7 +136,14 @@
struct listnode* node;
list_for_each(node, &svc->onrestart.commands) {
command* cmd = node_to_item(node, struct command, clist);
- cmd->func(cmd->nargs, cmd->args);
+ std::vector<std::string> arg_strs;
+ if (expand_command_arguments(cmd->nargs, cmd->args, &arg_strs)) {
+ std::vector<char*> args;
+ for (auto& s : arg_strs) {
+ args.push_back(&s[0]);
+ }
+ cmd->func(args.size(), &args[0]);
+ }
}
svc->NotifyStateChange("restarting");
return true;
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index c63fdaa..75924cb 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -59,11 +59,10 @@
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
- char hardware[PROP_VALUE_MAX];
- property_get("ro.hardware", hardware);
+ std::string hardware = property_get("ro.hardware");
ueventd_parse_config_file("/ueventd.rc");
- ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware).c_str());
+ ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware.c_str()).c_str());
device_init();
diff --git a/init/util.cpp b/init/util.cpp
index 7f29e94..f6131e3 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -465,3 +465,14 @@
android::base::StringAppendF(&hex, "%02x", bytes[i]);
return hex;
}
+
+/*
+ * Returns true is pathname is a directory
+ */
+bool is_dir(const char* pathname) {
+ struct stat info;
+ if (stat(pathname, &info) == -1) {
+ return false;
+ }
+ return S_ISDIR(info.st_mode);
+}
diff --git a/init/util.h b/init/util.h
index 3aba599..f08cb8d 100644
--- a/init/util.h
+++ b/init/util.h
@@ -64,4 +64,5 @@
int restorecon(const char *pathname);
int restorecon_recursive(const char *pathname);
std::string bytes_to_hex(const uint8_t *bytes, size_t bytes_len);
+bool is_dir(const char* pathname);
#endif
diff --git a/init/watchdogd.cpp b/init/watchdogd.cpp
index 881a4df..0d16db9 100644
--- a/init/watchdogd.cpp
+++ b/init/watchdogd.cpp
@@ -38,29 +38,30 @@
int margin = 10;
if (argc >= 3) margin = atoi(argv[2]);
- NOTICE("watchdogd started (interval %d, margin %d)!\n", interval, margin);
+ NOTICE("started (interval %d, margin %d)!\n", interval, margin);
int fd = open(DEV_NAME, O_RDWR|O_CLOEXEC);
if (fd == -1) {
- ERROR("watchdogd: Failed to open %s: %s\n", DEV_NAME, strerror(errno));
+ ERROR("Failed to open %s: %s\n", DEV_NAME, strerror(errno));
return 1;
}
int timeout = interval + margin;
int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
if (ret) {
- ERROR("watchdogd: Failed to set timeout to %d: %s\n", timeout, strerror(errno));
+ ERROR("Failed to set timeout to %d: %s\n", timeout, strerror(errno));
ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
if (ret) {
- ERROR("watchdogd: Failed to get timeout: %s\n", strerror(errno));
+ ERROR("Failed to get timeout: %s\n", strerror(errno));
} else {
if (timeout > margin) {
interval = timeout - margin;
} else {
interval = 1;
}
- ERROR("watchdogd: Adjusted interval to timeout returned by driver: timeout %d, interval %d, margin %d\n",
- timeout, interval, margin);
+ WARNING("Adjusted interval to timeout returned by driver:"
+ " timeout %d, interval %d, margin %d\n",
+ timeout, interval, margin);
}
}
diff --git a/libcutils/Android.mk b/libcutils/Android.mk
index d5a9050..5330949 100644
--- a/libcutils/Android.mk
+++ b/libcutils/Android.mk
@@ -73,7 +73,7 @@
LOCAL_SRC_FILES := $(commonSources) $(commonHostSources) dlmalloc_stubs.c
LOCAL_STATIC_LIBRARIES := liblog
ifneq ($(HOST_OS),windows)
-LOCAL_CFLAGS += -Werror
+LOCAL_CFLAGS += -Werror -Wall -Wextra
endif
LOCAL_MULTILIB := both
include $(BUILD_HOST_STATIC_LIBRARY)
@@ -83,7 +83,7 @@
LOCAL_SRC_FILES := $(commonSources) $(commonHostSources) dlmalloc_stubs.c
LOCAL_SHARED_LIBRARIES := liblog
ifneq ($(HOST_OS),windows)
-LOCAL_CFLAGS += -Werror
+LOCAL_CFLAGS += -Werror -Wall -Wextra
endif
LOCAL_MULTILIB := both
include $(BUILD_HOST_SHARED_LIBRARY)
@@ -122,7 +122,7 @@
LOCAL_C_INCLUDES := $(libcutils_c_includes)
LOCAL_STATIC_LIBRARIES := liblog
-LOCAL_CFLAGS += -Werror -std=gnu90
+LOCAL_CFLAGS += -Werror -Wall -Wextra -std=gnu90
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
@@ -131,7 +131,7 @@
# liblog symbols present in libcutils.
LOCAL_WHOLE_STATIC_LIBRARIES := libcutils liblog
LOCAL_SHARED_LIBRARIES := liblog
-LOCAL_CFLAGS += -Werror
+LOCAL_CFLAGS += -Werror -Wall -Wextra
LOCAL_C_INCLUDES := $(libcutils_c_includes)
include $(BUILD_SHARED_LIBRARY)
diff --git a/libcutils/android_reboot.c b/libcutils/android_reboot.c
index 6ae23c1..af7e189 100644
--- a/libcutils/android_reboot.c
+++ b/libcutils/android_reboot.c
@@ -14,43 +14,108 @@
* limitations under the License.
*/
-#include <unistd.h>
-#include <sys/reboot.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/stat.h>
+#include <errno.h>
#include <fcntl.h>
#include <mntent.h>
+#include <stdbool.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <cutils/android_reboot.h>
+#include <cutils/klog.h>
+#include <cutils/list.h>
-#define UNUSED __attribute__((unused))
+#define TAG "android_reboot"
+#define READONLY_CHECK_MS 5000
+#define READONLY_CHECK_TIMES 50
-/* Check to see if /proc/mounts contains any writeable filesystems
- * backed by a block device.
- * Return true if none found, else return false.
+typedef struct {
+ struct listnode list;
+ struct mntent entry;
+} mntent_list;
+
+static bool has_mount_option(const char* opts, const char* opt_to_find)
+{
+ bool ret = false;
+ char* copy = NULL;
+ char* opt;
+ char* rem;
+
+ while ((opt = strtok_r(copy ? NULL : (copy = strdup(opts)), ",", &rem))) {
+ if (!strcmp(opt, opt_to_find)) {
+ ret = true;
+ break;
+ }
+ }
+
+ free(copy);
+ return ret;
+}
+
+static bool is_block_device(const char* fsname)
+{
+ return !strncmp(fsname, "/dev/block", 10);
+}
+
+/* Find all read+write block devices in /proc/mounts and add them to
+ * |rw_entries|.
*/
-static int remount_ro_done(void)
+static void find_rw(struct listnode* rw_entries)
{
FILE* fp;
struct mntent* mentry;
- int found_rw_fs = 0;
if ((fp = setmntent("/proc/mounts", "r")) == NULL) {
- /* If we can't read /proc/mounts, just give up. */
- return 1;
+ KLOG_WARNING(TAG, "Failed to open /proc/mounts.\n");
+ return;
}
while ((mentry = getmntent(fp)) != NULL) {
- if (!strncmp(mentry->mnt_fsname, "/dev/block", 10) && strstr(mentry->mnt_opts, "rw,")) {
- found_rw_fs = 1;
- break;
+ if (is_block_device(mentry->mnt_fsname) &&
+ has_mount_option(mentry->mnt_opts, "rw")) {
+ mntent_list* item = (mntent_list*)calloc(1, sizeof(mntent_list));
+ item->entry = *mentry;
+ item->entry.mnt_fsname = strdup(mentry->mnt_fsname);
+ item->entry.mnt_dir = strdup(mentry->mnt_dir);
+ item->entry.mnt_type = strdup(mentry->mnt_type);
+ item->entry.mnt_opts = strdup(mentry->mnt_opts);
+ list_add_tail(rw_entries, &item->list);
}
}
endmntent(fp);
+}
- return !found_rw_fs;
+static void free_entries(struct listnode* entries)
+{
+ struct listnode* node;
+ struct listnode* n;
+ list_for_each_safe(node, n, entries) {
+ mntent_list* item = node_to_item(node, mntent_list, list);
+ free(item->entry.mnt_fsname);
+ free(item->entry.mnt_dir);
+ free(item->entry.mnt_type);
+ free(item->entry.mnt_opts);
+ free(item);
+ }
+}
+
+static mntent_list* find_item(struct listnode* rw_entries, const char* fsname_to_find)
+{
+ struct listnode* node;
+ list_for_each(node, rw_entries) {
+ mntent_list* item = node_to_item(node, mntent_list, list);
+ if (!strcmp(item->entry.mnt_fsname, fsname_to_find)) {
+ return item;
+ }
+ }
+ return NULL;
}
/* Remounting filesystems read-only is difficult when there are files
@@ -64,38 +129,92 @@
* repeatedly until there are no more writable filesystems mounted on
* block devices.
*/
-static void remount_ro(void)
+static void remount_ro(void (*cb_on_remount)(const struct mntent*))
{
- int fd, cnt = 0;
+ int fd, cnt;
+ FILE* fp;
+ struct mntent* mentry;
+ struct listnode* node;
+
+ list_declare(rw_entries);
+ list_declare(ro_entries);
+
+ sync();
+ find_rw(&rw_entries);
/* Trigger the remount of the filesystems as read-only,
* which also marks them clean.
*/
- fd = open("/proc/sysrq-trigger", O_WRONLY);
+ fd = TEMP_FAILURE_RETRY(open("/proc/sysrq-trigger", O_WRONLY));
if (fd < 0) {
- return;
+ KLOG_WARNING(TAG, "Failed to open sysrq-trigger.\n");
+ /* TODO: Try to remount each rw parition manually in readonly mode.
+ * This may succeed if no process is using the partition.
+ */
+ goto out;
}
- write(fd, "u", 1);
+ if (TEMP_FAILURE_RETRY(write(fd, "u", 1)) != 1) {
+ close(fd);
+ KLOG_WARNING(TAG, "Failed to write to sysrq-trigger.\n");
+ /* TODO: The same. Manually remount the paritions. */
+ goto out;
+ }
close(fd);
-
/* Now poll /proc/mounts till it's done */
- while (!remount_ro_done() && (cnt < 50)) {
- usleep(100000);
+ cnt = 0;
+ while (cnt < READONLY_CHECK_TIMES) {
+ if ((fp = setmntent("/proc/mounts", "r")) == NULL) {
+ /* If we can't read /proc/mounts, just give up. */
+ KLOG_WARNING(TAG, "Failed to open /proc/mounts.\n");
+ goto out;
+ }
+ while ((mentry = getmntent(fp)) != NULL) {
+ if (!is_block_device(mentry->mnt_fsname) ||
+ !has_mount_option(mentry->mnt_opts, "ro")) {
+ continue;
+ }
+ mntent_list* item = find_item(&rw_entries, mentry->mnt_fsname);
+ if (item) {
+ /* |item| has now been ro remounted. */
+ list_remove(&item->list);
+ list_add_tail(&ro_entries, &item->list);
+ }
+ }
+ endmntent(fp);
+ if (list_empty(&rw_entries)) {
+ /* All rw block devices are now readonly. */
+ break;
+ }
+ TEMP_FAILURE_RETRY(
+ usleep(READONLY_CHECK_MS * 1000 / READONLY_CHECK_TIMES));
cnt++;
}
- return;
+ list_for_each(node, &rw_entries) {
+ mntent_list* item = node_to_item(node, mntent_list, list);
+ KLOG_WARNING(TAG, "Failed to remount %s in readonly mode.\n",
+ item->entry.mnt_fsname);
+ }
+
+ if (cb_on_remount) {
+ list_for_each(node, &ro_entries) {
+ mntent_list* item = node_to_item(node, mntent_list, list);
+ cb_on_remount(&item->entry);
+ }
+ }
+
+out:
+ free_entries(&rw_entries);
+ free_entries(&ro_entries);
}
-
-int android_reboot(int cmd, int flags UNUSED, const char *arg)
+int android_reboot_with_callback(
+ int cmd, int flags __unused, const char *arg,
+ void (*cb_on_remount)(const struct mntent*))
{
int ret;
-
- sync();
- remount_ro();
-
+ remount_ro(cb_on_remount);
switch (cmd) {
case ANDROID_RB_RESTART:
ret = reboot(RB_AUTOBOOT);
@@ -117,3 +236,7 @@
return ret;
}
+int android_reboot(int cmd, int flags, const char *arg)
+{
+ return android_reboot_with_callback(cmd, flags, arg, NULL);
+}
diff --git a/libcutils/fs_config.c b/libcutils/fs_config.c
index 045d1d1..5f6f8f9 100644
--- a/libcutils/fs_config.c
+++ b/libcutils/fs_config.c
@@ -161,8 +161,7 @@
const char *out = getenv("OUT");
if (out && *out) {
char *name = NULL;
- asprintf(&name, "%s%s", out, dir ? conf_dir : conf_file);
- if (name) {
+ if (asprintf(&name, "%s%s", out, dir ? conf_dir : conf_file) != -1) {
fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_BINARY));
free(name);
}
diff --git a/libcutils/socket_network_client.c b/libcutils/socket_network_client.c
index 2610254..3300b8f 100644
--- a/libcutils/socket_network_client.c
+++ b/libcutils/socket_network_client.c
@@ -41,7 +41,10 @@
// Connect to the given host and port.
// 'timeout' is in seconds (0 for no timeout).
// Returns a file descriptor or -1 on error.
-int socket_network_client_timeout(const char* host, int port, int type, int timeout) {
+// On error, check *getaddrinfo_error (for use with gai_strerror) first;
+// if that's 0, use errno instead.
+int socket_network_client_timeout(const char* host, int port, int type, int timeout,
+ int* getaddrinfo_error) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
@@ -51,7 +54,8 @@
snprintf(port_str, sizeof(port_str), "%d", port);
struct addrinfo* addrs;
- if (getaddrinfo(host, port_str, &hints, &addrs) != 0) {
+ *getaddrinfo_error = getaddrinfo(host, port_str, &hints, &addrs);
+ if (*getaddrinfo_error != 0) {
return -1;
}
@@ -116,5 +120,6 @@
}
int socket_network_client(const char* host, int port, int type) {
- return socket_network_client_timeout(host, port, type, 0);
+ int getaddrinfo_error;
+ return socket_network_client_timeout(host, port, type, 0, &getaddrinfo_error);
}
diff --git a/liblog/Android.mk b/liblog/Android.mk
index ce282bd..930dcf7 100644
--- a/liblog/Android.mk
+++ b/liblog/Android.mk
@@ -24,11 +24,7 @@
# so make sure we do not regret hard-coding it as follows:
liblog_cflags := -DLIBLOG_LOG_TAG=1005
-ifneq ($(TARGET_USES_LOGD),false)
liblog_sources := logd_write.c
-else
-liblog_sources := logd_write_kern.c
-endif
# some files must not be compiled when building against Mingw
# they correspond to features not used by our host development tools
@@ -47,11 +43,7 @@
ifeq ($(strip $(USE_MINGW)),)
liblog_target_sources += logprint.c
endif
-ifneq ($(TARGET_USES_LOGD),false)
liblog_target_sources += log_read.c
-else
-liblog_target_sources += log_read_kern.c
-endif
# Shared and static library for host
# ========================================================
diff --git a/liblog/log_read_kern.c b/liblog/log_read_kern.c
deleted file mode 100644
index 69b405c..0000000
--- a/liblog/log_read_kern.c
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
-** Copyright 2013-2014, 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 _GNU_SOURCE /* asprintf for x86 host */
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/cdefs.h>
-#include <sys/ioctl.h>
-
-#include <cutils/list.h>
-#include <log/log.h>
-#include <log/logger.h>
-
-#define __LOGGERIO 0xAE
-
-#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */
-#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */
-#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */
-#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */
-#define LOGGER_GET_VERSION _IO(__LOGGERIO, 5) /* abi version */
-#define LOGGER_SET_VERSION _IO(__LOGGERIO, 6) /* abi version */
-
-typedef char bool;
-#define false (const bool)0
-#define true (const bool)1
-
-#define LOG_FILE_DIR "/dev/log/"
-
-/* timeout in milliseconds */
-#define LOG_TIMEOUT_FLUSH 5
-#define LOG_TIMEOUT_NEVER -1
-
-#define logger_for_each(logger, logger_list) \
- for (logger = node_to_item((logger_list)->node.next, struct logger, node); \
- logger != node_to_item(&(logger_list)->node, struct logger, node); \
- logger = node_to_item((logger)->node.next, struct logger, node))
-
-#ifndef __unused
-#define __unused __attribute__((unused))
-#endif
-
-/* In the future, we would like to make this list extensible */
-static const char *LOG_NAME[LOG_ID_MAX] = {
- [LOG_ID_MAIN] = "main",
- [LOG_ID_RADIO] = "radio",
- [LOG_ID_EVENTS] = "events",
- [LOG_ID_SYSTEM] = "system",
- [LOG_ID_CRASH] = "crash",
- [LOG_ID_KERNEL] = "kernel",
-};
-
-const char *android_log_id_to_name(log_id_t log_id)
-{
- if (log_id >= LOG_ID_MAX) {
- log_id = LOG_ID_MAIN;
- }
- return LOG_NAME[log_id];
-}
-
-static int accessmode(int mode)
-{
- if ((mode & ANDROID_LOG_ACCMODE) == ANDROID_LOG_WRONLY) {
- return W_OK;
- }
- if ((mode & ANDROID_LOG_ACCMODE) == ANDROID_LOG_RDWR) {
- return R_OK | W_OK;
- }
- return R_OK;
-}
-
-/* repeated fragment */
-static int check_allocate_accessible(char **n, const char *b, int mode)
-{
- *n = NULL;
-
- if (!b) {
- return -EINVAL;
- }
-
- asprintf(n, LOG_FILE_DIR "%s", b);
- if (!*n) {
- return -1;
- }
-
- return access(*n, accessmode(mode));
-}
-
-log_id_t android_name_to_log_id(const char *logName)
-{
- const char *b;
- char *n;
- int ret;
-
- if (!logName) {
- return -1; /* NB: log_id_t is unsigned */
- }
- b = strrchr(logName, '/');
- if (!b) {
- b = logName;
- } else {
- ++b;
- }
-
- ret = check_allocate_accessible(&n, b, ANDROID_LOG_RDONLY);
- free(n);
- if (ret) {
- return ret;
- }
-
- for(ret = LOG_ID_MIN; ret < LOG_ID_MAX; ++ret) {
- const char *l = LOG_NAME[ret];
- if (l && !strcmp(b, l)) {
- return ret;
- }
- }
- return -1; /* should never happen */
-}
-
-struct logger_list {
- struct listnode node;
- int mode;
- unsigned int tail;
- pid_t pid;
- unsigned int queued_lines;
- int timeout_ms;
- int error;
- bool flush;
- bool valid_entry; /* valiant(?) effort to deal with memory starvation */
- struct log_msg entry;
-};
-
-struct log_list {
- struct listnode node;
- struct log_msg entry; /* Truncated to event->len() + 1 to save space */
-};
-
-struct logger {
- struct listnode node;
- struct logger_list *top;
- int fd;
- log_id_t id;
- short *revents;
- struct listnode log_list;
-};
-
-/* android_logger_alloc unimplemented, no use case */
-/* android_logger_free not exported */
-static void android_logger_free(struct logger *logger)
-{
- if (!logger) {
- return;
- }
-
- while (!list_empty(&logger->log_list)) {
- struct log_list *entry = node_to_item(
- list_head(&logger->log_list), struct log_list, node);
- list_remove(&entry->node);
- free(entry);
- if (logger->top->queued_lines) {
- logger->top->queued_lines--;
- }
- }
-
- if (logger->fd >= 0) {
- close(logger->fd);
- }
-
- list_remove(&logger->node);
-
- free(logger);
-}
-
-log_id_t android_logger_get_id(struct logger *logger)
-{
- return logger->id;
-}
-
-/* worker for sending the command to the logger */
-static int logger_ioctl(struct logger *logger, int cmd, int mode)
-{
- char *n;
- int f, ret;
-
- if (!logger || !logger->top) {
- return -EFAULT;
- }
-
- if (((mode & ANDROID_LOG_ACCMODE) == ANDROID_LOG_RDWR)
- || (((mode ^ logger->top->mode) & ANDROID_LOG_ACCMODE) == 0)) {
- return ioctl(logger->fd, cmd);
- }
-
- /* We go here if android_logger_list_open got mode wrong for this ioctl */
- ret = check_allocate_accessible(&n, android_log_id_to_name(logger->id), mode);
- if (ret) {
- free(n);
- return ret;
- }
-
- f = open(n, mode);
- free(n);
- if (f < 0) {
- return f;
- }
-
- ret = ioctl(f, cmd);
- close (f);
-
- return ret;
-}
-
-int android_logger_clear(struct logger *logger)
-{
- return logger_ioctl(logger, LOGGER_FLUSH_LOG, ANDROID_LOG_WRONLY);
-}
-
-/* returns the total size of the log's ring buffer */
-long android_logger_get_log_size(struct logger *logger)
-{
- return logger_ioctl(logger, LOGGER_GET_LOG_BUF_SIZE, ANDROID_LOG_RDWR);
-}
-
-int android_logger_set_log_size(struct logger *logger __unused,
- unsigned long size __unused)
-{
- return -ENOTSUP;
-}
-
-/*
- * returns the readable size of the log's ring buffer (that is, amount of the
- * log consumed)
- */
-long android_logger_get_log_readable_size(struct logger *logger)
-{
- return logger_ioctl(logger, LOGGER_GET_LOG_LEN, ANDROID_LOG_RDONLY);
-}
-
-/*
- * returns the logger version
- */
-int android_logger_get_log_version(struct logger *logger)
-{
- int ret = logger_ioctl(logger, LOGGER_GET_VERSION, ANDROID_LOG_RDWR);
- return (ret < 0) ? 1 : ret;
-}
-
-/*
- * returns statistics
- */
-static const char unsupported[] = "18\nNot Supported\n\f";
-
-ssize_t android_logger_get_statistics(struct logger_list *logger_list __unused,
- char *buf, size_t len)
-{
- strncpy(buf, unsupported, len);
- return -ENOTSUP;
-}
-
-ssize_t android_logger_get_prune_list(struct logger_list *logger_list __unused,
- char *buf, size_t len)
-{
- strncpy(buf, unsupported, len);
- return -ENOTSUP;
-}
-
-int android_logger_set_prune_list(struct logger_list *logger_list __unused,
- char *buf, size_t len)
-{
- static const char unsupported_error[] = "Unsupported";
- strncpy(buf, unsupported, len);
- return -ENOTSUP;
-}
-
-struct logger_list *android_logger_list_alloc(int mode,
- unsigned int tail,
- pid_t pid)
-{
- struct logger_list *logger_list;
-
- logger_list = calloc(1, sizeof(*logger_list));
- if (!logger_list) {
- return NULL;
- }
- list_init(&logger_list->node);
- logger_list->mode = mode;
- logger_list->tail = tail;
- logger_list->pid = pid;
- return logger_list;
-}
-
-struct logger_list *android_logger_list_alloc_time(int mode,
- log_time start __unused,
- pid_t pid)
-{
- return android_logger_list_alloc(mode, 0, pid);
-}
-
-/* android_logger_list_register unimplemented, no use case */
-/* android_logger_list_unregister unimplemented, no use case */
-
-/* Open the named log and add it to the logger list */
-struct logger *android_logger_open(struct logger_list *logger_list,
- log_id_t id)
-{
- struct listnode *node;
- struct logger *logger;
- char *n;
-
- if (!logger_list || (id >= LOG_ID_MAX)) {
- goto err;
- }
-
- logger_for_each(logger, logger_list) {
- if (logger->id == id) {
- goto ok;
- }
- }
-
- logger = calloc(1, sizeof(*logger));
- if (!logger) {
- goto err;
- }
-
- if (check_allocate_accessible(&n, android_log_id_to_name(id),
- logger_list->mode)) {
- goto err_name;
- }
-
- logger->fd = open(n, logger_list->mode & (ANDROID_LOG_ACCMODE | ANDROID_LOG_NONBLOCK));
- if (logger->fd < 0) {
- goto err_name;
- }
-
- free(n);
- logger->id = id;
- list_init(&logger->log_list);
- list_add_tail(&logger_list->node, &logger->node);
- logger->top = logger_list;
- logger_list->timeout_ms = LOG_TIMEOUT_FLUSH;
- goto ok;
-
-err_name:
- free(n);
-err_logger:
- free(logger);
-err:
- logger = NULL;
-ok:
- return logger;
-}
-
-/* Open the single named log and make it part of a new logger list */
-struct logger_list *android_logger_list_open(log_id_t id,
- int mode,
- unsigned int tail,
- pid_t pid)
-{
- struct logger_list *logger_list = android_logger_list_alloc(mode, tail, pid);
- if (!logger_list) {
- return NULL;
- }
-
- if (!android_logger_open(logger_list, id)) {
- android_logger_list_free(logger_list);
- return NULL;
- }
-
- return logger_list;
-}
-
-/* prevent memory starvation when backfilling */
-static unsigned int queue_threshold(struct logger_list *logger_list)
-{
- return (logger_list->tail < 64) ? 64 : logger_list->tail;
-}
-
-static bool low_queue(struct listnode *node)
-{
- /* low is considered less than 2 */
- return list_head(node) == list_tail(node);
-}
-
-/* Flush queues in sequential order, one at a time */
-static int android_logger_list_flush(struct logger_list *logger_list,
- struct log_msg *log_msg)
-{
- int ret = 0;
- struct log_list *firstentry = NULL;
-
- while ((ret == 0)
- && (logger_list->flush
- || (logger_list->queued_lines > logger_list->tail))) {
- struct logger *logger;
-
- /* Merge sort */
- bool at_least_one_is_low = false;
- struct logger *firstlogger = NULL;
- firstentry = NULL;
-
- logger_for_each(logger, logger_list) {
- struct listnode *node;
- struct log_list *oldest = NULL;
-
- /* kernel logger channels not necessarily time-sort order */
- list_for_each(node, &logger->log_list) {
- struct log_list *entry = node_to_item(node,
- struct log_list, node);
- if (!oldest
- || (entry->entry.entry.sec < oldest->entry.entry.sec)
- || ((entry->entry.entry.sec == oldest->entry.entry.sec)
- && (entry->entry.entry.nsec < oldest->entry.entry.nsec))) {
- oldest = entry;
- }
- }
-
- if (!oldest) {
- at_least_one_is_low = true;
- continue;
- } else if (low_queue(&logger->log_list)) {
- at_least_one_is_low = true;
- }
-
- if (!firstentry
- || (oldest->entry.entry.sec < firstentry->entry.entry.sec)
- || ((oldest->entry.entry.sec == firstentry->entry.entry.sec)
- && (oldest->entry.entry.nsec < firstentry->entry.entry.nsec))) {
- firstentry = oldest;
- firstlogger = logger;
- }
- }
-
- if (!firstentry) {
- break;
- }
-
- /* when trimming list, tries to keep one entry behind in each bucket */
- if (!logger_list->flush
- && at_least_one_is_low
- && (logger_list->queued_lines < queue_threshold(logger_list))) {
- break;
- }
-
- /* within tail?, send! */
- if ((logger_list->tail == 0)
- || (logger_list->queued_lines <= logger_list->tail)) {
- int diff;
- ret = firstentry->entry.entry.hdr_size;
- if (!ret) {
- ret = sizeof(firstentry->entry.entry_v1);
- }
-
- /* Promote entry to v3 format */
- memcpy(log_msg->buf, firstentry->entry.buf, ret);
- diff = sizeof(firstentry->entry.entry_v3) - ret;
- if (diff < 0) {
- diff = 0;
- } else if (diff > 0) {
- memset(log_msg->buf + ret, 0, diff);
- }
- memcpy(log_msg->buf + ret + diff, firstentry->entry.buf + ret,
- firstentry->entry.entry.len + 1);
- ret += diff;
- log_msg->entry.hdr_size = ret;
- log_msg->entry.lid = firstlogger->id;
-
- ret += firstentry->entry.entry.len;
- }
-
- /* next entry */
- list_remove(&firstentry->node);
- free(firstentry);
- if (logger_list->queued_lines) {
- logger_list->queued_lines--;
- }
- }
-
- /* Flushed the list, no longer in tail mode for continuing content */
- if (logger_list->flush && !firstentry) {
- logger_list->tail = 0;
- }
- return ret;
-}
-
-/* Read from the selected logs */
-int android_logger_list_read(struct logger_list *logger_list,
- struct log_msg *log_msg)
-{
- struct logger *logger;
- nfds_t nfds;
- struct pollfd *p, *pollfds = NULL;
- int error = 0, ret = 0;
-
- memset(log_msg, 0, sizeof(struct log_msg));
-
- if (!logger_list) {
- return -ENODEV;
- }
-
- if (!(accessmode(logger_list->mode) & R_OK)) {
- logger_list->error = EPERM;
- goto done;
- }
-
- nfds = 0;
- logger_for_each(logger, logger_list) {
- ++nfds;
- }
- if (nfds <= 0) {
- error = ENODEV;
- goto done;
- }
-
- /* Do we have anything to offer from the buffer or state? */
- if (logger_list->valid_entry) { /* implies we are also in a flush state */
- goto flush;
- }
-
- ret = android_logger_list_flush(logger_list, log_msg);
- if (ret) {
- goto done;
- }
-
- if (logger_list->error) { /* implies we are also in a flush state */
- goto done;
- }
-
- /* Lets start grinding on metal */
- pollfds = calloc(nfds, sizeof(struct pollfd));
- if (!pollfds) {
- error = ENOMEM;
- goto flush;
- }
-
- p = pollfds;
- logger_for_each(logger, logger_list) {
- p->fd = logger->fd;
- p->events = POLLIN;
- logger->revents = &p->revents;
- ++p;
- }
-
- while (!ret && !error) {
- int result;
-
- /* If we oversleep it's ok, i.e. ignore EINTR. */
- result = TEMP_FAILURE_RETRY(
- poll(pollfds, nfds, logger_list->timeout_ms));
-
- if (result <= 0) {
- if (result) {
- error = errno;
- } else if (logger_list->mode & ANDROID_LOG_NONBLOCK) {
- error = EAGAIN;
- } else {
- logger_list->timeout_ms = LOG_TIMEOUT_NEVER;
- }
-
- logger_list->flush = true;
- goto try_flush;
- }
-
- logger_list->timeout_ms = LOG_TIMEOUT_FLUSH;
-
- /* Anti starvation */
- if (!logger_list->flush
- && (logger_list->queued_lines > (queue_threshold(logger_list) / 2))) {
- /* Any queues with input pending that is low? */
- bool starving = false;
- logger_for_each(logger, logger_list) {
- if ((*(logger->revents) & POLLIN)
- && low_queue(&logger->log_list)) {
- starving = true;
- break;
- }
- }
-
- /* pushback on any queues that are not low */
- if (starving) {
- logger_for_each(logger, logger_list) {
- if ((*(logger->revents) & POLLIN)
- && !low_queue(&logger->log_list)) {
- *(logger->revents) &= ~POLLIN;
- }
- }
- }
- }
-
- logger_for_each(logger, logger_list) {
- unsigned int hdr_size;
- struct log_list *entry;
- int diff;
-
- if (!(*(logger->revents) & POLLIN)) {
- continue;
- }
-
- memset(logger_list->entry.buf, 0, sizeof(struct log_msg));
- /* NOTE: driver guarantees we read exactly one full entry */
- result = read(logger->fd, logger_list->entry.buf,
- LOGGER_ENTRY_MAX_LEN);
- if (result <= 0) {
- if (!result) {
- error = EIO;
- } else if (errno != EINTR) {
- error = errno;
- }
- continue;
- }
-
- if (logger_list->pid
- && (logger_list->pid != logger_list->entry.entry.pid)) {
- continue;
- }
-
- hdr_size = logger_list->entry.entry.hdr_size;
- if (!hdr_size) {
- hdr_size = sizeof(logger_list->entry.entry_v1);
- }
-
- if ((hdr_size > sizeof(struct log_msg))
- || (logger_list->entry.entry.len
- > sizeof(logger_list->entry.buf) - hdr_size)
- || (logger_list->entry.entry.len != result - hdr_size)) {
- error = EINVAL;
- continue;
- }
-
- /* Promote entry to v3 format */
- diff = sizeof(logger_list->entry.entry_v3) - hdr_size;
- if (diff > 0) {
- if (logger_list->entry.entry.len
- > sizeof(logger_list->entry.buf) - hdr_size - diff) {
- error = EINVAL;
- continue;
- }
- result += diff;
- memmove(logger_list->entry.buf + hdr_size + diff,
- logger_list->entry.buf + hdr_size,
- logger_list->entry.entry.len + 1);
- memset(logger_list->entry.buf + hdr_size, 0, diff);
- logger_list->entry.entry.hdr_size = hdr_size + diff;
- }
- logger_list->entry.entry.lid = logger->id;
-
- /* speedup: If not tail, and only one list, send directly */
- if (!logger_list->tail
- && (list_head(&logger_list->node)
- == list_tail(&logger_list->node))) {
- ret = result;
- memcpy(log_msg->buf, logger_list->entry.buf, result + 1);
- break;
- }
-
- entry = malloc(sizeof(*entry) - sizeof(entry->entry) + result + 1);
-
- if (!entry) {
- logger_list->valid_entry = true;
- error = ENOMEM;
- break;
- }
-
- logger_list->queued_lines++;
-
- memcpy(entry->entry.buf, logger_list->entry.buf, result);
- entry->entry.buf[result] = '\0';
- list_add_tail(&logger->log_list, &entry->node);
- }
-
- if (ret <= 0) {
-try_flush:
- ret = android_logger_list_flush(logger_list, log_msg);
- }
- }
-
- free(pollfds);
-
-flush:
- if (error) {
- logger_list->flush = true;
- }
-
- if (ret <= 0) {
- ret = android_logger_list_flush(logger_list, log_msg);
-
- if (!ret && logger_list->valid_entry) {
- ret = logger_list->entry.entry.hdr_size;
- if (!ret) {
- ret = sizeof(logger_list->entry.entry_v1);
- }
- ret += logger_list->entry.entry.len;
-
- memcpy(log_msg->buf, logger_list->entry.buf,
- sizeof(struct log_msg));
- logger_list->valid_entry = false;
- }
- }
-
-done:
- if (logger_list->error) {
- error = logger_list->error;
- }
- if (error) {
- logger_list->error = error;
- if (!ret) {
- ret = -error;
- }
- }
- return ret;
-}
-
-/* Close all the logs */
-void android_logger_list_free(struct logger_list *logger_list)
-{
- if (logger_list == NULL) {
- return;
- }
-
- while (!list_empty(&logger_list->node)) {
- struct listnode *node = list_head(&logger_list->node);
- struct logger *logger = node_to_item(node, struct logger, node);
- android_logger_free(logger);
- }
-
- free(logger_list);
-}
diff --git a/liblog/logd_write_kern.c b/liblog/logd_write_kern.c
deleted file mode 100644
index 8742b34..0000000
--- a/liblog/logd_write_kern.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2007-2014 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 <errno.h>
-#include <fcntl.h>
-#include <pthread.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <android/set_abort_message.h>
-
-#include <log/log.h>
-#include <log/logd.h>
-#include <log/logger.h>
-
-#define LOGGER_LOG_MAIN "log/main"
-#define LOGGER_LOG_RADIO "log/radio"
-#define LOGGER_LOG_EVENTS "log/events"
-#define LOGGER_LOG_SYSTEM "log/system"
-
-#define LOG_BUF_SIZE 1024
-
-#define log_open(pathname, flags) open(pathname, (flags) | O_CLOEXEC)
-#define log_writev(filedes, vector, count) writev(filedes, vector, count)
-#define log_close(filedes) close(filedes)
-
-static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
-static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
-
-static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
-
-#ifndef __unused
-#define __unused __attribute__((__unused__))
-#endif
-
-static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1, -1 };
-
-/*
- * This is used by the C++ code to decide if it should write logs through
- * the C code. Basically, if /dev/log/... is available, we're running in
- * the simulator rather than a desktop tool and want to use the device.
- */
-static enum {
- kLogUninitialized, kLogNotAvailable, kLogAvailable
-} g_log_status = kLogUninitialized;
-int __android_log_dev_available(void)
-{
- if (g_log_status == kLogUninitialized) {
- if (access("/dev/"LOGGER_LOG_MAIN, W_OK) == 0)
- g_log_status = kLogAvailable;
- else
- g_log_status = kLogNotAvailable;
- }
-
- return (g_log_status == kLogAvailable);
-}
-
-static int __write_to_log_null(log_id_t log_fd __unused, struct iovec *vec __unused,
- size_t nr __unused)
-{
- return -1;
-}
-
-static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
-{
- ssize_t ret;
- int log_fd;
-
- if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
- if (log_id == LOG_ID_CRASH) {
- log_id = LOG_ID_MAIN;
- }
- log_fd = log_fds[(int)log_id];
- } else {
- return -EBADF;
- }
-
- do {
- ret = log_writev(log_fd, vec, nr);
- if (ret < 0) {
- ret = -errno;
- }
- } while (ret == -EINTR);
-
- return ret;
-}
-
-static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
-{
- pthread_mutex_lock(&log_init_lock);
-
- if (write_to_log == __write_to_log_init) {
- log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
- log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
- log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
- log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);
-
- write_to_log = __write_to_log_kernel;
-
- if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
- log_fds[LOG_ID_EVENTS] < 0) {
- log_close(log_fds[LOG_ID_MAIN]);
- log_close(log_fds[LOG_ID_RADIO]);
- log_close(log_fds[LOG_ID_EVENTS]);
- log_fds[LOG_ID_MAIN] = -1;
- log_fds[LOG_ID_RADIO] = -1;
- log_fds[LOG_ID_EVENTS] = -1;
- write_to_log = __write_to_log_null;
- }
-
- if (log_fds[LOG_ID_SYSTEM] < 0) {
- log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN];
- }
- }
-
- pthread_mutex_unlock(&log_init_lock);
-
- return write_to_log(log_id, vec, nr);
-}
-
-int __android_log_write(int prio, const char *tag, const char *msg)
-{
- return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
-}
-
-int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
-{
- struct iovec vec[3];
- char tmp_tag[32];
-
- if (!tag)
- tag = "";
-
- /* XXX: This needs to go! */
- if ((bufID != LOG_ID_RADIO) &&
- (!strcmp(tag, "HTC_RIL") ||
- !strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
- !strncmp(tag, "IMS", 3) || /* Any log tag with "IMS" as the prefix */
- !strcmp(tag, "AT") ||
- !strcmp(tag, "GSM") ||
- !strcmp(tag, "STK") ||
- !strcmp(tag, "CDMA") ||
- !strcmp(tag, "PHONE") ||
- !strcmp(tag, "SMS"))) {
- bufID = LOG_ID_RADIO;
- /* Inform third party apps/ril/radio.. to use Rlog or RLOG */
- snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
- tag = tmp_tag;
- }
-
- if (prio == ANDROID_LOG_FATAL) {
- android_set_abort_message(msg);
- }
-
- vec[0].iov_base = (unsigned char *) &prio;
- vec[0].iov_len = 1;
- vec[1].iov_base = (void *) tag;
- vec[1].iov_len = strlen(tag) + 1;
- vec[2].iov_base = (void *) msg;
- vec[2].iov_len = strlen(msg) + 1;
-
- return write_to_log(bufID, vec, 3);
-}
-
-int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap)
-{
- char buf[LOG_BUF_SIZE];
-
- vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-
- return __android_log_write(prio, tag, buf);
-}
-
-int __android_log_print(int prio, const char *tag, const char *fmt, ...)
-{
- va_list ap;
- char buf[LOG_BUF_SIZE];
-
- va_start(ap, fmt);
- vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
- va_end(ap);
-
- return __android_log_write(prio, tag, buf);
-}
-
-int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...)
-{
- va_list ap;
- char buf[LOG_BUF_SIZE];
-
- va_start(ap, fmt);
- vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
- va_end(ap);
-
- return __android_log_buf_write(bufID, prio, tag, buf);
-}
-
-void __android_log_assert(const char *cond, const char *tag,
- const char *fmt, ...)
-{
- char buf[LOG_BUF_SIZE];
-
- if (fmt) {
- va_list ap;
- va_start(ap, fmt);
- vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
- va_end(ap);
- } else {
- /* Msg not provided, log condition. N.B. Do not use cond directly as
- * format string as it could contain spurious '%' syntax (e.g.
- * "%d" in "blocks%devs == 0").
- */
- if (cond)
- snprintf(buf, LOG_BUF_SIZE, "Assertion failed: %s", cond);
- else
- strcpy(buf, "Unspecified assertion failed");
- }
-
- __android_log_write(ANDROID_LOG_FATAL, tag, buf);
- abort(); /* abort so we have a chance to debug the situation */
- /* NOTREACHED */
-}
-
-int __android_log_bwrite(int32_t tag, const void *payload, size_t len)
-{
- struct iovec vec[2];
-
- vec[0].iov_base = &tag;
- vec[0].iov_len = sizeof(tag);
- vec[1].iov_base = (void*)payload;
- vec[1].iov_len = len;
-
- return write_to_log(LOG_ID_EVENTS, vec, 2);
-}
-
-/*
- * Like __android_log_bwrite, but takes the type as well. Doesn't work
- * for the general case where we're generating lists of stuff, but very
- * handy if we just want to dump an integer into the log.
- */
-int __android_log_btwrite(int32_t tag, char type, const void *payload,
- size_t len)
-{
- struct iovec vec[3];
-
- vec[0].iov_base = &tag;
- vec[0].iov_len = sizeof(tag);
- vec[1].iov_base = &type;
- vec[1].iov_len = sizeof(type);
- vec[2].iov_base = (void*)payload;
- vec[2].iov_len = len;
-
- return write_to_log(LOG_ID_EVENTS, vec, 3);
-}
-
-/*
- * Like __android_log_bwrite, but used for writing strings to the
- * event log.
- */
-int __android_log_bswrite(int32_t tag, const char *payload)
-{
- struct iovec vec[4];
- char type = EVENT_TYPE_STRING;
- uint32_t len = strlen(payload);
-
- vec[0].iov_base = &tag;
- vec[0].iov_len = sizeof(tag);
- vec[1].iov_base = &type;
- vec[1].iov_len = sizeof(type);
- vec[2].iov_base = &len;
- vec[2].iov_len = sizeof(len);
- vec[3].iov_base = (void*)payload;
- vec[3].iov_len = len;
-
- return write_to_log(LOG_ID_EVENTS, vec, 4);
-}
diff --git a/liblog/tests/Android.mk b/liblog/tests/Android.mk
index a407c50..8229859 100644
--- a/liblog/tests/Android.mk
+++ b/liblog/tests/Android.mk
@@ -65,10 +65,6 @@
test_src_files += \
libc_test.cpp
-ifneq ($(TARGET_USES_LOGD),false)
-test_c_flags += -DTARGET_USES_LOGD
-endif
-
endif
# Build tests for the device (with .so). Run with:
diff --git a/liblog/tests/libc_test.cpp b/liblog/tests/libc_test.cpp
index 0e84f4e..9dd6f03 100644
--- a/liblog/tests/libc_test.cpp
+++ b/liblog/tests/libc_test.cpp
@@ -25,9 +25,7 @@
#define _ANDROID_LOG_H // Priorities redefined
#define _LIBS_LOG_LOG_H // log ids redefined
typedef unsigned char log_id_t; // log_id_t missing as a result
-#ifdef TARGET_USES_LOGD
#define _LIBS_LOG_LOG_READ_H // log_time redefined
-#endif
#include <log/log.h>
#include <log/logger.h>
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 5489cc9..489bea6 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -34,8 +34,7 @@
CommandListener::CommandListener(LogBuffer *buf, LogReader * /*reader*/,
LogListener * /*swl*/) :
- FrameworkListener(getLogSocket()),
- mBuf(*buf) {
+ FrameworkListener(getLogSocket()) {
// registerCmd(new ShutdownCmd(buf, writer, swl));
registerCmd(new ClearCmd(buf));
registerCmd(new GetBufSizeCmd(buf));
@@ -47,10 +46,9 @@
registerCmd(new ReinitCmd());
}
-CommandListener::ShutdownCmd::ShutdownCmd(LogBuffer *buf, LogReader *reader,
+CommandListener::ShutdownCmd::ShutdownCmd(LogReader *reader,
LogListener *swl) :
LogCommand("shutdown"),
- mBuf(*buf),
mReader(*reader),
mSwl(*swl) {
}
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
index 83e06b4..3877675 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -27,7 +27,6 @@
void reinit_signal_handler(int /*signal*/);
class CommandListener : public FrameworkListener {
- LogBuffer &mBuf;
public:
CommandListener(LogBuffer *buf, LogReader *reader, LogListener *swl);
@@ -37,12 +36,11 @@
static int getLogSocket();
class ShutdownCmd : public LogCommand {
- LogBuffer &mBuf;
LogReader &mReader;
LogListener &mSwl;
public:
- ShutdownCmd(LogBuffer *buf, LogReader *reader, LogListener *swl);
+ ShutdownCmd(LogReader *reader, LogListener *swl);
virtual ~ShutdownCmd() {}
int runCommand(SocketClient *c, int argc, char ** argv);
};
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index d578c04..eff26f5 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -43,8 +43,10 @@
if (!isdigit(*s++)) {
return NULL;
}
+ static const size_t max_prio_len = 4;
+ size_t len = 0;
char c;
- while ((c = *s++)) {
+ while (((c = *s++)) && (++len <= max_prio_len)) {
if (!isdigit(c)) {
return (c == '>') ? s : NULL;
}
@@ -73,7 +75,7 @@
}
// Like strtok_r with "\r\n" except that we look for log signatures (regex)
-// \(\(<[0-9]+>\)\([[] *[0-9]+[.][0-9]+[]] \)\{0,1\}\|[[] *[0-9]+[.][0-9]+[]] \)
+// \(\(<[0-9]\{1,4\}>\)\([[] *[0-9]+[.][0-9]+[]] \)\{0,1\}\|[[] *[0-9]+[.][0-9]+[]] \)
// and split if we see a second one without a newline.
#define SIGNATURE_MASK 0xF0
@@ -165,7 +167,7 @@
break;
}
}
- /* NOTREACHED */
+ // NOTREACHED
}
log_time LogKlog::correction = log_time(CLOCK_REALTIME) - log_time(CLOCK_MONOTONIC);
@@ -175,8 +177,6 @@
logbuf(buf),
reader(reader),
signature(CLOCK_MONOTONIC),
- fdWrite(fdWrite),
- fdRead(fdRead),
initialized(false),
enableLogging(true),
auditd(auditd) {
@@ -465,7 +465,7 @@
if (strncmp(bt, cp, size)) {
// <PRI>[<TIME>] <tag>_host '<tag>.<num>' : message
if (!strncmp(bt + size - 5, "_host", 5)
- && !strncmp(bt, cp, size - 5)) {
+ && !strncmp(bt, cp, size - 5)) {
const char *b = cp;
cp += size - 5;
if (*cp == '.') {
@@ -490,7 +490,6 @@
}
}
} else if (isspace(cp[size])) {
- const char *b = cp;
cp += size;
while (isspace(*++cp));
// <PRI>[<TIME>] <tag> <tag> : message
@@ -535,11 +534,15 @@
}
size = etag - tag;
if ((size <= 1)
- || ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1])))
- || ((size == 3) && !strncmp(tag, "CPU", 3))
- || ((size == 7) && !strncmp(tag, "WARNING", 7))
- || ((size == 5) && !strncmp(tag, "ERROR", 5))
- || ((size == 4) && !strncmp(tag, "INFO", 4))) {
+ // register names like x9
+ || ((size == 2) && (isdigit(tag[0]) || isdigit(tag[1])))
+ // register names like x18 but not driver names like en0
+ || ((size == 3) && (isdigit(tag[1]) && isdigit(tag[2])))
+ // blacklist
+ || ((size == 3) && !strncmp(tag, "CPU", 3))
+ || ((size == 7) && !strncmp(tag, "WARNING", 7))
+ || ((size == 5) && !strncmp(tag, "ERROR", 5))
+ || ((size == 4) && !strncmp(tag, "INFO", 4))) {
buf = start;
etag = tag = "";
}
diff --git a/logd/LogKlog.h b/logd/LogKlog.h
index a898c63..24b2685 100644
--- a/logd/LogKlog.h
+++ b/logd/LogKlog.h
@@ -27,8 +27,6 @@
LogBuffer *logbuf;
LogReader *reader;
const log_time signature;
- const int fdWrite; // /dev/kmsg
- const int fdRead; // /proc/kmsg
// Set once thread is started, separates KLOG_ACTION_READ_ALL
// and KLOG_ACTION_READ phases.
bool initialized;
diff --git a/logd/tests/Android.mk b/logd/tests/Android.mk
index 85ca4ac..a7c6b53 100644
--- a/logd/tests/Android.mk
+++ b/logd/tests/Android.mk
@@ -34,10 +34,6 @@
-Werror \
-fno-builtin \
-ifneq ($(TARGET_USES_LOGD),false)
-test_c_flags += -DTARGET_USES_LOGD=1
-endif
-
test_src_files := \
logd_test.cpp
diff --git a/logd/tests/logd_test.cpp b/logd/tests/logd_test.cpp
index 3266360..44fa95c 100644
--- a/logd/tests/logd_test.cpp
+++ b/logd/tests/logd_test.cpp
@@ -137,13 +137,7 @@
alloc_statistics(&buf, &len);
-#ifdef TARGET_USES_LOGD
ASSERT_TRUE(NULL != buf);
-#else
- if (!buf) {
- return;
- }
-#endif
// remove trailing FF
char *cp = buf + len - 1;
@@ -167,7 +161,6 @@
EXPECT_EQ(0, truncated);
-#ifdef TARGET_USES_LOGD
char *main_logs = strstr(cp, "\nChattiest UIDs in main ");
EXPECT_TRUE(NULL != main_logs);
@@ -179,7 +172,6 @@
char *events_logs = strstr(cp, "\nChattiest UIDs in events ");
EXPECT_TRUE(NULL != events_logs);
-#endif
delete [] buf;
}
@@ -419,37 +411,17 @@
return;
}
-#ifdef TARGET_USES_LOGD
EXPECT_GE(200000UL, ns[log_maximum_retry]); // 104734 user
-#else
- EXPECT_GE(10000UL, ns[log_maximum_retry]); // 5636 kernel
-#endif
-#ifdef TARGET_USES_LOGD
EXPECT_GE(90000UL, ns[log_maximum]); // 46913 user
-#else
- EXPECT_GE(10000UL, ns[log_maximum]); // 5637 kernel
-#endif
EXPECT_GE(4096UL, ns[clock_overhead]); // 4095
-#ifdef TARGET_USES_LOGD
EXPECT_GE(250000UL, ns[log_overhead]); // 126886 user
-#else
- EXPECT_GE(100000UL, ns[log_overhead]); // 50945 kernel
-#endif
-#ifdef TARGET_USES_LOGD
EXPECT_GE(10000UL, ns[log_latency]); // 5669 user space
-#else
- EXPECT_GE(500000UL, ns[log_latency]); // 254200 kernel
-#endif
-#ifdef TARGET_USES_LOGD
EXPECT_GE(20000000UL, ns[log_delay]); // 10500289 user
-#else
- EXPECT_GE(55000UL, ns[log_delay]); // 27341 kernel
-#endif
for (unsigned i = 0; i < sizeof(ns) / sizeof(ns[0]); ++i) {
EXPECT_NE(0UL, ns[i]);
@@ -457,14 +429,8 @@
alloc_statistics(&buf, &len);
-#ifdef TARGET_USES_LOGD
bool collected_statistics = !!buf;
EXPECT_EQ(true, collected_statistics);
-#else
- if (!buf) {
- return;
- }
-#endif
ASSERT_TRUE(NULL != buf);
diff --git a/logwrapper/logwrap.c b/logwrapper/logwrap.c
index 3a6276e..777dafe 100644
--- a/logwrapper/logwrap.c
+++ b/logwrapper/logwrap.c
@@ -355,7 +355,8 @@
}
if (poll_fds[0].revents & POLLIN) {
- sz = read(parent_read, &buffer[b], sizeof(buffer) - 1 - b);
+ sz = TEMP_FAILURE_RETRY(
+ read(parent_read, &buffer[b], sizeof(buffer) - 1 - b));
sz += b;
// Log one line at a time
@@ -490,7 +491,7 @@
}
/* Use ptty instead of socketpair so that STDOUT is not buffered */
- parent_ptty = open("/dev/ptmx", O_RDWR);
+ parent_ptty = TEMP_FAILURE_RETRY(open("/dev/ptmx", O_RDWR));
if (parent_ptty < 0) {
ERROR("Cannot create parent ptty\n");
rc = -1;
@@ -505,7 +506,7 @@
goto err_ptty;
}
- child_ptty = open(child_devname, O_RDWR);
+ child_ptty = TEMP_FAILURE_RETRY(open(child_devname, O_RDWR));
if (child_ptty < 0) {
ERROR("Cannot open child_ptty\n");
rc = -1;
diff --git a/metrics/OWNERS b/metrics/OWNERS
new file mode 100644
index 0000000..7f5e50d
--- /dev/null
+++ b/metrics/OWNERS
@@ -0,0 +1,3 @@
+semenzato@chromium.org
+derat@chromium.org
+bsimonnet@chromium.org
diff --git a/metrics/README b/metrics/README
new file mode 100644
index 0000000..4b92af3
--- /dev/null
+++ b/metrics/README
@@ -0,0 +1,138 @@
+Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+
+The Chrome OS "metrics" package contains utilities for client-side user metric
+collection.
+When Chrome is installed, Chrome will take care of aggregating and uploading the
+metrics to the UMA server.
+When Chrome is not installed (embedded build) and the metrics_uploader USE flag
+is set, metrics_daemon will aggregate and upload the metrics itself.
+
+
+================================================================================
+The Metrics Library: libmetrics
+================================================================================
+
+libmetrics is a small library that implements the basic C and C++ API for
+metrics collection. All metrics collection is funneled through this library. The
+easiest and recommended way for a client-side module to collect user metrics is
+to link libmetrics and use its APIs to send metrics to Chrome for transport to
+UMA. In order to use the library in a module, you need to do the following:
+
+- Add a dependence (DEPEND and RDEPEND) on chromeos-base/metrics to the module's
+ ebuild.
+
+- Link the module with libmetrics (for example, by passing -lmetrics to the
+ module's link command). Both libmetrics.so and libmetrics.a are built and
+ installed under $SYSROOT/usr/lib/. Note that by default -lmetrics will link
+ against libmetrics.so, which is preferred.
+
+- To access the metrics library API in the module, include the
+ <metrics/metrics_library.h> header file. The file is installed in
+ $SYSROOT/usr/include/ when the metrics library is built and installed.
+
+- The API is documented in metrics_library.h under src/platform/metrics/. Before
+ using the API methods, a MetricsLibrary object needs to be constructed and
+ initialized through its Init method.
+
+ For more information on the C API see c_metrics_library.h.
+
+- Samples are sent to Chrome only if the "/home/chronos/Consent To Send Stats"
+ file exists or the metrics are declared enabled in the policy file (see the
+ AreMetricsEnabled API method).
+
+- On the target platform, shortly after the sample is sent, it should be visible
+ in Chrome through "about:histograms".
+
+
+================================================================================
+Histogram Naming Convention
+================================================================================
+
+Use TrackerArea.MetricName. For example:
+
+Platform.DailyUseTime
+Network.TimeToDrop
+
+
+================================================================================
+Server Side
+================================================================================
+
+If the histogram data is visible in about:histograms, it will be sent by an
+official Chrome build to UMA, assuming the user has opted into metrics
+collection. To make the histogram visible on "chromedashboard", the histogram
+description XML file needs to be updated (steps 2 and 3 after following the
+"Details on how to add your own histograms" link under the Histograms tab).
+Include the string "Chrome OS" in the histogram description so that it's easier
+to distinguish Chrome OS specific metrics from general Chrome histograms.
+
+The UMA server logs and keeps the collected field data even if the metric's name
+is not added to the histogram XML. However, the dashboard histogram for that
+metric will show field data as of the histogram XML update date; it will not
+include data for older dates. If past data needs to be displayed, manual
+server-side intervention is required. In other words, one should assume that
+field data collection starts only after the histogram XML has been updated.
+
+
+================================================================================
+The Metrics Client: metrics_client
+================================================================================
+
+metrics_client is a simple shell command-line utility for sending histogram
+samples and user actions. It's installed under /usr/bin on the target platform
+and uses libmetrics to send the data to Chrome. The utility is useful for
+generating metrics from shell scripts.
+
+For usage information and command-line options, run "metrics_client" on the
+target platform or look for "Usage:" in metrics_client.cc.
+
+
+================================================================================
+The Metrics Daemon: metrics_daemon
+================================================================================
+
+metrics_daemon is a daemon that runs in the background on the target platform
+and is intended for passive or ongoing metrics collection, or metrics collection
+requiring feedback from multiple modules. For example, it listens to D-Bus
+signals related to the user session and screen saver states to determine if the
+user is actively using the device or not and generates the corresponding
+data. The metrics daemon uses libmetrics to send the data to Chrome.
+
+The recommended way to generate metrics data from a module is to link and use
+libmetrics directly. However, the module could instead send signals to or
+communicate in some alternative way with the metrics daemon. Then the metrics
+daemon needs to monitor for the relevant events and take appropriate action --
+for example, aggregate data and send the histogram samples.
+
+
+================================================================================
+FAQ
+================================================================================
+
+Q. What should my histogram's |min| and |max| values be set at?
+
+A. You should set the values to a range that covers the vast majority of samples
+ that would appear in the field. Note that samples below the |min| will still
+ be collected in the underflow bucket and samples above the |max| will end up
+ in the overflow bucket. Also, the reported mean of the data will be correct
+ regardless of the range.
+
+Q. How many buckets should I use in my histogram?
+
+A. You should allocate as many buckets as necessary to perform proper analysis
+ on the collected data. Note, however, that the memory allocated in Chrome for
+ each histogram is proportional to the number of buckets. Therefore, it is
+ strongly recommended to keep this number low (e.g., 50 is normal, while 100
+ is probably high).
+
+Q. When should I use an enumeration (linear) histogram vs. a regular
+ (exponential) histogram?
+
+A. Enumeration histograms should really be used only for sampling enumerated
+ events and, in some cases, percentages. Normally, you should use a regular
+ histogram with exponential bucket layout that provides higher resolution at
+ the low end of the range and lower resolution at the high end. Regular
+ histograms are generally used for collecting performance data (e.g., timing,
+ memory usage, power) as well as aggregated event counts.
diff --git a/metrics/WATCHLISTS b/metrics/WATCHLISTS
new file mode 100644
index 0000000..a051f35
--- /dev/null
+++ b/metrics/WATCHLISTS
@@ -0,0 +1,16 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+ 'WATCHLIST_DEFINITIONS': {
+ 'all': {
+ 'filepath': '.',
+ },
+ },
+ 'WATCHLISTS': {
+ 'all': ['petkov@chromium.org',
+ 'semenzato@chromium.org',
+ 'sosa@chromium.org']
+ },
+}
diff --git a/metrics/c_metrics_library.cc b/metrics/c_metrics_library.cc
new file mode 100644
index 0000000..90a2d59
--- /dev/null
+++ b/metrics/c_metrics_library.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//
+// C wrapper to libmetrics
+//
+
+#include "metrics/c_metrics_library.h"
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+extern "C" CMetricsLibrary CMetricsLibraryNew(void) {
+ MetricsLibrary* lib = new MetricsLibrary;
+ return reinterpret_cast<CMetricsLibrary>(lib);
+}
+
+extern "C" void CMetricsLibraryDelete(CMetricsLibrary handle) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ delete lib;
+}
+
+extern "C" void CMetricsLibraryInit(CMetricsLibrary handle) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib != NULL)
+ lib->Init();
+}
+
+extern "C" int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
+ const char* name, int sample,
+ int min, int max, int nbuckets) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendToUMA(std::string(name), sample, min, max, nbuckets);
+}
+
+extern "C" int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
+ const char* name, int sample,
+ int max) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendEnumToUMA(std::string(name), sample, max);
+}
+
+extern "C" int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
+ const char* name, int sample) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendSparseToUMA(std::string(name), sample);
+}
+
+extern "C" int CMetricsLibrarySendUserActionToUMA(CMetricsLibrary handle,
+ const char* action) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendUserActionToUMA(std::string(action));
+}
+
+extern "C" int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
+ const char* crash_kind) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendCrashToUMA(crash_kind);
+}
+
+extern "C" int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->AreMetricsEnabled();
+}
diff --git a/metrics/c_metrics_library.h b/metrics/c_metrics_library.h
new file mode 100644
index 0000000..7f78e43
--- /dev/null
+++ b/metrics/c_metrics_library.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_C_METRICS_LIBRARY_H_
+#define METRICS_C_METRICS_LIBRARY_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+typedef struct CMetricsLibraryOpaque* CMetricsLibrary;
+
+// C wrapper for MetricsLibrary::MetricsLibrary.
+CMetricsLibrary CMetricsLibraryNew(void);
+
+// C wrapper for MetricsLibrary::~MetricsLibrary.
+void CMetricsLibraryDelete(CMetricsLibrary handle);
+
+// C wrapper for MetricsLibrary::Init.
+void CMetricsLibraryInit(CMetricsLibrary handle);
+
+// C wrapper for MetricsLibrary::SendToUMA.
+int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
+ const char* name, int sample,
+ int min, int max, int nbuckets);
+
+// C wrapper for MetricsLibrary::SendEnumToUMA.
+int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
+ const char* name, int sample, int max);
+
+// C wrapper for MetricsLibrary::SendSparseToUMA.
+int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
+ const char* name, int sample);
+
+// C wrapper for MetricsLibrary::SendUserActionToUMA.
+int CMetricsLibrarySendUserActionToUMA(CMetricsLibrary handle,
+ const char* action);
+
+// C wrapper for MetricsLibrary::SendCrashToUMA.
+int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
+ const char* crash_kind);
+
+// C wrapper for MetricsLibrary::AreMetricsEnabled.
+int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle);
+
+#if defined(__cplusplus)
+}
+#endif
+#endif // METRICS_C_METRICS_LIBRARY_H_
diff --git a/metrics/init/metrics_daemon.conf b/metrics/init/metrics_daemon.conf
new file mode 100644
index 0000000..e6932cf
--- /dev/null
+++ b/metrics/init/metrics_daemon.conf
@@ -0,0 +1,18 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Metrics collection daemon"
+author "chromium-os-dev@chromium.org"
+
+# The metrics daemon is responsible for receiving and forwarding to
+# chrome UMA statistics not produced by chrome.
+start on starting system-services
+stop on stopping system-services
+respawn
+
+# metrics will update the next line to add -uploader for embedded builds.
+env DAEMON_FLAGS=""
+
+expect fork
+exec metrics_daemon ${DAEMON_FLAGS}
diff --git a/metrics/init/metrics_library.conf b/metrics/init/metrics_library.conf
new file mode 100644
index 0000000..03016d1
--- /dev/null
+++ b/metrics/init/metrics_library.conf
@@ -0,0 +1,25 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Metrics Library upstart file"
+author "chromium-os-dev@chromium.org"
+
+# The metrics library is used by several programs (daemons and others)
+# to send UMA stats.
+start on starting boot-services
+
+pre-start script
+ # Create the file used as communication endpoint for metrics.
+ METRICS_DIR=/var/lib/metrics
+ EVENTS_FILE=${METRICS_DIR}/uma-events
+ mkdir -p ${METRICS_DIR}
+ touch ${EVENTS_FILE}
+ chown chronos.chronos ${EVENTS_FILE}
+ chmod 666 ${EVENTS_FILE}
+ # TRANSITION ONLY.
+ # TODO(semenzato) Remove after Chrome change, see issue 447256.
+ # Let Chrome read the metrics file from the old location.
+ mkdir -p /var/run/metrics
+ ln -sf ${EVENTS_FILE} /var/run/metrics
+end script
diff --git a/metrics/libmetrics-334380.gyp b/metrics/libmetrics-334380.gyp
new file mode 100644
index 0000000..9771821
--- /dev/null
+++ b/metrics/libmetrics-334380.gyp
@@ -0,0 +1,8 @@
+{
+ 'variables': {
+ 'libbase_ver': 334380,
+ },
+ 'includes': [
+ 'libmetrics.gypi',
+ ],
+}
diff --git a/metrics/libmetrics.gypi b/metrics/libmetrics.gypi
new file mode 100644
index 0000000..5b90a55
--- /dev/null
+++ b/metrics/libmetrics.gypi
@@ -0,0 +1,33 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(libbase_ver)',
+ ]
+ },
+ 'cflags_cc': [
+ '-fno-exceptions',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'libmetrics-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'cflags': [
+ '-fvisibility=default',
+ ],
+ 'libraries+': [
+ '-lpolicy-<(libbase_ver)',
+ ],
+ 'sources': [
+ 'c_metrics_library.cc',
+ 'metrics_library.cc',
+ 'serialization/metric_sample.cc',
+ 'serialization/serialization_utils.cc',
+ 'timer.cc',
+ ],
+ 'include_dirs': ['.'],
+ },
+ ],
+}
diff --git a/metrics/libmetrics.pc.in b/metrics/libmetrics.pc.in
new file mode 100644
index 0000000..233f318
--- /dev/null
+++ b/metrics/libmetrics.pc.in
@@ -0,0 +1,7 @@
+bslot=@BSLOT@
+
+Name: libmetrics
+Description: Chrome OS metrics library
+Version: ${bslot}
+Requires.private: libchrome-${bslot}
+Libs: -lmetrics-${bslot}
diff --git a/metrics/make_tests.sh b/metrics/make_tests.sh
new file mode 100755
index 0000000..9dcc804
--- /dev/null
+++ b/metrics/make_tests.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Builds tests.
+
+set -e
+make tests
+mkdir -p "${OUT_DIR}"
+cp *_test "${OUT_DIR}"
diff --git a/metrics/metrics.gyp b/metrics/metrics.gyp
new file mode 100644
index 0000000..276ec78
--- /dev/null
+++ b/metrics/metrics.gyp
@@ -0,0 +1,184 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'dbus-1',
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(libbase_ver)',
+ ]
+ },
+ 'cflags_cc': [
+ '-fno-exceptions',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'libmetrics_daemon',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ 'libupload_service',
+ 'metrics_proto',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lrootdev',
+ ],
+ },
+ 'sources': [
+ 'persistent_integer.cc',
+ 'metrics_daemon.cc',
+ 'metrics_daemon_main.cc',
+ ],
+ 'include_dirs': ['.'],
+ },
+ {
+ 'target_name': 'metrics_client',
+ 'type': 'executable',
+ 'dependencies': [
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ ],
+ 'sources': [
+ 'metrics_client.cc',
+ ]
+ },
+ {
+ 'target_name': 'libupload_service',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'metrics_proto',
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lvboot_host',
+ ],
+ },
+ 'variables': {
+ 'exported_deps': [
+ 'protobuf-lite',
+ ],
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps+': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'uploader/upload_service.cc',
+ 'uploader/metrics_hashes.cc',
+ 'uploader/metrics_log.cc',
+ 'uploader/metrics_log_base.cc',
+ 'uploader/system_profile_cache.cc',
+ 'uploader/sender_http.cc',
+ ],
+ 'include_dirs': ['.']
+ },
+ {
+ 'target_name': 'metrics_proto',
+ 'type': 'static_library',
+ 'variables': {
+ 'proto_in_dir': 'uploader/proto',
+ 'proto_out_dir': 'include/metrics/uploader/proto',
+ },
+ 'sources': [
+ '<(proto_in_dir)/chrome_user_metrics_extension.proto',
+ '<(proto_in_dir)/histogram_event.proto',
+ '<(proto_in_dir)/system_profile.proto',
+ '<(proto_in_dir)/user_action_event.proto',
+ ],
+ 'includes': [
+ '../common-mk/protoc.gypi'
+ ],
+ },
+ ],
+ 'conditions': [
+ ['USE_passive_metrics == 1', {
+ 'targets': [
+ {
+ 'target_name': 'metrics_daemon',
+ 'type': 'executable',
+ 'dependencies': ['libmetrics_daemon'],
+ },
+ ],
+ }],
+ ['USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'persistent_integer_test',
+ 'type': 'executable',
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'persistent_integer.cc',
+ 'persistent_integer_test.cc',
+ ]
+ },
+ {
+ 'target_name': 'metrics_library_test',
+ 'type': 'executable',
+ 'dependencies': [
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ ],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'metrics_library_test.cc',
+ 'serialization/serialization_utils_unittest.cc',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lpolicy-<(libbase_ver)',
+ ]
+ }
+ },
+ {
+ 'target_name': 'timer_test',
+ 'type': 'executable',
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'timer.cc',
+ 'timer_test.cc',
+ ]
+ },
+ {
+ 'target_name': 'upload_service_test',
+ 'type': 'executable',
+ 'sources': [
+ 'persistent_integer.cc',
+ 'uploader/metrics_hashes_unittest.cc',
+ 'uploader/metrics_log_base_unittest.cc',
+ 'uploader/mock/sender_mock.cc',
+ 'uploader/upload_service_test.cc',
+ ],
+ 'dependencies': [
+ 'libupload_service',
+ ],
+ 'includes':[
+ '../common-mk/common_test.gypi',
+ ],
+ 'include_dirs': ['.']
+ },
+ ],
+ }],
+ ['USE_passive_metrics == 1 and USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'metrics_daemon_test',
+ 'type': 'executable',
+ 'dependencies': [
+ 'libmetrics_daemon',
+ ],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'metrics_daemon_test.cc',
+ ],
+ 'include_dirs': ['.'],
+ },
+ ],
+ }],
+ ]
+}
diff --git a/metrics/metrics_client.cc b/metrics/metrics_client.cc
new file mode 100644
index 0000000..bbe9dcd
--- /dev/null
+++ b/metrics/metrics_client.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "metrics/metrics_library.h"
+
+enum Mode {
+ kModeSendSample,
+ kModeSendEnumSample,
+ kModeSendSparseSample,
+ kModeSendUserAction,
+ kModeSendCrosEvent,
+ kModeHasConsent,
+ kModeIsGuestMode,
+};
+
+void ShowUsage() {
+ fprintf(stderr,
+ "Usage: metrics_client [-ab] [-t] name sample min max nbuckets\n"
+ " metrics_client [-ab] -e name sample max\n"
+ " metrics_client [-ab] -s name sample\n"
+ " metrics_client [-ab] -v event\n"
+ " metrics_client -u action\n"
+ " metrics_client [-cg]\n"
+ "\n"
+ " default: send metric with integer values to Chrome only\n"
+ " |min| > 0, |min| <= sample < |max|\n"
+ " -a: send metric (name/sample) to Autotest only\n"
+ " -b: send metric to both Chrome and Autotest\n"
+ " -c: return exit status 0 if user consents to stats, 1 otherwise,\n"
+ " in guest mode always return 1\n"
+ " -e: send linear/enumeration histogram data\n"
+ " -g: return exit status 0 if machine in guest mode, 1 otherwise\n"
+ " -s: send a sparse histogram sample\n"
+ " -t: convert sample from double seconds to int milliseconds\n"
+ " -u: send a user action to Chrome\n"
+ " -v: send a Platform.CrOSEvent enum histogram sample\n");
+ exit(1);
+}
+
+static int ParseInt(const char *arg) {
+ char *endptr;
+ int value = strtol(arg, &endptr, 0);
+ if (*endptr != '\0') {
+ fprintf(stderr, "metrics client: bad integer \"%s\"\n", arg);
+ ShowUsage();
+ }
+ return value;
+}
+
+static double ParseDouble(const char *arg) {
+ char *endptr;
+ double value = strtod(arg, &endptr);
+ if (*endptr != '\0') {
+ fprintf(stderr, "metrics client: bad double \"%s\"\n", arg);
+ ShowUsage();
+ }
+ return value;
+}
+
+static int SendStats(char* argv[],
+ int name_index,
+ enum Mode mode,
+ bool secs_to_msecs,
+ bool send_to_autotest,
+ bool send_to_chrome) {
+ const char* name = argv[name_index];
+ int sample;
+ if (secs_to_msecs) {
+ sample = static_cast<int>(ParseDouble(argv[name_index + 1]) * 1000.0);
+ } else {
+ sample = ParseInt(argv[name_index + 1]);
+ }
+
+ // Send metrics
+ if (send_to_autotest) {
+ MetricsLibrary::SendToAutotest(name, sample);
+ }
+
+ if (send_to_chrome) {
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ if (mode == kModeSendSparseSample) {
+ metrics_lib.SendSparseToUMA(name, sample);
+ } else if (mode == kModeSendEnumSample) {
+ int max = ParseInt(argv[name_index + 2]);
+ metrics_lib.SendEnumToUMA(name, sample, max);
+ } else {
+ int min = ParseInt(argv[name_index + 2]);
+ int max = ParseInt(argv[name_index + 3]);
+ int nbuckets = ParseInt(argv[name_index + 4]);
+ metrics_lib.SendToUMA(name, sample, min, max, nbuckets);
+ }
+ }
+ return 0;
+}
+
+static int SendUserAction(char* argv[], int action_index) {
+ const char* action = argv[action_index];
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ metrics_lib.SendUserActionToUMA(action);
+ return 0;
+}
+
+static int SendCrosEvent(char* argv[], int action_index) {
+ const char* event = argv[action_index];
+ bool result;
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ result = metrics_lib.SendCrosEventToUMA(event);
+ if (!result) {
+ fprintf(stderr, "metrics_client: could not send event %s\n", event);
+ return 1;
+ }
+ return 0;
+}
+
+static int HasConsent() {
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ return metrics_lib.AreMetricsEnabled() ? 0 : 1;
+}
+
+static int IsGuestMode() {
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ return metrics_lib.IsGuestMode() ? 0 : 1;
+}
+
+int main(int argc, char** argv) {
+ enum Mode mode = kModeSendSample;
+ bool send_to_autotest = false;
+ bool send_to_chrome = true;
+ bool secs_to_msecs = false;
+
+ // Parse arguments
+ int flag;
+ while ((flag = getopt(argc, argv, "abcegstuv")) != -1) {
+ switch (flag) {
+ case 'a':
+ send_to_autotest = true;
+ send_to_chrome = false;
+ break;
+ case 'b':
+ send_to_chrome = true;
+ send_to_autotest = true;
+ break;
+ case 'c':
+ mode = kModeHasConsent;
+ break;
+ case 'e':
+ mode = kModeSendEnumSample;
+ break;
+ case 'g':
+ mode = kModeIsGuestMode;
+ break;
+ case 's':
+ mode = kModeSendSparseSample;
+ break;
+ case 't':
+ secs_to_msecs = true;
+ break;
+ case 'u':
+ mode = kModeSendUserAction;
+ break;
+ case 'v':
+ mode = kModeSendCrosEvent;
+ break;
+ default:
+ ShowUsage();
+ break;
+ }
+ }
+ int arg_index = optind;
+
+ int expected_args = 0;
+ if (mode == kModeSendSample)
+ expected_args = 5;
+ else if (mode == kModeSendEnumSample)
+ expected_args = 3;
+ else if (mode == kModeSendSparseSample)
+ expected_args = 2;
+ else if (mode == kModeSendUserAction)
+ expected_args = 1;
+ else if (mode == kModeSendCrosEvent)
+ expected_args = 1;
+
+ if ((arg_index + expected_args) != argc) {
+ ShowUsage();
+ }
+
+ switch (mode) {
+ case kModeSendSample:
+ case kModeSendEnumSample:
+ case kModeSendSparseSample:
+ if ((mode != kModeSendSample) && secs_to_msecs) {
+ ShowUsage();
+ }
+ return SendStats(argv,
+ arg_index,
+ mode,
+ secs_to_msecs,
+ send_to_autotest,
+ send_to_chrome);
+ case kModeSendUserAction:
+ return SendUserAction(argv, arg_index);
+ case kModeSendCrosEvent:
+ return SendCrosEvent(argv, arg_index);
+ case kModeHasConsent:
+ return HasConsent();
+ case kModeIsGuestMode:
+ return IsGuestMode();
+ default:
+ ShowUsage();
+ return 0;
+ }
+}
diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc
new file mode 100644
index 0000000..880e90c
--- /dev/null
+++ b/metrics/metrics_daemon.cc
@@ -0,0 +1,1167 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/metrics_daemon.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <math.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/hash.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/sys_info.h>
+#include <chromeos/dbus/service_constants.h>
+#include <dbus/dbus.h>
+#include <dbus/message.h>
+#include "uploader/upload_service.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using chromeos_metrics::PersistentInteger;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace {
+
+#define SAFE_MESSAGE(e) (e.message ? e.message : "unknown error")
+
+const char kCrashReporterInterface[] = "org.chromium.CrashReporter";
+const char kCrashReporterUserCrashSignal[] = "UserCrash";
+const char kCrashReporterMatchRule[] =
+ "type='signal',interface='%s',path='/',member='%s'";
+
+// Build type of an official build.
+// See src/third_party/chromiumos-overlay/chromeos/scripts/cros_set_lsb_release.
+const char kOfficialBuild[] = "Official Build";
+
+const int kSecondsPerMinute = 60;
+const int kMinutesPerHour = 60;
+const int kHoursPerDay = 24;
+const int kMinutesPerDay = kHoursPerDay * kMinutesPerHour;
+const int kSecondsPerDay = kSecondsPerMinute * kMinutesPerDay;
+const int kDaysPerWeek = 7;
+const int kSecondsPerWeek = kSecondsPerDay * kDaysPerWeek;
+
+// Interval between calls to UpdateStats().
+const uint32_t kUpdateStatsIntervalMs = 300000;
+
+const char kKernelCrashDetectedFile[] = "/var/run/kernel-crash-detected";
+const char kUncleanShutdownDetectedFile[] =
+ "/var/run/unclean-shutdown-detected";
+
+} // namespace
+
+// disk stats metrics
+
+// The {Read,Write}Sectors numbers are in sectors/second.
+// A sector is usually 512 bytes.
+
+const char MetricsDaemon::kMetricReadSectorsLongName[] =
+ "Platform.ReadSectorsLong";
+const char MetricsDaemon::kMetricWriteSectorsLongName[] =
+ "Platform.WriteSectorsLong";
+const char MetricsDaemon::kMetricReadSectorsShortName[] =
+ "Platform.ReadSectorsShort";
+const char MetricsDaemon::kMetricWriteSectorsShortName[] =
+ "Platform.WriteSectorsShort";
+
+const int MetricsDaemon::kMetricStatsShortInterval = 1; // seconds
+const int MetricsDaemon::kMetricStatsLongInterval = 30; // seconds
+
+const int MetricsDaemon::kMetricMeminfoInterval = 30; // seconds
+
+// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte
+// sectors.
+const int MetricsDaemon::kMetricSectorsIOMax = 500000; // sectors/second
+const int MetricsDaemon::kMetricSectorsBuckets = 50; // buckets
+// Page size is 4k, sector size is 0.5k. We're not interested in page fault
+// rates that the disk cannot sustain.
+const int MetricsDaemon::kMetricPageFaultsMax = kMetricSectorsIOMax / 8;
+const int MetricsDaemon::kMetricPageFaultsBuckets = 50;
+
+// Major page faults, i.e. the ones that require data to be read from disk.
+
+const char MetricsDaemon::kMetricPageFaultsLongName[] =
+ "Platform.PageFaultsLong";
+const char MetricsDaemon::kMetricPageFaultsShortName[] =
+ "Platform.PageFaultsShort";
+
+// Swap in and Swap out
+
+const char MetricsDaemon::kMetricSwapInLongName[] =
+ "Platform.SwapInLong";
+const char MetricsDaemon::kMetricSwapInShortName[] =
+ "Platform.SwapInShort";
+
+const char MetricsDaemon::kMetricSwapOutLongName[] =
+ "Platform.SwapOutLong";
+const char MetricsDaemon::kMetricSwapOutShortName[] =
+ "Platform.SwapOutShort";
+
+const char MetricsDaemon::kMetricsProcStatFileName[] = "/proc/stat";
+const int MetricsDaemon::kMetricsProcStatFirstLineItemsCount = 11;
+
+// Thermal CPU throttling.
+
+const char MetricsDaemon::kMetricScaledCpuFrequencyName[] =
+ "Platform.CpuFrequencyThermalScaling";
+
+// Zram sysfs entries.
+
+const char MetricsDaemon::kComprDataSizeName[] = "compr_data_size";
+const char MetricsDaemon::kOrigDataSizeName[] = "orig_data_size";
+const char MetricsDaemon::kZeroPagesName[] = "zero_pages";
+
+// Memory use stats collection intervals. We collect some memory use interval
+// at these intervals after boot, and we stop collecting after the last one,
+// with the assumption that in most cases the memory use won't change much
+// after that.
+static const int kMemuseIntervals[] = {
+ 1 * kSecondsPerMinute, // 1 minute mark
+ 4 * kSecondsPerMinute, // 5 minute mark
+ 25 * kSecondsPerMinute, // 0.5 hour mark
+ 120 * kSecondsPerMinute, // 2.5 hour mark
+ 600 * kSecondsPerMinute, // 12.5 hour mark
+};
+
+MetricsDaemon::MetricsDaemon()
+ : memuse_final_time_(0),
+ memuse_interval_index_(0),
+ read_sectors_(0),
+ write_sectors_(0),
+ vmstats_(),
+ stats_state_(kStatsShort),
+ stats_initial_time_(0),
+ ticks_per_second_(0),
+ latest_cpu_use_ticks_(0) {}
+
+MetricsDaemon::~MetricsDaemon() {
+}
+
+double MetricsDaemon::GetActiveTime() {
+ struct timespec ts;
+ int r = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (r < 0) {
+ PLOG(WARNING) << "clock_gettime(CLOCK_MONOTONIC) failed";
+ return 0;
+ } else {
+ return ts.tv_sec + static_cast<double>(ts.tv_nsec) / (1000 * 1000 * 1000);
+ }
+}
+
+int MetricsDaemon::Run() {
+ if (CheckSystemCrash(kKernelCrashDetectedFile)) {
+ ProcessKernelCrash();
+ }
+
+ if (CheckSystemCrash(kUncleanShutdownDetectedFile)) {
+ ProcessUncleanShutdown();
+ }
+
+ // On OS version change, clear version stats (which are reported daily).
+ int32_t version = GetOsVersionHash();
+ if (version_cycle_->Get() != version) {
+ version_cycle_->Set(version);
+ kernel_crashes_version_count_->Set(0);
+ version_cumulative_active_use_->Set(0);
+ version_cumulative_cpu_use_->Set(0);
+ }
+
+ return chromeos::DBusDaemon::Run();
+}
+
+void MetricsDaemon::RunUploaderTest() {
+ upload_service_.reset(new UploadService(new SystemProfileCache(true,
+ config_root_),
+ metrics_lib_,
+ server_));
+ upload_service_->Init(upload_interval_, metrics_file_);
+ upload_service_->UploadEvent();
+}
+
+uint32_t MetricsDaemon::GetOsVersionHash() {
+ static uint32_t cached_version_hash = 0;
+ static bool version_hash_is_cached = false;
+ if (version_hash_is_cached)
+ return cached_version_hash;
+ version_hash_is_cached = true;
+ std::string version;
+ if (base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_VERSION", &version)) {
+ cached_version_hash = base::Hash(version);
+ } else if (testing_) {
+ cached_version_hash = 42; // return any plausible value for the hash
+ } else {
+ LOG(FATAL) << "could not find CHROMEOS_RELEASE_VERSION";
+ }
+ return cached_version_hash;
+}
+
+bool MetricsDaemon::IsOnOfficialBuild() const {
+ std::string build_type;
+ return (base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BUILD_TYPE",
+ &build_type) &&
+ build_type == kOfficialBuild);
+}
+
+void MetricsDaemon::Init(bool testing,
+ bool uploader_active,
+ MetricsLibraryInterface* metrics_lib,
+ const string& diskstats_path,
+ const string& vmstats_path,
+ const string& scaling_max_freq_path,
+ const string& cpuinfo_max_freq_path,
+ const base::TimeDelta& upload_interval,
+ const string& server,
+ const string& metrics_file,
+ const string& config_root) {
+ testing_ = testing;
+ uploader_active_ = uploader_active;
+ config_root_ = config_root;
+ DCHECK(metrics_lib != nullptr);
+ metrics_lib_ = metrics_lib;
+
+ upload_interval_ = upload_interval;
+ server_ = server;
+ metrics_file_ = metrics_file;
+
+ // Get ticks per second (HZ) on this system.
+ // Sysconf cannot fail, so no sanity checks are needed.
+ ticks_per_second_ = sysconf(_SC_CLK_TCK);
+
+ daily_active_use_.reset(
+ new PersistentInteger("Platform.DailyUseTime"));
+ version_cumulative_active_use_.reset(
+ new PersistentInteger("Platform.CumulativeDailyUseTime"));
+ version_cumulative_cpu_use_.reset(
+ new PersistentInteger("Platform.CumulativeCpuTime"));
+
+ kernel_crash_interval_.reset(
+ new PersistentInteger("Platform.KernelCrashInterval"));
+ unclean_shutdown_interval_.reset(
+ new PersistentInteger("Platform.UncleanShutdownInterval"));
+ user_crash_interval_.reset(
+ new PersistentInteger("Platform.UserCrashInterval"));
+
+ any_crashes_daily_count_.reset(
+ new PersistentInteger("Platform.AnyCrashesDaily"));
+ any_crashes_weekly_count_.reset(
+ new PersistentInteger("Platform.AnyCrashesWeekly"));
+ user_crashes_daily_count_.reset(
+ new PersistentInteger("Platform.UserCrashesDaily"));
+ user_crashes_weekly_count_.reset(
+ new PersistentInteger("Platform.UserCrashesWeekly"));
+ kernel_crashes_daily_count_.reset(
+ new PersistentInteger("Platform.KernelCrashesDaily"));
+ kernel_crashes_weekly_count_.reset(
+ new PersistentInteger("Platform.KernelCrashesWeekly"));
+ kernel_crashes_version_count_.reset(
+ new PersistentInteger("Platform.KernelCrashesSinceUpdate"));
+ unclean_shutdowns_daily_count_.reset(
+ new PersistentInteger("Platform.UncleanShutdownsDaily"));
+ unclean_shutdowns_weekly_count_.reset(
+ new PersistentInteger("Platform.UncleanShutdownsWeekly"));
+
+ daily_cycle_.reset(new PersistentInteger("daily.cycle"));
+ weekly_cycle_.reset(new PersistentInteger("weekly.cycle"));
+ version_cycle_.reset(new PersistentInteger("version.cycle"));
+
+ diskstats_path_ = diskstats_path;
+ vmstats_path_ = vmstats_path;
+ scaling_max_freq_path_ = scaling_max_freq_path;
+ cpuinfo_max_freq_path_ = cpuinfo_max_freq_path;
+
+ // If testing, initialize Stats Reporter without connecting DBus
+ if (testing_)
+ StatsReporterInit();
+}
+
+int MetricsDaemon::OnInit() {
+ int return_code = chromeos::DBusDaemon::OnInit();
+ if (return_code != EX_OK)
+ return return_code;
+
+ StatsReporterInit();
+
+ // Start collecting meminfo stats.
+ ScheduleMeminfoCallback(kMetricMeminfoInterval);
+ memuse_final_time_ = GetActiveTime() + kMemuseIntervals[0];
+ ScheduleMemuseCallback(kMemuseIntervals[0]);
+
+ if (testing_)
+ return EX_OK;
+
+ bus_->AssertOnDBusThread();
+ CHECK(bus_->SetUpAsyncOperations());
+
+ if (bus_->is_connected()) {
+ const std::string match_rule =
+ base::StringPrintf(kCrashReporterMatchRule,
+ kCrashReporterInterface,
+ kCrashReporterUserCrashSignal);
+
+ bus_->AddFilterFunction(&MetricsDaemon::MessageFilter, this);
+
+ DBusError error;
+ dbus_error_init(&error);
+ bus_->AddMatch(match_rule, &error);
+
+ if (dbus_error_is_set(&error)) {
+ LOG(ERROR) << "Failed to add match rule \"" << match_rule << "\". Got "
+ << error.name << ": " << error.message;
+ return EX_SOFTWARE;
+ }
+ } else {
+ LOG(ERROR) << "DBus isn't connected.";
+ return EX_UNAVAILABLE;
+ }
+
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::HandleUpdateStatsTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
+
+ if (uploader_active_) {
+ if (IsOnOfficialBuild()) {
+ LOG(INFO) << "uploader enabled";
+ upload_service_.reset(
+ new UploadService(new SystemProfileCache(), metrics_lib_, server_));
+ upload_service_->Init(upload_interval_, metrics_file_);
+ } else {
+ LOG(INFO) << "uploader disabled on non-official build";
+ }
+ }
+
+ return EX_OK;
+}
+
+void MetricsDaemon::OnShutdown(int* return_code) {
+ if (!testing_ && bus_->is_connected()) {
+ const std::string match_rule =
+ base::StringPrintf(kCrashReporterMatchRule,
+ kCrashReporterInterface,
+ kCrashReporterUserCrashSignal);
+
+ bus_->RemoveFilterFunction(&MetricsDaemon::MessageFilter, this);
+
+ DBusError error;
+ dbus_error_init(&error);
+ bus_->RemoveMatch(match_rule, &error);
+
+ if (dbus_error_is_set(&error)) {
+ LOG(ERROR) << "Failed to remove match rule \"" << match_rule << "\". Got "
+ << error.name << ": " << error.message;
+ }
+ }
+ chromeos::DBusDaemon::OnShutdown(return_code);
+}
+
+// static
+DBusHandlerResult MetricsDaemon::MessageFilter(DBusConnection* connection,
+ DBusMessage* message,
+ void* user_data) {
+ int message_type = dbus_message_get_type(message);
+ if (message_type != DBUS_MESSAGE_TYPE_SIGNAL) {
+ DLOG(WARNING) << "unexpected message type " << message_type;
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ // Signal messages always have interfaces.
+ const std::string interface(dbus_message_get_interface(message));
+ const std::string member(dbus_message_get_member(message));
+ DLOG(INFO) << "Got " << interface << "." << member << " D-Bus signal";
+
+ MetricsDaemon* daemon = static_cast<MetricsDaemon*>(user_data);
+
+ DBusMessageIter iter;
+ dbus_message_iter_init(message, &iter);
+ if (interface == kCrashReporterInterface) {
+ CHECK_EQ(member, kCrashReporterUserCrashSignal);
+ daemon->ProcessUserCrash();
+ } else {
+ // Ignore messages from the bus itself.
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+// One might argue that parts of this should go into
+// chromium/src/base/sys_info_chromeos.c instead, but put it here for now.
+
+TimeDelta MetricsDaemon::GetIncrementalCpuUse() {
+ FilePath proc_stat_path = FilePath(kMetricsProcStatFileName);
+ std::string proc_stat_string;
+ if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) {
+ LOG(WARNING) << "cannot open " << kMetricsProcStatFileName;
+ return TimeDelta();
+ }
+
+ std::vector<std::string> proc_stat_lines;
+ base::SplitString(proc_stat_string, '\n', &proc_stat_lines);
+ if (proc_stat_lines.empty()) {
+ LOG(WARNING) << "cannot parse " << kMetricsProcStatFileName
+ << ": " << proc_stat_string;
+ return TimeDelta();
+ }
+ std::vector<std::string> proc_stat_totals;
+ base::SplitStringAlongWhitespace(proc_stat_lines[0], &proc_stat_totals);
+
+ uint64_t user_ticks, user_nice_ticks, system_ticks;
+ if (proc_stat_totals.size() != kMetricsProcStatFirstLineItemsCount ||
+ proc_stat_totals[0] != "cpu" ||
+ !base::StringToUint64(proc_stat_totals[1], &user_ticks) ||
+ !base::StringToUint64(proc_stat_totals[2], &user_nice_ticks) ||
+ !base::StringToUint64(proc_stat_totals[3], &system_ticks)) {
+ LOG(WARNING) << "cannot parse first line: " << proc_stat_lines[0];
+ return TimeDelta(base::TimeDelta::FromSeconds(0));
+ }
+
+ uint64_t total_cpu_use_ticks = user_ticks + user_nice_ticks + system_ticks;
+
+ // Sanity check.
+ if (total_cpu_use_ticks < latest_cpu_use_ticks_) {
+ LOG(WARNING) << "CPU time decreasing from " << latest_cpu_use_ticks_
+ << " to " << total_cpu_use_ticks;
+ return TimeDelta();
+ }
+
+ uint64_t diff = total_cpu_use_ticks - latest_cpu_use_ticks_;
+ latest_cpu_use_ticks_ = total_cpu_use_ticks;
+ // Use microseconds to avoid significant truncations.
+ return base::TimeDelta::FromMicroseconds(
+ diff * 1000 * 1000 / ticks_per_second_);
+}
+
+void MetricsDaemon::ProcessUserCrash() {
+ // Counts the active time up to now.
+ UpdateStats(TimeTicks::Now(), Time::Now());
+
+ // Reports the active use time since the last crash and resets it.
+ SendCrashIntervalSample(user_crash_interval_);
+
+ any_crashes_daily_count_->Add(1);
+ any_crashes_weekly_count_->Add(1);
+ user_crashes_daily_count_->Add(1);
+ user_crashes_weekly_count_->Add(1);
+}
+
+void MetricsDaemon::ProcessKernelCrash() {
+ // Counts the active time up to now.
+ UpdateStats(TimeTicks::Now(), Time::Now());
+
+ // Reports the active use time since the last crash and resets it.
+ SendCrashIntervalSample(kernel_crash_interval_);
+
+ any_crashes_daily_count_->Add(1);
+ any_crashes_weekly_count_->Add(1);
+ kernel_crashes_daily_count_->Add(1);
+ kernel_crashes_weekly_count_->Add(1);
+
+ kernel_crashes_version_count_->Add(1);
+}
+
+void MetricsDaemon::ProcessUncleanShutdown() {
+ // Counts the active time up to now.
+ UpdateStats(TimeTicks::Now(), Time::Now());
+
+ // Reports the active use time since the last crash and resets it.
+ SendCrashIntervalSample(unclean_shutdown_interval_);
+
+ unclean_shutdowns_daily_count_->Add(1);
+ unclean_shutdowns_weekly_count_->Add(1);
+ any_crashes_daily_count_->Add(1);
+ any_crashes_weekly_count_->Add(1);
+}
+
+bool MetricsDaemon::CheckSystemCrash(const string& crash_file) {
+ FilePath crash_detected(crash_file);
+ if (!base::PathExists(crash_detected))
+ return false;
+
+ // Deletes the crash-detected file so that the daemon doesn't report
+ // another kernel crash in case it's restarted.
+ base::DeleteFile(crash_detected, false); // not recursive
+ return true;
+}
+
+void MetricsDaemon::StatsReporterInit() {
+ DiskStatsReadStats(&read_sectors_, &write_sectors_);
+ VmStatsReadStats(&vmstats_);
+ // The first time around just run the long stat, so we don't delay boot.
+ stats_state_ = kStatsLong;
+ stats_initial_time_ = GetActiveTime();
+ if (stats_initial_time_ < 0) {
+ LOG(WARNING) << "not collecting disk stats";
+ } else {
+ ScheduleStatsCallback(kMetricStatsLongInterval);
+ }
+}
+
+void MetricsDaemon::ScheduleStatsCallback(int wait) {
+ if (testing_) {
+ return;
+ }
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::StatsCallback, base::Unretained(this)),
+ base::TimeDelta::FromSeconds(wait));
+}
+
+bool MetricsDaemon::DiskStatsReadStats(uint64_t* read_sectors,
+ uint64_t* write_sectors) {
+ int nchars;
+ int nitems;
+ bool success = false;
+ char line[200];
+ if (diskstats_path_.empty()) {
+ return false;
+ }
+ int file = HANDLE_EINTR(open(diskstats_path_.c_str(), O_RDONLY));
+ if (file < 0) {
+ PLOG(WARNING) << "cannot open " << diskstats_path_;
+ return false;
+ }
+ nchars = HANDLE_EINTR(read(file, line, sizeof(line)));
+ if (nchars < 0) {
+ PLOG(WARNING) << "cannot read from " << diskstats_path_;
+ return false;
+ } else {
+ LOG_IF(WARNING, nchars == sizeof(line))
+ << "line too long in " << diskstats_path_;
+ line[nchars] = '\0';
+ nitems = sscanf(line, "%*d %*d %" PRIu64 " %*d %*d %*d %" PRIu64,
+ read_sectors, write_sectors);
+ if (nitems == 2) {
+ success = true;
+ } else {
+ LOG(WARNING) << "found " << nitems << " items in "
+ << diskstats_path_ << ", expected 2";
+ }
+ }
+ IGNORE_EINTR(close(file));
+ return success;
+}
+
+bool MetricsDaemon::VmStatsParseStats(const char* stats,
+ struct VmstatRecord* record) {
+ // a mapping of string name to field in VmstatRecord and whether we found it
+ struct mapping {
+ const string name;
+ uint64_t* value_p;
+ bool found;
+ } map[] =
+ { { .name = "pgmajfault",
+ .value_p = &record->page_faults_,
+ .found = false },
+ { .name = "pswpin",
+ .value_p = &record->swap_in_,
+ .found = false },
+ { .name = "pswpout",
+ .value_p = &record->swap_out_,
+ .found = false }, };
+
+ // Each line in the file has the form
+ // <ID> <VALUE>
+ // for instance:
+ // nr_free_pages 213427
+ vector<string> lines;
+ Tokenize(stats, "\n", &lines);
+ for (vector<string>::iterator it = lines.begin();
+ it != lines.end(); ++it) {
+ vector<string> tokens;
+ base::SplitString(*it, ' ', &tokens);
+ if (tokens.size() == 2) {
+ for (unsigned int i = 0; i < sizeof(map)/sizeof(struct mapping); i++) {
+ if (!tokens[0].compare(map[i].name)) {
+ if (!base::StringToUint64(tokens[1], map[i].value_p))
+ return false;
+ map[i].found = true;
+ }
+ }
+ } else {
+ LOG(WARNING) << "unexpected vmstat format";
+ }
+ }
+ // make sure we got all the stats
+ for (unsigned i = 0; i < sizeof(map)/sizeof(struct mapping); i++) {
+ if (map[i].found == false) {
+ LOG(WARNING) << "vmstat missing " << map[i].name;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MetricsDaemon::VmStatsReadStats(struct VmstatRecord* stats) {
+ string value_string;
+ FilePath* path = new FilePath(vmstats_path_);
+ if (!base::ReadFileToString(*path, &value_string)) {
+ delete path;
+ LOG(WARNING) << "cannot read " << vmstats_path_;
+ return false;
+ }
+ delete path;
+ return VmStatsParseStats(value_string.c_str(), stats);
+}
+
+bool MetricsDaemon::ReadFreqToInt(const string& sysfs_file_name, int* value) {
+ const FilePath sysfs_path(sysfs_file_name);
+ string value_string;
+ if (!base::ReadFileToString(sysfs_path, &value_string)) {
+ LOG(WARNING) << "cannot read " << sysfs_path.value().c_str();
+ return false;
+ }
+ if (!base::RemoveChars(value_string, "\n", &value_string)) {
+ LOG(WARNING) << "no newline in " << value_string;
+ // Continue even though the lack of newline is suspicious.
+ }
+ if (!base::StringToInt(value_string, value)) {
+ LOG(WARNING) << "cannot convert " << value_string << " to int";
+ return false;
+ }
+ return true;
+}
+
+void MetricsDaemon::SendCpuThrottleMetrics() {
+ // |max_freq| is 0 only the first time through.
+ static int max_freq = 0;
+ if (max_freq == -1)
+ // Give up, as sysfs did not report max_freq correctly.
+ return;
+ if (max_freq == 0 || testing_) {
+ // One-time initialization of max_freq. (Every time when testing.)
+ if (!ReadFreqToInt(cpuinfo_max_freq_path_, &max_freq)) {
+ max_freq = -1;
+ return;
+ }
+ if (max_freq == 0) {
+ LOG(WARNING) << "sysfs reports 0 max CPU frequency\n";
+ max_freq = -1;
+ return;
+ }
+ if (max_freq % 10000 == 1000) {
+ // Special case: system has turbo mode, and max non-turbo frequency is
+ // max_freq - 1000. This relies on "normal" (non-turbo) frequencies
+ // being multiples of (at least) 10 MHz. Although there is no guarantee
+ // of this, it seems a fairly reasonable assumption. Otherwise we should
+ // read scaling_available_frequencies, sort the frequencies, compare the
+ // two highest ones, and check if they differ by 1000 (kHz) (and that's a
+ // hack too, no telling when it will change).
+ max_freq -= 1000;
+ }
+ }
+ int scaled_freq = 0;
+ if (!ReadFreqToInt(scaling_max_freq_path_, &scaled_freq))
+ return;
+ // Frequencies are in kHz. If scaled_freq > max_freq, turbo is on, but
+ // scaled_freq is not the actual turbo frequency. We indicate this situation
+ // with a 101% value.
+ int percent = scaled_freq > max_freq ? 101 : scaled_freq / (max_freq / 100);
+ SendLinearSample(kMetricScaledCpuFrequencyName, percent, 101, 102);
+}
+
+// Collects disk and vm stats alternating over a short and a long interval.
+
+void MetricsDaemon::StatsCallback() {
+ uint64_t read_sectors_now, write_sectors_now;
+ struct VmstatRecord vmstats_now;
+ double time_now = GetActiveTime();
+ double delta_time = time_now - stats_initial_time_;
+ if (testing_) {
+ // Fake the time when testing.
+ delta_time = stats_state_ == kStatsShort ?
+ kMetricStatsShortInterval : kMetricStatsLongInterval;
+ }
+ bool diskstats_success = DiskStatsReadStats(&read_sectors_now,
+ &write_sectors_now);
+ int delta_read = read_sectors_now - read_sectors_;
+ int delta_write = write_sectors_now - write_sectors_;
+ int read_sectors_per_second = delta_read / delta_time;
+ int write_sectors_per_second = delta_write / delta_time;
+ bool vmstats_success = VmStatsReadStats(&vmstats_now);
+ uint64_t delta_faults = vmstats_now.page_faults_ - vmstats_.page_faults_;
+ uint64_t delta_swap_in = vmstats_now.swap_in_ - vmstats_.swap_in_;
+ uint64_t delta_swap_out = vmstats_now.swap_out_ - vmstats_.swap_out_;
+ uint64_t page_faults_per_second = delta_faults / delta_time;
+ uint64_t swap_in_per_second = delta_swap_in / delta_time;
+ uint64_t swap_out_per_second = delta_swap_out / delta_time;
+
+ switch (stats_state_) {
+ case kStatsShort:
+ if (diskstats_success) {
+ SendSample(kMetricReadSectorsShortName,
+ read_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ SendSample(kMetricWriteSectorsShortName,
+ write_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ }
+ if (vmstats_success) {
+ SendSample(kMetricPageFaultsShortName,
+ page_faults_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapInShortName,
+ swap_in_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapOutShortName,
+ swap_out_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ }
+ // Schedule long callback.
+ stats_state_ = kStatsLong;
+ ScheduleStatsCallback(kMetricStatsLongInterval -
+ kMetricStatsShortInterval);
+ break;
+ case kStatsLong:
+ if (diskstats_success) {
+ SendSample(kMetricReadSectorsLongName,
+ read_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ SendSample(kMetricWriteSectorsLongName,
+ write_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ // Reset sector counters.
+ read_sectors_ = read_sectors_now;
+ write_sectors_ = write_sectors_now;
+ }
+ if (vmstats_success) {
+ SendSample(kMetricPageFaultsLongName,
+ page_faults_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapInLongName,
+ swap_in_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapOutLongName,
+ swap_out_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+
+ vmstats_ = vmstats_now;
+ }
+ SendCpuThrottleMetrics();
+ // Set start time for new cycle.
+ stats_initial_time_ = time_now;
+ // Schedule short callback.
+ stats_state_ = kStatsShort;
+ ScheduleStatsCallback(kMetricStatsShortInterval);
+ break;
+ default:
+ LOG(FATAL) << "Invalid stats state";
+ }
+}
+
+void MetricsDaemon::ScheduleMeminfoCallback(int wait) {
+ if (testing_) {
+ return;
+ }
+ base::TimeDelta waitDelta = base::TimeDelta::FromSeconds(wait);
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::MeminfoCallback, base::Unretained(this),
+ waitDelta),
+ waitDelta);
+}
+
+void MetricsDaemon::MeminfoCallback(base::TimeDelta wait) {
+ string meminfo_raw;
+ const FilePath meminfo_path("/proc/meminfo");
+ if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
+ LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+ return;
+ }
+ // Make both calls even if the first one fails.
+ bool success = ProcessMeminfo(meminfo_raw);
+ bool reschedule =
+ ReportZram(base::FilePath(FILE_PATH_LITERAL("/sys/block/zram0"))) &&
+ success;
+ if (reschedule) {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::MeminfoCallback, base::Unretained(this),
+ wait),
+ wait);
+ }
+}
+
+// static
+bool MetricsDaemon::ReadFileToUint64(const base::FilePath& path,
+ uint64_t* value) {
+ std::string content;
+ if (!base::ReadFileToString(path, &content)) {
+ PLOG(WARNING) << "cannot read " << path.MaybeAsASCII();
+ return false;
+ }
+ // Remove final newline.
+ base::TrimWhitespaceASCII(content, base::TRIM_TRAILING, &content);
+ if (!base::StringToUint64(content, value)) {
+ LOG(WARNING) << "invalid integer: " << content;
+ return false;
+ }
+ return true;
+}
+
+bool MetricsDaemon::ReportZram(const base::FilePath& zram_dir) {
+ // Data sizes are in bytes. |zero_pages| is in number of pages.
+ uint64_t compr_data_size, orig_data_size, zero_pages;
+ const size_t page_size = 4096;
+
+ if (!ReadFileToUint64(zram_dir.Append(kComprDataSizeName),
+ &compr_data_size) ||
+ !ReadFileToUint64(zram_dir.Append(kOrigDataSizeName), &orig_data_size) ||
+ !ReadFileToUint64(zram_dir.Append(kZeroPagesName), &zero_pages)) {
+ return false;
+ }
+
+ // |orig_data_size| does not include zero-filled pages.
+ orig_data_size += zero_pages * page_size;
+
+ const int compr_data_size_mb = compr_data_size >> 20;
+ const int savings_mb = (orig_data_size - compr_data_size) >> 20;
+ const int zero_ratio_percent = zero_pages * page_size * 100 / orig_data_size;
+
+ // Report compressed size in megabytes. 100 MB or less has little impact.
+ SendSample("Platform.ZramCompressedSize", compr_data_size_mb, 100, 4000, 50);
+ SendSample("Platform.ZramSavings", savings_mb, 100, 4000, 50);
+ // The compression ratio is multiplied by 100 for better resolution. The
+ // ratios of interest are between 1 and 6 (100% and 600% as reported). We
+ // don't want samples when very little memory is being compressed.
+ if (compr_data_size_mb >= 1) {
+ SendSample("Platform.ZramCompressionRatioPercent",
+ orig_data_size * 100 / compr_data_size, 100, 600, 50);
+ }
+ // The values of interest for zero_pages are between 1MB and 1GB. The units
+ // are number of pages.
+ SendSample("Platform.ZramZeroPages", zero_pages, 256, 256 * 1024, 50);
+ SendSample("Platform.ZramZeroRatioPercent", zero_ratio_percent, 1, 50, 50);
+
+ return true;
+}
+
+bool MetricsDaemon::ProcessMeminfo(const string& meminfo_raw) {
+ static const MeminfoRecord fields_array[] = {
+ { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
+ { "MemFree", "MemFree" },
+ { "Buffers", "Buffers" },
+ { "Cached", "Cached" },
+ // { "SwapCached", "SwapCached" },
+ { "Active", "Active" },
+ { "Inactive", "Inactive" },
+ { "ActiveAnon", "Active(anon)" },
+ { "InactiveAnon", "Inactive(anon)" },
+ { "ActiveFile" , "Active(file)" },
+ { "InactiveFile", "Inactive(file)" },
+ { "Unevictable", "Unevictable", kMeminfoOp_HistLog },
+ // { "Mlocked", "Mlocked" },
+ { "SwapTotal", "SwapTotal", kMeminfoOp_SwapTotal },
+ { "SwapFree", "SwapFree", kMeminfoOp_SwapFree },
+ // { "Dirty", "Dirty" },
+ // { "Writeback", "Writeback" },
+ { "AnonPages", "AnonPages" },
+ { "Mapped", "Mapped" },
+ { "Shmem", "Shmem", kMeminfoOp_HistLog },
+ { "Slab", "Slab", kMeminfoOp_HistLog },
+ // { "SReclaimable", "SReclaimable" },
+ // { "SUnreclaim", "SUnreclaim" },
+ };
+ vector<MeminfoRecord> fields(fields_array,
+ fields_array + arraysize(fields_array));
+ if (!FillMeminfo(meminfo_raw, &fields)) {
+ return false;
+ }
+ int total_memory = fields[0].value;
+ if (total_memory == 0) {
+ // this "cannot happen"
+ LOG(WARNING) << "borked meminfo parser";
+ return false;
+ }
+ int swap_total = 0;
+ int swap_free = 0;
+ // Send all fields retrieved, except total memory.
+ for (unsigned int i = 1; i < fields.size(); i++) {
+ string metrics_name = base::StringPrintf("Platform.Meminfo%s",
+ fields[i].name);
+ int percent;
+ switch (fields[i].op) {
+ case kMeminfoOp_HistPercent:
+ // report value as percent of total memory
+ percent = fields[i].value * 100 / total_memory;
+ SendLinearSample(metrics_name, percent, 100, 101);
+ break;
+ case kMeminfoOp_HistLog:
+ // report value in kbytes, log scale, 4Gb max
+ SendSample(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100);
+ break;
+ case kMeminfoOp_SwapTotal:
+ swap_total = fields[i].value;
+ case kMeminfoOp_SwapFree:
+ swap_free = fields[i].value;
+ break;
+ }
+ }
+ if (swap_total > 0) {
+ int swap_used = swap_total - swap_free;
+ int swap_used_percent = swap_used * 100 / swap_total;
+ SendSample("Platform.MeminfoSwapUsed", swap_used, 1, 8 * 1000 * 1000, 100);
+ SendLinearSample("Platform.MeminfoSwapUsedPercent", swap_used_percent,
+ 100, 101);
+ }
+ return true;
+}
+
+bool MetricsDaemon::FillMeminfo(const string& meminfo_raw,
+ vector<MeminfoRecord>* fields) {
+ vector<string> lines;
+ unsigned int nlines = Tokenize(meminfo_raw, "\n", &lines);
+
+ // Scan meminfo output and collect field values. Each field name has to
+ // match a meminfo entry (case insensitive) after removing non-alpha
+ // characters from the entry.
+ unsigned int ifield = 0;
+ for (unsigned int iline = 0;
+ iline < nlines && ifield < fields->size();
+ iline++) {
+ vector<string> tokens;
+ Tokenize(lines[iline], ": ", &tokens);
+ if (strcmp((*fields)[ifield].match, tokens[0].c_str()) == 0) {
+ // Name matches. Parse value and save.
+ char* rest;
+ (*fields)[ifield].value =
+ static_cast<int>(strtol(tokens[1].c_str(), &rest, 10));
+ if (*rest != '\0') {
+ LOG(WARNING) << "missing meminfo value";
+ return false;
+ }
+ ifield++;
+ }
+ }
+ if (ifield < fields->size()) {
+ // End of input reached while scanning.
+ LOG(WARNING) << "cannot find field " << (*fields)[ifield].match
+ << " and following";
+ return false;
+ }
+ return true;
+}
+
+void MetricsDaemon::ScheduleMemuseCallback(double interval) {
+ if (testing_) {
+ return;
+ }
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::MemuseCallback, base::Unretained(this)),
+ base::TimeDelta::FromSeconds(interval));
+}
+
+void MetricsDaemon::MemuseCallback() {
+ // Since we only care about active time (i.e. uptime minus sleep time) but
+ // the callbacks are driven by real time (uptime), we check if we should
+ // reschedule this callback due to intervening sleep periods.
+ double now = GetActiveTime();
+ // Avoid intervals of less than one second.
+ double remaining_time = ceil(memuse_final_time_ - now);
+ if (remaining_time > 0) {
+ ScheduleMemuseCallback(remaining_time);
+ } else {
+ // Report stats and advance the measurement interval unless there are
+ // errors or we've completed the last interval.
+ if (MemuseCallbackWork() &&
+ memuse_interval_index_ < arraysize(kMemuseIntervals)) {
+ double interval = kMemuseIntervals[memuse_interval_index_++];
+ memuse_final_time_ = now + interval;
+ ScheduleMemuseCallback(interval);
+ }
+ }
+}
+
+bool MetricsDaemon::MemuseCallbackWork() {
+ string meminfo_raw;
+ const FilePath meminfo_path("/proc/meminfo");
+ if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
+ LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+ return false;
+ }
+ return ProcessMemuse(meminfo_raw);
+}
+
+bool MetricsDaemon::ProcessMemuse(const string& meminfo_raw) {
+ static const MeminfoRecord fields_array[] = {
+ { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
+ { "ActiveAnon", "Active(anon)" },
+ { "InactiveAnon", "Inactive(anon)" },
+ };
+ vector<MeminfoRecord> fields(fields_array,
+ fields_array + arraysize(fields_array));
+ if (!FillMeminfo(meminfo_raw, &fields)) {
+ return false;
+ }
+ int total = fields[0].value;
+ int active_anon = fields[1].value;
+ int inactive_anon = fields[2].value;
+ if (total == 0) {
+ // this "cannot happen"
+ LOG(WARNING) << "borked meminfo parser";
+ return false;
+ }
+ string metrics_name = base::StringPrintf("Platform.MemuseAnon%d",
+ memuse_interval_index_);
+ SendLinearSample(metrics_name, (active_anon + inactive_anon) * 100 / total,
+ 100, 101);
+ return true;
+}
+
+void MetricsDaemon::SendSample(const string& name, int sample,
+ int min, int max, int nbuckets) {
+ metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
+}
+
+void MetricsDaemon::SendKernelCrashesCumulativeCountStats() {
+ // Report the number of crashes for this OS version, but don't clear the
+ // counter. It is cleared elsewhere on version change.
+ int64_t crashes_count = kernel_crashes_version_count_->Get();
+ SendSample(kernel_crashes_version_count_->Name(),
+ crashes_count,
+ 1, // value of first bucket
+ 500, // value of last bucket
+ 100); // number of buckets
+
+
+ int64_t cpu_use_ms = version_cumulative_cpu_use_->Get();
+ SendSample(version_cumulative_cpu_use_->Name(),
+ cpu_use_ms / 1000, // stat is in seconds
+ 1, // device may be used very little...
+ 8 * 1000 * 1000, // ... or a lot (a little over 90 days)
+ 100);
+
+ // On the first run after an autoupdate, cpu_use_ms and active_use_seconds
+ // can be zero. Avoid division by zero.
+ if (cpu_use_ms > 0) {
+ // Send the crash frequency since update in number of crashes per CPU year.
+ SendSample("Logging.KernelCrashesPerCpuYear",
+ crashes_count * kSecondsPerDay * 365 * 1000 / cpu_use_ms,
+ 1,
+ 1000 * 1000, // about one crash every 30s of CPU time
+ 100);
+ }
+
+ int64_t active_use_seconds = version_cumulative_active_use_->Get();
+ if (active_use_seconds > 0) {
+ SendSample(version_cumulative_active_use_->Name(),
+ active_use_seconds / 1000, // stat is in seconds
+ 1, // device may be used very little...
+ 8 * 1000 * 1000, // ... or a lot (about 90 days)
+ 100);
+ // Same as above, but per year of active time.
+ SendSample("Logging.KernelCrashesPerActiveYear",
+ crashes_count * kSecondsPerDay * 365 / active_use_seconds,
+ 1,
+ 1000 * 1000, // about one crash every 30s of active time
+ 100);
+ }
+}
+
+void MetricsDaemon::SendDailyUseSample(
+ const scoped_ptr<PersistentInteger>& use) {
+ SendSample(use->Name(),
+ use->GetAndClear(),
+ 1, // value of first bucket
+ kSecondsPerDay, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsDaemon::SendCrashIntervalSample(
+ const scoped_ptr<PersistentInteger>& interval) {
+ SendSample(interval->Name(),
+ interval->GetAndClear(),
+ 1, // value of first bucket
+ 4 * kSecondsPerWeek, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsDaemon::SendCrashFrequencySample(
+ const scoped_ptr<PersistentInteger>& frequency) {
+ SendSample(frequency->Name(),
+ frequency->GetAndClear(),
+ 1, // value of first bucket
+ 100, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsDaemon::SendLinearSample(const string& name, int sample,
+ int max, int nbuckets) {
+ // TODO(semenzato): add a proper linear histogram to the Chrome external
+ // metrics API.
+ LOG_IF(FATAL, nbuckets != max + 1) << "unsupported histogram scale";
+ metrics_lib_->SendEnumToUMA(name, sample, max);
+}
+
+void MetricsDaemon::UpdateStats(TimeTicks now_ticks,
+ Time now_wall_time) {
+ const int elapsed_seconds = (now_ticks - last_update_stats_time_).InSeconds();
+ daily_active_use_->Add(elapsed_seconds);
+ version_cumulative_active_use_->Add(elapsed_seconds);
+ user_crash_interval_->Add(elapsed_seconds);
+ kernel_crash_interval_->Add(elapsed_seconds);
+ version_cumulative_cpu_use_->Add(GetIncrementalCpuUse().InMilliseconds());
+ last_update_stats_time_ = now_ticks;
+
+ const TimeDelta since_epoch = now_wall_time - Time::UnixEpoch();
+ const int day = since_epoch.InDays();
+ const int week = day / 7;
+
+ if (daily_cycle_->Get() != day) {
+ daily_cycle_->Set(day);
+ SendDailyUseSample(daily_active_use_);
+ SendDailyUseSample(version_cumulative_active_use_);
+ SendCrashFrequencySample(any_crashes_daily_count_);
+ SendCrashFrequencySample(user_crashes_daily_count_);
+ SendCrashFrequencySample(kernel_crashes_daily_count_);
+ SendCrashFrequencySample(unclean_shutdowns_daily_count_);
+ SendKernelCrashesCumulativeCountStats();
+ }
+
+ if (weekly_cycle_->Get() != week) {
+ weekly_cycle_->Set(week);
+ SendCrashFrequencySample(any_crashes_weekly_count_);
+ SendCrashFrequencySample(user_crashes_weekly_count_);
+ SendCrashFrequencySample(kernel_crashes_weekly_count_);
+ SendCrashFrequencySample(unclean_shutdowns_weekly_count_);
+ }
+}
+
+void MetricsDaemon::HandleUpdateStatsTimeout() {
+ UpdateStats(TimeTicks::Now(), Time::Now());
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::HandleUpdateStatsTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
+}
diff --git a/metrics/metrics_daemon.h b/metrics/metrics_daemon.h
new file mode 100644
index 0000000..b1b2d11
--- /dev/null
+++ b/metrics/metrics_daemon.h
@@ -0,0 +1,371 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_METRICS_DAEMON_H_
+#define METRICS_METRICS_DAEMON_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/time/time.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "metrics/metrics_library.h"
+#include "metrics/persistent_integer.h"
+#include "uploader/upload_service.h"
+
+using chromeos_metrics::PersistentInteger;
+
+class MetricsDaemon : public chromeos::DBusDaemon {
+ public:
+ MetricsDaemon();
+ ~MetricsDaemon();
+
+ // Initializes metrics class variables.
+ void Init(bool testing,
+ bool uploader_active,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& diskstats_path,
+ const std::string& vmstats_path,
+ const std::string& cpuinfo_max_freq_path,
+ const std::string& scaling_max_freq_path,
+ const base::TimeDelta& upload_interval,
+ const std::string& server,
+ const std::string& metrics_file,
+ const std::string& config_root);
+
+ // Initializes DBus and MessageLoop variables before running the MessageLoop.
+ int OnInit() override;
+
+ // Clean up data set up in OnInit before shutting down message loop.
+ void OnShutdown(int* return_code) override;
+
+ // Does all the work.
+ int Run() override;
+
+ // Triggers an upload event and exit. (Used to test UploadService)
+ void RunUploaderTest();
+
+ protected:
+ // Used also by the unit tests.
+ static const char kComprDataSizeName[];
+ static const char kOrigDataSizeName[];
+ static const char kZeroPagesName[];
+
+ private:
+ friend class MetricsDaemonTest;
+ FRIEND_TEST(MetricsDaemonTest, CheckSystemCrash);
+ FRIEND_TEST(MetricsDaemonTest, ComputeEpochNoCurrent);
+ FRIEND_TEST(MetricsDaemonTest, ComputeEpochNoLast);
+ FRIEND_TEST(MetricsDaemonTest, GetHistogramPath);
+ FRIEND_TEST(MetricsDaemonTest, IsNewEpoch);
+ FRIEND_TEST(MetricsDaemonTest, MessageFilter);
+ FRIEND_TEST(MetricsDaemonTest, ParseVmStats);
+ FRIEND_TEST(MetricsDaemonTest, ProcessKernelCrash);
+ FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo);
+ FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo2);
+ FRIEND_TEST(MetricsDaemonTest, ProcessUncleanShutdown);
+ FRIEND_TEST(MetricsDaemonTest, ProcessUserCrash);
+ FRIEND_TEST(MetricsDaemonTest, ReportCrashesDailyFrequency);
+ FRIEND_TEST(MetricsDaemonTest, ReadFreqToInt);
+ FRIEND_TEST(MetricsDaemonTest, ReportDiskStats);
+ FRIEND_TEST(MetricsDaemonTest, ReportKernelCrashInterval);
+ FRIEND_TEST(MetricsDaemonTest, ReportUncleanShutdownInterval);
+ FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval);
+ FRIEND_TEST(MetricsDaemonTest, SendSample);
+ FRIEND_TEST(MetricsDaemonTest, SendCpuThrottleMetrics);
+ FRIEND_TEST(MetricsDaemonTest, SendZramMetrics);
+
+ // State for disk stats collector callback.
+ enum StatsState {
+ kStatsShort, // short wait before short interval collection
+ kStatsLong, // final wait before new collection
+ };
+
+ // Data record for aggregating daily usage.
+ class UseRecord {
+ public:
+ UseRecord() : day_(0), seconds_(0) {}
+ int day_;
+ int seconds_;
+ };
+
+ // Type of scale to use for meminfo histograms. For most of them we use
+ // percent of total RAM, but for some we use absolute numbers, usually in
+ // megabytes, on a log scale from 0 to 4000, and 0 to 8000 for compressed
+ // swap (since it can be larger than total RAM).
+ enum MeminfoOp {
+ kMeminfoOp_HistPercent = 0,
+ kMeminfoOp_HistLog,
+ kMeminfoOp_SwapTotal,
+ kMeminfoOp_SwapFree,
+ };
+
+ // Record for retrieving and reporting values from /proc/meminfo.
+ struct MeminfoRecord {
+ const char* name; // print name
+ const char* match; // string to match in output of /proc/meminfo
+ MeminfoOp op; // histogram scale selector, or other operator
+ int value; // value from /proc/meminfo
+ };
+
+ // Record for retrieving and reporting values from /proc/vmstat
+ struct VmstatRecord {
+ uint64_t page_faults_; // major faults
+ uint64_t swap_in_; // pages swapped in
+ uint64_t swap_out_; // pages swapped out
+ };
+
+ // Metric parameters.
+ static const char kMetricReadSectorsLongName[];
+ static const char kMetricReadSectorsShortName[];
+ static const char kMetricWriteSectorsLongName[];
+ static const char kMetricWriteSectorsShortName[];
+ static const char kMetricPageFaultsShortName[];
+ static const char kMetricPageFaultsLongName[];
+ static const char kMetricSwapInLongName[];
+ static const char kMetricSwapInShortName[];
+ static const char kMetricSwapOutLongName[];
+ static const char kMetricSwapOutShortName[];
+ static const char kMetricScaledCpuFrequencyName[];
+ static const int kMetricStatsShortInterval;
+ static const int kMetricStatsLongInterval;
+ static const int kMetricMeminfoInterval;
+ static const int kMetricSectorsIOMax;
+ static const int kMetricSectorsBuckets;
+ static const int kMetricPageFaultsMax;
+ static const int kMetricPageFaultsBuckets;
+ static const char kMetricsDiskStatsPath[];
+ static const char kMetricsVmStatsPath[];
+ static const char kMetricsProcStatFileName[];
+ static const int kMetricsProcStatFirstLineItemsCount;
+
+ // Returns the active time since boot (uptime minus sleep time) in seconds.
+ double GetActiveTime();
+
+ // D-Bus filter callback.
+ static DBusHandlerResult MessageFilter(DBusConnection* connection,
+ DBusMessage* message,
+ void* user_data);
+
+ // Updates the daily usage file, if necessary, by adding |seconds|
+ // of active use to the |day| since Epoch. If there's usage data for
+ // day in the past in the usage file, that data is sent to UMA and
+ // removed from the file. If there's already usage data for |day| in
+ // the usage file, the |seconds| are accumulated.
+ void LogDailyUseRecord(int day, int seconds);
+
+ // Updates the active use time and logs time between user-space
+ // process crashes.
+ void ProcessUserCrash();
+
+ // Updates the active use time and logs time between kernel crashes.
+ void ProcessKernelCrash();
+
+ // Updates the active use time and logs time between unclean shutdowns.
+ void ProcessUncleanShutdown();
+
+ // Checks if a kernel crash has been detected and returns true if
+ // so. The method assumes that a kernel crash has happened if
+ // |crash_file| exists. It removes the file immediately if it
+ // exists, so it must not be called more than once.
+ bool CheckSystemCrash(const std::string& crash_file);
+
+ // Sends a regular (exponential) histogram sample to Chrome for
+ // transport to UMA. See MetricsLibrary::SendToUMA in
+ // metrics_library.h for a description of the arguments.
+ void SendSample(const std::string& name, int sample,
+ int min, int max, int nbuckets);
+
+ // Sends a linear histogram sample to Chrome for transport to UMA. See
+ // MetricsLibrary::SendToUMA in metrics_library.h for a description of the
+ // arguments.
+ void SendLinearSample(const std::string& name, int sample,
+ int max, int nbuckets);
+
+ // Sends various cumulative kernel crash-related stats, for instance the
+ // total number of kernel crashes since the last version update.
+ void SendKernelCrashesCumulativeCountStats();
+
+ // Returns the total (system-wide) CPU usage between the time of the most
+ // recent call to this function and now.
+ base::TimeDelta GetIncrementalCpuUse();
+
+ // Sends a sample representing the number of seconds of active use
+ // for a 24-hour period.
+ void SendDailyUseSample(const scoped_ptr<PersistentInteger>& use);
+
+ // Sends a sample representing a time interval between two crashes of the
+ // same type.
+ void SendCrashIntervalSample(const scoped_ptr<PersistentInteger>& interval);
+
+ // Sends a sample representing a frequency of crashes of some type.
+ void SendCrashFrequencySample(const scoped_ptr<PersistentInteger>& frequency);
+
+ // Initializes vm and disk stats reporting.
+ void StatsReporterInit();
+
+ // Schedules a callback for the next vm and disk stats collection.
+ void ScheduleStatsCallback(int wait);
+
+ // Reads cumulative disk statistics from sysfs. Returns true for success.
+ bool DiskStatsReadStats(uint64_t* read_sectors, uint64_t* write_sectors);
+
+ // Reads cumulative vm statistics from procfs. Returns true for success.
+ bool VmStatsReadStats(struct VmstatRecord* stats);
+
+ // Parse cumulative vm statistics from a C string. Returns true for success.
+ bool VmStatsParseStats(const char* stats, struct VmstatRecord* record);
+
+ // Reports disk and vm statistics.
+ void StatsCallback();
+
+ // Schedules meminfo collection callback.
+ void ScheduleMeminfoCallback(int wait);
+
+ // Reports memory statistics. Reschedules callback on success.
+ void MeminfoCallback(base::TimeDelta wait);
+
+ // Parses content of /proc/meminfo and sends fields of interest to UMA.
+ // Returns false on errors. |meminfo_raw| contains the content of
+ // /proc/meminfo.
+ bool ProcessMeminfo(const std::string& meminfo_raw);
+
+ // Parses meminfo data from |meminfo_raw|. |fields| is a vector containing
+ // the fields of interest. The order of the fields must be the same in which
+ // /proc/meminfo prints them. The result of parsing fields[i] is placed in
+ // fields[i].value.
+ bool FillMeminfo(const std::string& meminfo_raw,
+ std::vector<MeminfoRecord>* fields);
+
+ // Schedule a memory use callback in |interval| seconds.
+ void ScheduleMemuseCallback(double interval);
+
+ // Calls MemuseCallbackWork, and possibly schedules next callback, if enough
+ // active time has passed. Otherwise reschedules itself to simulate active
+ // time callbacks (i.e. wall clock time minus sleep time).
+ void MemuseCallback();
+
+ // Reads /proc/meminfo and sends total anonymous memory usage to UMA.
+ bool MemuseCallbackWork();
+
+ // Parses meminfo data and sends it to UMA.
+ bool ProcessMemuse(const std::string& meminfo_raw);
+
+ // Sends stats for thermal CPU throttling.
+ void SendCpuThrottleMetrics();
+
+ // Reads an integer CPU frequency value from sysfs.
+ bool ReadFreqToInt(const std::string& sysfs_file_name, int* value);
+
+ // Reads the current OS version from /etc/lsb-release and hashes it
+ // to a unsigned 32-bit int.
+ uint32_t GetOsVersionHash();
+
+ // Returns true if the system is using an official build.
+ bool IsOnOfficialBuild() const;
+
+ // Updates stats, additionally sending them to UMA if enough time has elapsed
+ // since the last report.
+ void UpdateStats(base::TimeTicks now_ticks, base::Time now_wall_time);
+
+ // Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
+ void HandleUpdateStatsTimeout();
+
+ // Reports zram statistics.
+ bool ReportZram(const base::FilePath& zram_dir);
+
+ // Reads a string from a file and converts it to uint64_t.
+ static bool ReadFileToUint64(const base::FilePath& path, uint64_t* value);
+
+ // VARIABLES
+
+ // Test mode.
+ bool testing_;
+
+ // Whether the uploader is enabled or disabled.
+ bool uploader_active_;
+
+ // Root of the configuration files to use.
+ std::string config_root_;
+
+ // The metrics library handle.
+ MetricsLibraryInterface* metrics_lib_;
+
+ // Timestamps last network state update. This timestamp is used to
+ // sample the time from the network going online to going offline so
+ // TimeTicks ensures a monotonically increasing TimeDelta.
+ base::TimeTicks network_state_last_;
+
+ // The last time that UpdateStats() was called.
+ base::TimeTicks last_update_stats_time_;
+
+ // End time of current memuse stat collection interval.
+ double memuse_final_time_;
+
+ // Selects the wait time for the next memory use callback.
+ unsigned int memuse_interval_index_;
+
+ // Contain the most recent disk and vm cumulative stats.
+ uint64_t read_sectors_;
+ uint64_t write_sectors_;
+ struct VmstatRecord vmstats_;
+
+ StatsState stats_state_;
+ double stats_initial_time_;
+
+ // The system "HZ", or frequency of ticks. Some system data uses ticks as a
+ // unit, and this is used to convert to standard time units.
+ uint32_t ticks_per_second_;
+ // Used internally by GetIncrementalCpuUse() to return the CPU utilization
+ // between calls.
+ uint64_t latest_cpu_use_ticks_;
+
+ // Persistent values and accumulators for crash statistics.
+ scoped_ptr<PersistentInteger> daily_cycle_;
+ scoped_ptr<PersistentInteger> weekly_cycle_;
+ scoped_ptr<PersistentInteger> version_cycle_;
+
+ // Active use accumulated in a day.
+ scoped_ptr<PersistentInteger> daily_active_use_;
+ // Active use accumulated since the latest version update.
+ scoped_ptr<PersistentInteger> version_cumulative_active_use_;
+
+ // The CPU time accumulator. This contains the CPU time, in milliseconds,
+ // used by the system since the most recent OS version update.
+ scoped_ptr<PersistentInteger> version_cumulative_cpu_use_;
+
+ scoped_ptr<PersistentInteger> user_crash_interval_;
+ scoped_ptr<PersistentInteger> kernel_crash_interval_;
+ scoped_ptr<PersistentInteger> unclean_shutdown_interval_;
+
+ scoped_ptr<PersistentInteger> any_crashes_daily_count_;
+ scoped_ptr<PersistentInteger> any_crashes_weekly_count_;
+ scoped_ptr<PersistentInteger> user_crashes_daily_count_;
+ scoped_ptr<PersistentInteger> user_crashes_weekly_count_;
+ scoped_ptr<PersistentInteger> kernel_crashes_daily_count_;
+ scoped_ptr<PersistentInteger> kernel_crashes_weekly_count_;
+ scoped_ptr<PersistentInteger> kernel_crashes_version_count_;
+ scoped_ptr<PersistentInteger> unclean_shutdowns_daily_count_;
+ scoped_ptr<PersistentInteger> unclean_shutdowns_weekly_count_;
+
+ std::string diskstats_path_;
+ std::string vmstats_path_;
+ std::string scaling_max_freq_path_;
+ std::string cpuinfo_max_freq_path_;
+
+ base::TimeDelta upload_interval_;
+ std::string server_;
+ std::string metrics_file_;
+
+ scoped_ptr<UploadService> upload_service_;
+};
+
+#endif // METRICS_METRICS_DAEMON_H_
diff --git a/metrics/metrics_daemon_main.cc b/metrics/metrics_daemon_main.cc
new file mode 100644
index 0000000..1f64ef3
--- /dev/null
+++ b/metrics/metrics_daemon_main.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <chromeos/flag_helper.h>
+#include <chromeos/syslog_logging.h>
+#include <rootdev/rootdev.h>
+
+#include "metrics/metrics_daemon.h"
+
+const char kScalingMaxFreqPath[] =
+ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq";
+const char kCpuinfoMaxFreqPath[] =
+ "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
+
+// Returns the path to the disk stats in the sysfs. Returns the null string if
+// it cannot find the disk stats file.
+static
+const std::string MetricsMainDiskStatsPath() {
+ char dev_path_cstr[PATH_MAX];
+ std::string dev_prefix = "/dev/";
+ std::string dev_path;
+ std::string dev_name;
+
+ int ret = rootdev(dev_path_cstr, sizeof(dev_path_cstr), true, true);
+ if (ret != 0) {
+ LOG(WARNING) << "error " << ret << " determining root device";
+ return "";
+ }
+ dev_path = dev_path_cstr;
+ // Check that rootdev begins with "/dev/".
+ if (!base::StartsWithASCII(dev_path, dev_prefix, false)) {
+ LOG(WARNING) << "unexpected root device " << dev_path;
+ return "";
+ }
+ // Get the device name, e.g. "sda" from "/dev/sda".
+ dev_name = dev_path.substr(dev_prefix.length());
+ return "/sys/class/block/" + dev_name + "/stat";
+}
+
+int main(int argc, char** argv) {
+ DEFINE_bool(daemon, true, "run as daemon (use -nodaemon for debugging)");
+
+ // The uploader is disabled by default on ChromeOS as Chrome is responsible
+ // for sending the metrics.
+ DEFINE_bool(uploader, false, "activate the uploader");
+
+ // Upload the metrics once and exit. (used for testing)
+ DEFINE_bool(uploader_test,
+ false,
+ "run the uploader once and exit");
+
+ // Upload Service flags.
+ DEFINE_int32(upload_interval_secs,
+ 1800,
+ "Interval at which metrics_daemon sends the metrics. (needs "
+ "-uploader)");
+ DEFINE_string(server,
+ "https://clients4.google.com/uma/v2",
+ "Server to upload the metrics to. (needs -uploader)");
+ DEFINE_string(metrics_file,
+ "/var/lib/metrics/uma-events",
+ "File to use as a proxy for uploading the metrics");
+ DEFINE_string(config_root,
+ "/", "Root of the configuration files (testing only)");
+
+ chromeos::FlagHelper::Init(argc, argv, "Chromium OS Metrics Daemon");
+
+ // Also log to stderr when not running as daemon.
+ chromeos::InitLog(chromeos::kLogToSyslog | chromeos::kLogHeader |
+ (FLAGS_daemon ? 0 : chromeos::kLogToStderr));
+
+ if (FLAGS_daemon && daemon(0, 0) != 0) {
+ return errno;
+ }
+
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ MetricsDaemon daemon;
+ daemon.Init(FLAGS_uploader_test,
+ FLAGS_uploader | FLAGS_uploader_test,
+ &metrics_lib,
+ MetricsMainDiskStatsPath(),
+ "/proc/vmstat",
+ kScalingMaxFreqPath,
+ kCpuinfoMaxFreqPath,
+ base::TimeDelta::FromSeconds(FLAGS_upload_interval_secs),
+ FLAGS_server,
+ FLAGS_metrics_file,
+ FLAGS_config_root);
+
+ if (FLAGS_uploader_test) {
+ daemon.RunUploaderTest();
+ return 0;
+ }
+
+ daemon.Run();
+}
diff --git a/metrics/metrics_daemon_test.cc b/metrics/metrics_daemon_test.cc
new file mode 100644
index 0000000..7dafbd6
--- /dev/null
+++ b/metrics/metrics_daemon_test.cc
@@ -0,0 +1,390 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <inttypes.h>
+#include <utime.h>
+
+#include <string>
+#include <vector>
+
+#include <base/at_exit.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest.h>
+
+#include "metrics/metrics_daemon.h"
+#include "metrics/metrics_library_mock.h"
+#include "metrics/persistent_integer_mock.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using std::string;
+using std::vector;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Return;
+using ::testing::StrictMock;
+using chromeos_metrics::PersistentIntegerMock;
+
+static const char kFakeDiskStatsName[] = "fake-disk-stats";
+static const char kFakeDiskStatsFormat[] =
+ " 1793 1788 %" PRIu64 " 105580 "
+ " 196 175 %" PRIu64 " 30290 "
+ " 0 44060 135850\n";
+static const uint64_t kFakeReadSectors[] = {80000, 100000};
+static const uint64_t kFakeWriteSectors[] = {3000, 4000};
+
+static const char kFakeVmStatsName[] = "fake-vm-stats";
+static const char kFakeScalingMaxFreqPath[] = "fake-scaling-max-freq";
+static const char kFakeCpuinfoMaxFreqPath[] = "fake-cpuinfo-max-freq";
+static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
+static const char kMetricsFilePath[] = "/var/lib/metrics/uma-events";
+
+class MetricsDaemonTest : public testing::Test {
+ protected:
+ std::string kFakeDiskStats0;
+ std::string kFakeDiskStats1;
+
+ virtual void SetUp() {
+ kFakeDiskStats0 = base::StringPrintf(kFakeDiskStatsFormat,
+ kFakeReadSectors[0],
+ kFakeWriteSectors[0]);
+ kFakeDiskStats1 = base::StringPrintf(kFakeDiskStatsFormat,
+ kFakeReadSectors[1],
+ kFakeWriteSectors[1]);
+ CreateFakeDiskStatsFile(kFakeDiskStats0.c_str());
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 10000000);
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 10000000);
+
+ chromeos_metrics::PersistentInteger::SetTestingMode(true);
+ daemon_.Init(true,
+ false,
+ &metrics_lib_,
+ kFakeDiskStatsName,
+ kFakeVmStatsName,
+ kFakeScalingMaxFreqPath,
+ kFakeCpuinfoMaxFreqPath,
+ base::TimeDelta::FromMinutes(30),
+ kMetricsServer,
+ kMetricsFilePath,
+ "/");
+
+ // Replace original persistent values with mock ones.
+ daily_active_use_mock_ =
+ new StrictMock<PersistentIntegerMock>("1.mock");
+ daemon_.daily_active_use_.reset(daily_active_use_mock_);
+
+ kernel_crash_interval_mock_ =
+ new StrictMock<PersistentIntegerMock>("2.mock");
+ daemon_.kernel_crash_interval_.reset(kernel_crash_interval_mock_);
+
+ user_crash_interval_mock_ =
+ new StrictMock<PersistentIntegerMock>("3.mock");
+ daemon_.user_crash_interval_.reset(user_crash_interval_mock_);
+
+ unclean_shutdown_interval_mock_ =
+ new StrictMock<PersistentIntegerMock>("4.mock");
+ daemon_.unclean_shutdown_interval_.reset(unclean_shutdown_interval_mock_);
+ }
+
+ virtual void TearDown() {
+ EXPECT_EQ(0, unlink(kFakeDiskStatsName));
+ EXPECT_EQ(0, unlink(kFakeScalingMaxFreqPath));
+ EXPECT_EQ(0, unlink(kFakeCpuinfoMaxFreqPath));
+ }
+
+ // Adds active use aggregation counters update expectations that the
+ // specified count will be added.
+ void ExpectActiveUseUpdate(int count) {
+ EXPECT_CALL(*daily_active_use_mock_, Add(count))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*kernel_crash_interval_mock_, Add(count))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*user_crash_interval_mock_, Add(count))
+ .Times(1)
+ .RetiresOnSaturation();
+ }
+
+ // As above, but ignore values of counter updates.
+ void IgnoreActiveUseUpdate() {
+ EXPECT_CALL(*daily_active_use_mock_, Add(_))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*kernel_crash_interval_mock_, Add(_))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*user_crash_interval_mock_, Add(_))
+ .Times(1)
+ .RetiresOnSaturation();
+ }
+
+ // Adds a metrics library mock expectation that the specified metric
+ // will be generated.
+ void ExpectSample(const std::string& name, int sample) {
+ EXPECT_CALL(metrics_lib_, SendToUMA(name, sample, _, _, _))
+ .Times(1)
+ .WillOnce(Return(true))
+ .RetiresOnSaturation();
+ }
+
+ // Creates a new DBus signal message with zero or more string arguments.
+ // The message can be deallocated through DeleteDBusMessage.
+ //
+ // |path| is the object emitting the signal.
+ // |interface| is the interface the signal is emitted from.
+ // |name| is the name of the signal.
+ // |arg_values| contains the values of the string arguments.
+ DBusMessage* NewDBusSignalString(const string& path,
+ const string& interface,
+ const string& name,
+ const vector<string>& arg_values) {
+ DBusMessage* msg = dbus_message_new_signal(path.c_str(),
+ interface.c_str(),
+ name.c_str());
+ DBusMessageIter iter;
+ dbus_message_iter_init_append(msg, &iter);
+ for (vector<string>::const_iterator it = arg_values.begin();
+ it != arg_values.end(); ++it) {
+ const char* str_value = it->c_str();
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &str_value);
+ }
+ return msg;
+ }
+
+ // Deallocates the DBus message |msg| previously allocated through
+ // dbus_message_new*.
+ void DeleteDBusMessage(DBusMessage* msg) {
+ dbus_message_unref(msg);
+ }
+
+ // Creates or overwrites an input file containing fake disk stats.
+ void CreateFakeDiskStatsFile(const char* fake_stats) {
+ if (unlink(kFakeDiskStatsName) < 0) {
+ EXPECT_EQ(errno, ENOENT);
+ }
+ FILE* f = fopen(kFakeDiskStatsName, "w");
+ EXPECT_EQ(1, fwrite(fake_stats, strlen(fake_stats), 1, f));
+ EXPECT_EQ(0, fclose(f));
+ }
+
+ // Creates or overwrites the file in |path| so that it contains the printable
+ // representation of |value|.
+ void CreateUint64ValueFile(const base::FilePath& path, uint64_t value) {
+ base::DeleteFile(path, false);
+ std::string value_string = base::Uint64ToString(value);
+ ASSERT_EQ(value_string.length(),
+ base::WriteFile(path, value_string.c_str(),
+ value_string.length()));
+ }
+
+ // The MetricsDaemon under test.
+ MetricsDaemon daemon_;
+
+ // Mocks. They are strict mock so that all unexpected
+ // calls are marked as failures.
+ StrictMock<MetricsLibraryMock> metrics_lib_;
+ StrictMock<PersistentIntegerMock>* daily_active_use_mock_;
+ StrictMock<PersistentIntegerMock>* kernel_crash_interval_mock_;
+ StrictMock<PersistentIntegerMock>* user_crash_interval_mock_;
+ StrictMock<PersistentIntegerMock>* unclean_shutdown_interval_mock_;
+};
+
+TEST_F(MetricsDaemonTest, CheckSystemCrash) {
+ static const char kKernelCrashDetected[] = "test-kernel-crash-detected";
+ EXPECT_FALSE(daemon_.CheckSystemCrash(kKernelCrashDetected));
+
+ base::FilePath crash_detected(kKernelCrashDetected);
+ base::WriteFile(crash_detected, "", 0);
+ EXPECT_TRUE(base::PathExists(crash_detected));
+ EXPECT_TRUE(daemon_.CheckSystemCrash(kKernelCrashDetected));
+ EXPECT_FALSE(base::PathExists(crash_detected));
+ EXPECT_FALSE(daemon_.CheckSystemCrash(kKernelCrashDetected));
+ EXPECT_FALSE(base::PathExists(crash_detected));
+ base::DeleteFile(crash_detected, false);
+}
+
+TEST_F(MetricsDaemonTest, MessageFilter) {
+ // Ignore calls to SendToUMA.
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AnyNumber());
+
+ DBusMessage* msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+ DBusHandlerResult res =
+ MetricsDaemon::MessageFilter(/* connection */ nullptr, msg, &daemon_);
+ EXPECT_EQ(DBUS_HANDLER_RESULT_NOT_YET_HANDLED, res);
+ DeleteDBusMessage(msg);
+
+ IgnoreActiveUseUpdate();
+ vector<string> signal_args;
+ msg = NewDBusSignalString("/",
+ "org.chromium.CrashReporter",
+ "UserCrash",
+ signal_args);
+ res = MetricsDaemon::MessageFilter(/* connection */ nullptr, msg, &daemon_);
+ EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
+ DeleteDBusMessage(msg);
+
+ signal_args.clear();
+ signal_args.push_back("randomstate");
+ signal_args.push_back("bob"); // arbitrary username
+ msg = NewDBusSignalString("/",
+ "org.chromium.UnknownService.Manager",
+ "StateChanged",
+ signal_args);
+ res = MetricsDaemon::MessageFilter(/* connection */ nullptr, msg, &daemon_);
+ EXPECT_EQ(DBUS_HANDLER_RESULT_NOT_YET_HANDLED, res);
+ DeleteDBusMessage(msg);
+}
+
+TEST_F(MetricsDaemonTest, SendSample) {
+ ExpectSample("Dummy.Metric", 3);
+ daemon_.SendSample("Dummy.Metric", /* sample */ 3,
+ /* min */ 1, /* max */ 100, /* buckets */ 50);
+}
+
+TEST_F(MetricsDaemonTest, ReportDiskStats) {
+ uint64_t read_sectors_now, write_sectors_now;
+ CreateFakeDiskStatsFile(kFakeDiskStats1.c_str());
+ daemon_.DiskStatsReadStats(&read_sectors_now, &write_sectors_now);
+ EXPECT_EQ(read_sectors_now, kFakeReadSectors[1]);
+ EXPECT_EQ(write_sectors_now, kFakeWriteSectors[1]);
+
+ MetricsDaemon::StatsState s_state = daemon_.stats_state_;
+ EXPECT_CALL(metrics_lib_,
+ SendToUMA(_, (kFakeReadSectors[1] - kFakeReadSectors[0]) / 30,
+ _, _, _));
+ EXPECT_CALL(metrics_lib_,
+ SendToUMA(_, (kFakeWriteSectors[1] - kFakeWriteSectors[0]) / 30,
+ _, _, _));
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, _)); // SendCpuThrottleMetrics
+ daemon_.StatsCallback();
+ EXPECT_TRUE(s_state != daemon_.stats_state_);
+}
+
+TEST_F(MetricsDaemonTest, ProcessMeminfo) {
+ string meminfo =
+ "MemTotal: 2000000 kB\nMemFree: 500000 kB\n"
+ "Buffers: 1000000 kB\nCached: 213652 kB\n"
+ "SwapCached: 0 kB\nActive: 133400 kB\n"
+ "Inactive: 183396 kB\nActive(anon): 92984 kB\n"
+ "Inactive(anon): 58860 kB\nActive(file): 40416 kB\n"
+ "Inactive(file): 124536 kB\nUnevictable: 0 kB\n"
+ "Mlocked: 0 kB\nSwapTotal: 0 kB\n"
+ "SwapFree: 0 kB\nDirty: 40 kB\n"
+ "Writeback: 0 kB\nAnonPages: 92652 kB\n"
+ "Mapped: 59716 kB\nShmem: 59196 kB\n"
+ "Slab: 16656 kB\nSReclaimable: 6132 kB\n"
+ "SUnreclaim: 10524 kB\nKernelStack: 1648 kB\n"
+ "PageTables: 2780 kB\nNFS_Unstable: 0 kB\n"
+ "Bounce: 0 kB\nWritebackTmp: 0 kB\n"
+ "CommitLimit: 970656 kB\nCommitted_AS: 1260528 kB\n"
+ "VmallocTotal: 122880 kB\nVmallocUsed: 12144 kB\n"
+ "VmallocChunk: 103824 kB\nDirectMap4k: 9636 kB\n"
+ "DirectMap2M: 1955840 kB\n";
+
+ // All enum calls must report percents.
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100)).Times(AtLeast(1));
+ // Check that MemFree is correctly computed at 25%.
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMemFree", 25, 100))
+ .Times(AtLeast(1));
+ // Check that we call SendToUma at least once (log histogram).
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _))
+ .Times(AtLeast(1));
+ // Make sure we don't report fields not in the list.
+ EXPECT_CALL(metrics_lib_, SendToUMA("Platform.MeminfoMlocked", _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMlocked", _, _))
+ .Times(0);
+ EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsDaemonTest, ProcessMeminfo2) {
+ string meminfo = "MemTotal: 2000000 kB\nMemFree: 1000000 kB\n";
+ // Not enough fields.
+ EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsDaemonTest, ParseVmStats) {
+ static char kVmStats[] = "pswpin 1345\npswpout 8896\n"
+ "foo 100\nbar 200\npgmajfault 42\netcetc 300\n";
+ struct MetricsDaemon::VmstatRecord stats;
+ EXPECT_TRUE(daemon_.VmStatsParseStats(kVmStats, &stats));
+ EXPECT_EQ(stats.page_faults_, 42);
+ EXPECT_EQ(stats.swap_in_, 1345);
+ EXPECT_EQ(stats.swap_out_, 8896);
+}
+
+TEST_F(MetricsDaemonTest, ReadFreqToInt) {
+ const int fake_scaled_freq = 1666999;
+ const int fake_max_freq = 2000000;
+ int scaled_freq = 0;
+ int max_freq = 0;
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath),
+ fake_scaled_freq);
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), fake_max_freq);
+ EXPECT_TRUE(daemon_.testing_);
+ EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeScalingMaxFreqPath, &scaled_freq));
+ EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeCpuinfoMaxFreqPath, &max_freq));
+ EXPECT_EQ(fake_scaled_freq, scaled_freq);
+ EXPECT_EQ(fake_max_freq, max_freq);
+}
+
+TEST_F(MetricsDaemonTest, SendCpuThrottleMetrics) {
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 2001000);
+ // Test the 101% and 100% cases.
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2001000);
+ EXPECT_TRUE(daemon_.testing_);
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 101, 101));
+ daemon_.SendCpuThrottleMetrics();
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2000000);
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 100, 101));
+ daemon_.SendCpuThrottleMetrics();
+}
+
+TEST_F(MetricsDaemonTest, SendZramMetrics) {
+ EXPECT_TRUE(daemon_.testing_);
+
+ // |compr_data_size| is the size in bytes of compressed data.
+ const uint64_t compr_data_size = 50 * 1000 * 1000;
+ // The constant '3' is a realistic but random choice.
+ // |orig_data_size| does not include zero pages.
+ const uint64_t orig_data_size = compr_data_size * 3;
+ const uint64_t page_size = 4096;
+ const uint64_t zero_pages = 10 * 1000 * 1000 / page_size;
+
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::kComprDataSizeName),
+ compr_data_size);
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::kOrigDataSizeName),
+ orig_data_size);
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::kZeroPagesName),
+ zero_pages);
+
+ const uint64_t real_orig_size = orig_data_size + zero_pages * page_size;
+ const uint64_t zero_ratio_percent =
+ zero_pages * page_size * 100 / real_orig_size;
+ // Ratio samples are in percents.
+ const uint64_t actual_ratio_sample = real_orig_size * 100 / compr_data_size;
+
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, compr_data_size >> 20, _, _, _));
+ EXPECT_CALL(metrics_lib_,
+ SendToUMA(_, (real_orig_size - compr_data_size) >> 20, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, actual_ratio_sample, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_pages, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_ratio_percent, _, _, _));
+
+ EXPECT_TRUE(daemon_.ReportZram(base::FilePath(".")));
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/metrics/metrics_library.cc b/metrics/metrics_library.cc
new file mode 100644
index 0000000..70c8eac
--- /dev/null
+++ b/metrics/metrics_library.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/metrics_library.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <cstdio>
+#include <cstring>
+
+#include "metrics/serialization/metric_sample.h"
+#include "metrics/serialization/serialization_utils.h"
+
+#include "policy/device_policy.h"
+
+static const char kAutotestPath[] = "/var/log/metrics/autotest-events";
+static const char kUMAEventsPath[] = "/var/lib/metrics/uma-events";
+static const char kConsentFile[] = "/home/chronos/Consent To Send Stats";
+static const char kCrosEventHistogramName[] = "Platform.CrOSEvent";
+static const int kCrosEventHistogramMax = 100;
+
+/* Add new cros events here.
+ *
+ * The index of the event is sent in the message, so please do not
+ * reorder the names.
+ */
+static const char *kCrosEventNames[] = {
+ "ModemManagerCommandSendFailure", // 0
+ "HwWatchdogReboot", // 1
+ "Cras.NoCodecsFoundAtBoot", // 2
+ "Chaps.DatabaseCorrupted", // 3
+ "Chaps.DatabaseRepairFailure", // 4
+ "Chaps.DatabaseCreateFailure", // 5
+ "Attestation.OriginSpecificExhausted", // 6
+ "SpringPowerSupply.Original.High", // 7
+ "SpringPowerSupply.Other.High", // 8
+ "SpringPowerSupply.Original.Low", // 9
+ "SpringPowerSupply.ChargerIdle", // 10
+ "TPM.NonZeroDictionaryAttackCounter", // 11
+ "TPM.EarlyResetDuringCommand", // 12
+};
+
+time_t MetricsLibrary::cached_enabled_time_ = 0;
+bool MetricsLibrary::cached_enabled_ = false;
+
+MetricsLibrary::MetricsLibrary() : consent_file_(kConsentFile) {}
+MetricsLibrary::~MetricsLibrary() {}
+
+// We take buffer and buffer_size as parameters in order to simplify testing
+// of various alignments of the |device_name| with |buffer_size|.
+bool MetricsLibrary::IsDeviceMounted(const char* device_name,
+ const char* mounts_file,
+ char* buffer,
+ int buffer_size,
+ bool* result) {
+ if (buffer == nullptr || buffer_size < 1)
+ return false;
+ int mounts_fd = open(mounts_file, O_RDONLY);
+ if (mounts_fd < 0)
+ return false;
+ // match_offset describes:
+ // -1 -- not beginning of line
+ // 0..strlen(device_name)-1 -- this offset in device_name is next to match
+ // strlen(device_name) -- matched full name, just need a space.
+ int match_offset = 0;
+ bool match = false;
+ while (!match) {
+ int read_size = read(mounts_fd, buffer, buffer_size);
+ if (read_size <= 0) {
+ if (errno == -EINTR)
+ continue;
+ break;
+ }
+ for (int i = 0; i < read_size; ++i) {
+ if (buffer[i] == '\n') {
+ match_offset = 0;
+ continue;
+ }
+ if (match_offset < 0) {
+ continue;
+ }
+ if (device_name[match_offset] == '\0') {
+ if (buffer[i] == ' ') {
+ match = true;
+ break;
+ }
+ match_offset = -1;
+ continue;
+ }
+
+ if (buffer[i] == device_name[match_offset]) {
+ ++match_offset;
+ } else {
+ match_offset = -1;
+ }
+ }
+ }
+ close(mounts_fd);
+ *result = match;
+ return true;
+}
+
+bool MetricsLibrary::IsGuestMode() {
+ char buffer[256];
+ bool result = false;
+ if (!IsDeviceMounted("guestfs",
+ "/proc/mounts",
+ buffer,
+ sizeof(buffer),
+ &result)) {
+ return false;
+ }
+ return result && (access("/var/run/state/logged-in", F_OK) == 0);
+}
+
+bool MetricsLibrary::AreMetricsEnabled() {
+ static struct stat stat_buffer;
+ time_t this_check_time = time(nullptr);
+ if (this_check_time != cached_enabled_time_) {
+ cached_enabled_time_ = this_check_time;
+
+ if (!policy_provider_.get())
+ policy_provider_.reset(new policy::PolicyProvider());
+ policy_provider_->Reload();
+ // We initialize with the default value which is false and will be preserved
+ // if the policy is not set.
+ bool enabled = false;
+ bool has_policy = false;
+ if (policy_provider_->device_policy_is_loaded()) {
+ has_policy =
+ policy_provider_->GetDevicePolicy().GetMetricsEnabled(&enabled);
+ }
+ // If policy couldn't be loaded or the metrics policy is not set we should
+ // still respect the consent file if it is present for migration purposes.
+ // TODO(pastarmovj)
+ if (!has_policy) {
+ enabled = stat(consent_file_.c_str(), &stat_buffer) >= 0;
+ }
+
+ if (enabled && !IsGuestMode())
+ cached_enabled_ = true;
+ else
+ cached_enabled_ = false;
+ }
+ return cached_enabled_;
+}
+
+void MetricsLibrary::Init() {
+ uma_events_file_ = kUMAEventsPath;
+}
+
+bool MetricsLibrary::SendToAutotest(const std::string& name, int value) {
+ FILE* autotest_file = fopen(kAutotestPath, "a+");
+ if (autotest_file == nullptr) {
+ PLOG(ERROR) << kAutotestPath << ": fopen";
+ return false;
+ }
+
+ fprintf(autotest_file, "%s=%d\n", name.c_str(), value);
+ fclose(autotest_file);
+ return true;
+}
+
+bool MetricsLibrary::SendToUMA(const std::string& name,
+ int sample,
+ int min,
+ int max,
+ int nbuckets) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::HistogramSample(name, sample, min, max, nbuckets)
+ .get(),
+ kUMAEventsPath);
+}
+
+bool MetricsLibrary::SendEnumToUMA(const std::string& name, int sample,
+ int max) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::LinearHistogramSample(name, sample, max).get(),
+ kUMAEventsPath);
+}
+
+bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::SparseHistogramSample(name, sample).get(),
+ kUMAEventsPath);
+}
+
+bool MetricsLibrary::SendUserActionToUMA(const std::string& action) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::UserActionSample(action).get(), kUMAEventsPath);
+}
+
+bool MetricsLibrary::SendCrashToUMA(const char *crash_kind) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::CrashSample(crash_kind).get(), kUMAEventsPath);
+}
+
+void MetricsLibrary::SetPolicyProvider(policy::PolicyProvider* provider) {
+ policy_provider_.reset(provider);
+}
+
+bool MetricsLibrary::SendCrosEventToUMA(const std::string& event) {
+ for (size_t i = 0; i < arraysize(kCrosEventNames); i++) {
+ if (strcmp(event.c_str(), kCrosEventNames[i]) == 0) {
+ return SendEnumToUMA(kCrosEventHistogramName, i, kCrosEventHistogramMax);
+ }
+ }
+ return false;
+}
diff --git a/metrics/metrics_library.h b/metrics/metrics_library.h
new file mode 100644
index 0000000..a90f3e6
--- /dev/null
+++ b/metrics/metrics_library.h
@@ -0,0 +1,150 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_METRICS_LIBRARY_H_
+#define METRICS_METRICS_LIBRARY_H_
+
+#include <sys/types.h>
+#include <string>
+#include <unistd.h>
+
+#include <base/compiler_specific.h>
+#include <base/macros.h>
+#include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "policy/libpolicy.h"
+
+class MetricsLibraryInterface {
+ public:
+ virtual void Init() = 0;
+ virtual bool AreMetricsEnabled() = 0;
+ virtual bool SendToUMA(const std::string& name, int sample,
+ int min, int max, int nbuckets) = 0;
+ virtual bool SendEnumToUMA(const std::string& name, int sample, int max) = 0;
+ virtual bool SendSparseToUMA(const std::string& name, int sample) = 0;
+ virtual bool SendUserActionToUMA(const std::string& action) = 0;
+ virtual ~MetricsLibraryInterface() {}
+};
+
+// Library used to send metrics to both Autotest and Chrome/UMA.
+class MetricsLibrary : public MetricsLibraryInterface {
+ public:
+ MetricsLibrary();
+ virtual ~MetricsLibrary();
+
+ // Initializes the library.
+ void Init() override;
+
+ // Returns whether or not the machine is running in guest mode.
+ bool IsGuestMode();
+
+ // Returns whether or not metrics collection is enabled.
+ bool AreMetricsEnabled() override;
+
+ // Sends histogram data to Chrome for transport to UMA and returns
+ // true on success. This method results in the equivalent of an
+ // asynchronous non-blocking RPC to UMA_HISTOGRAM_CUSTOM_COUNTS
+ // inside Chrome (see base/histogram.h).
+ //
+ // |sample| is the sample value to be recorded (|min| <= |sample| < |max|).
+ // |min| is the minimum value of the histogram samples (|min| > 0).
+ // |max| is the maximum value of the histogram samples.
+ // |nbuckets| is the number of histogram buckets.
+ // [0,min) is the implicit underflow bucket.
+ // [|max|,infinity) is the implicit overflow bucket.
+ //
+ // Note that the memory allocated in Chrome for each histogram is
+ // proportional to the number of buckets. Therefore, it is strongly
+ // recommended to keep this number low (e.g., 50 is normal, while
+ // 100 is high).
+ bool SendToUMA(const std::string& name, int sample,
+ int min, int max, int nbuckets) override;
+
+ // Sends linear histogram data to Chrome for transport to UMA and
+ // returns true on success. This method results in the equivalent of
+ // an asynchronous non-blocking RPC to UMA_HISTOGRAM_ENUMERATION
+ // inside Chrome (see base/histogram.h).
+ //
+ // |sample| is the sample value to be recorded (1 <= |sample| < |max|).
+ // |max| is the maximum value of the histogram samples.
+ // 0 is the implicit underflow bucket.
+ // [|max|,infinity) is the implicit overflow bucket.
+ //
+ // An enumeration histogram requires |max| + 1 number of
+ // buckets. Note that the memory allocated in Chrome for each
+ // histogram is proportional to the number of buckets. Therefore, it
+ // is strongly recommended to keep this number low (e.g., 50 is
+ // normal, while 100 is high).
+ bool SendEnumToUMA(const std::string& name, int sample, int max) override;
+
+ // Sends sparse histogram sample to Chrome for transport to UMA. Returns
+ // true on success.
+ //
+ // |sample| is the 32-bit integer value to be recorded.
+ bool SendSparseToUMA(const std::string& name, int sample) override;
+
+ // Sends a user action to Chrome for transport to UMA and returns true on
+ // success. This method results in the equivalent of an asynchronous
+ // non-blocking RPC to UserMetrics::RecordAction. The new metric must be
+ // added to chrome/tools/extract_actions.py in the Chromium repository, which
+ // should then be run to generate a hash for the new action.
+ //
+ // Until http://crosbug.com/11125 is fixed, the metric must also be added to
+ // chrome/browser/chromeos/external_metrics.cc.
+ //
+ // |action| is the user-generated event (e.g., "MuteKeyPressed").
+ bool SendUserActionToUMA(const std::string& action) override;
+
+ // Sends a signal to UMA that a crash of the given |crash_kind|
+ // has occurred. Used by UMA to generate stability statistics.
+ bool SendCrashToUMA(const char *crash_kind);
+
+ // Sends a "generic Chrome OS event" to UMA. This is an event name
+ // that is translated into an enumerated histogram entry. Event names
+ // are added to metrics_library.cc. Optionally, they can be added
+ // to histograms.xml---but part of the reason for this is to simplify
+ // the addition of events (at the cost of having to look them up by
+ // number in the histograms dashboard).
+ bool SendCrosEventToUMA(const std::string& event);
+
+ // Sends to Autotest and returns true on success.
+ static bool SendToAutotest(const std::string& name, int value);
+
+ private:
+ friend class CMetricsLibraryTest;
+ friend class MetricsLibraryTest;
+ FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabled);
+ FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage);
+ FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong);
+ FRIEND_TEST(MetricsLibraryTest, IsDeviceMounted);
+ FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome);
+ FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation);
+
+ // Sets |*result| to whether or not the |mounts_file| indicates that
+ // the |device_name| is currently mounted. Uses |buffer| of
+ // |buffer_size| to read the file. Returns false if any error.
+ bool IsDeviceMounted(const char* device_name,
+ const char* mounts_file,
+ char* buffer, int buffer_size,
+ bool* result);
+
+ // This function is used by tests only to mock the device policies.
+ void SetPolicyProvider(policy::PolicyProvider* provider);
+
+ // Time at which we last checked if metrics were enabled.
+ static time_t cached_enabled_time_;
+
+ // Cached state of whether or not metrics were enabled.
+ static bool cached_enabled_;
+
+ std::string uma_events_file_;
+ std::string consent_file_;
+
+ scoped_ptr<policy::PolicyProvider> policy_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLibrary);
+};
+
+#endif // METRICS_METRICS_LIBRARY_H_
diff --git a/metrics/metrics_library_mock.h b/metrics/metrics_library_mock.h
new file mode 100644
index 0000000..99892bf
--- /dev/null
+++ b/metrics/metrics_library_mock.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_METRICS_LIBRARY_MOCK_H_
+#define METRICS_METRICS_LIBRARY_MOCK_H_
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+#include <gmock/gmock.h>
+
+class MetricsLibraryMock : public MetricsLibraryInterface {
+ public:
+ bool metrics_enabled_ = true;
+
+ MOCK_METHOD0(Init, void());
+ MOCK_METHOD5(SendToUMA, bool(const std::string& name, int sample,
+ int min, int max, int nbuckets));
+ MOCK_METHOD3(SendEnumToUMA, bool(const std::string& name, int sample,
+ int max));
+ MOCK_METHOD2(SendSparseToUMA, bool(const std::string& name, int sample));
+ MOCK_METHOD1(SendUserActionToUMA, bool(const std::string& action));
+
+ bool AreMetricsEnabled() override {return metrics_enabled_;};
+};
+
+#endif // METRICS_METRICS_LIBRARY_MOCK_H_
diff --git a/metrics/metrics_library_test.cc b/metrics/metrics_library_test.cc
new file mode 100644
index 0000000..7ede303
--- /dev/null
+++ b/metrics/metrics_library_test.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstring>
+
+#include <base/files/file_util.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/mock_device_policy.h>
+#include <policy/libpolicy.h>
+
+#include "metrics/c_metrics_library.h"
+#include "metrics/metrics_library.h"
+
+using base::FilePath;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::AnyNumber;
+
+static const FilePath kTestUMAEventsFile("test-uma-events");
+static const char kTestMounts[] = "test-mounts";
+
+ACTION_P(SetMetricsPolicy, enabled) {
+ *arg0 = enabled;
+ return true;
+}
+
+class MetricsLibraryTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ EXPECT_TRUE(lib_.uma_events_file_.empty());
+ lib_.Init();
+ EXPECT_FALSE(lib_.uma_events_file_.empty());
+ lib_.uma_events_file_ = kTestUMAEventsFile.value();
+ EXPECT_EQ(0, WriteFile(kTestUMAEventsFile, "", 0));
+ device_policy_ = new policy::MockDevicePolicy();
+ EXPECT_CALL(*device_policy_, LoadPolicy())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(SetMetricsPolicy(true));
+ provider_ = new policy::PolicyProvider(device_policy_);
+ lib_.SetPolicyProvider(provider_);
+ // Defeat metrics enabled caching between tests.
+ lib_.cached_enabled_time_ = 0;
+ }
+
+ virtual void TearDown() {
+ base::DeleteFile(FilePath(kTestMounts), false);
+ base::DeleteFile(kTestUMAEventsFile, false);
+ }
+
+ void VerifyEnabledCacheHit(bool to_value);
+ void VerifyEnabledCacheEviction(bool to_value);
+
+ MetricsLibrary lib_;
+ policy::MockDevicePolicy* device_policy_;
+ policy::PolicyProvider* provider_;
+};
+
+TEST_F(MetricsLibraryTest, IsDeviceMounted) {
+ static const char kTestContents[] =
+ "0123456789abcde 0123456789abcde\nguestfs foo bar\n";
+ char buffer[1024];
+ int block_sizes[] = { 1, 2, 3, 4, 5, 6, 8, 12, 14, 16, 32, 1024 };
+ bool result;
+ EXPECT_FALSE(lib_.IsDeviceMounted("guestfs",
+ "nonexistent",
+ buffer,
+ 1,
+ &result));
+ ASSERT_TRUE(base::WriteFile(base::FilePath(kTestMounts),
+ kTestContents,
+ strlen(kTestContents)));
+ EXPECT_FALSE(lib_.IsDeviceMounted("guestfs",
+ kTestMounts,
+ buffer,
+ 0,
+ &result));
+ for (size_t i = 0; i < arraysize(block_sizes); ++i) {
+ EXPECT_TRUE(lib_.IsDeviceMounted("0123456789abcde",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("guestfs",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("0123456",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("9abcde",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("foo",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("bar",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ }
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledFalse) {
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(false));
+ EXPECT_FALSE(lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledTrue) {
+ EXPECT_TRUE(lib_.AreMetricsEnabled());
+}
+
+void MetricsLibraryTest::VerifyEnabledCacheHit(bool to_value) {
+ // We might step from one second to the next one time, but not 100
+ // times in a row.
+ for (int i = 0; i < 100; ++i) {
+ lib_.cached_enabled_time_ = 0;
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(!to_value));
+ ASSERT_EQ(!to_value, lib_.AreMetricsEnabled());
+ ON_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillByDefault(SetMetricsPolicy(to_value));
+ if (lib_.AreMetricsEnabled() == !to_value)
+ return;
+ }
+ ADD_FAILURE() << "Did not see evidence of caching";
+}
+
+void MetricsLibraryTest::VerifyEnabledCacheEviction(bool to_value) {
+ lib_.cached_enabled_time_ = 0;
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(!to_value));
+ ASSERT_EQ(!to_value, lib_.AreMetricsEnabled());
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(to_value));
+ ASSERT_LT(abs(static_cast<int>(time(nullptr) - lib_.cached_enabled_time_)),
+ 5);
+ // Sleep one second (or cheat to be faster).
+ --lib_.cached_enabled_time_;
+ ASSERT_EQ(to_value, lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledCaching) {
+ VerifyEnabledCacheHit(false);
+ VerifyEnabledCacheHit(true);
+ VerifyEnabledCacheEviction(false);
+ VerifyEnabledCacheEviction(true);
+}
+
+class CMetricsLibraryTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ lib_ = CMetricsLibraryNew();
+ MetricsLibrary& ml = *reinterpret_cast<MetricsLibrary*>(lib_);
+ EXPECT_TRUE(ml.uma_events_file_.empty());
+ CMetricsLibraryInit(lib_);
+ EXPECT_FALSE(ml.uma_events_file_.empty());
+ ml.uma_events_file_ = kTestUMAEventsFile.value();
+ EXPECT_EQ(0, WriteFile(kTestUMAEventsFile, "", 0));
+ device_policy_ = new policy::MockDevicePolicy();
+ EXPECT_CALL(*device_policy_, LoadPolicy())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(SetMetricsPolicy(true));
+ provider_ = new policy::PolicyProvider(device_policy_);
+ ml.SetPolicyProvider(provider_);
+ reinterpret_cast<MetricsLibrary*>(lib_)->cached_enabled_time_ = 0;
+ }
+
+ virtual void TearDown() {
+ CMetricsLibraryDelete(lib_);
+ base::DeleteFile(kTestUMAEventsFile, false);
+ }
+
+ CMetricsLibrary lib_;
+ policy::MockDevicePolicy* device_policy_;
+ policy::PolicyProvider* provider_;
+};
+
+TEST_F(CMetricsLibraryTest, AreMetricsEnabledFalse) {
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(false));
+ EXPECT_FALSE(CMetricsLibraryAreMetricsEnabled(lib_));
+}
+
+TEST_F(CMetricsLibraryTest, AreMetricsEnabledTrue) {
+ EXPECT_TRUE(CMetricsLibraryAreMetricsEnabled(lib_));
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/metrics/persistent_integer.cc b/metrics/persistent_integer.cc
new file mode 100644
index 0000000..dd38f1e
--- /dev/null
+++ b/metrics/persistent_integer.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/persistent_integer.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+
+#include "metrics/metrics_library.h"
+
+namespace {
+
+// The directory for the persistent storage.
+const char kBackingFilesDirectory[] = "/var/lib/metrics/";
+
+}
+
+namespace chromeos_metrics {
+
+// Static class member instantiation.
+bool PersistentInteger::testing_ = false;
+
+PersistentInteger::PersistentInteger(const std::string& name) :
+ value_(0),
+ version_(kVersion),
+ name_(name),
+ synced_(false) {
+ if (testing_) {
+ backing_file_name_ = name_;
+ } else {
+ backing_file_name_ = kBackingFilesDirectory + name_;
+ }
+}
+
+PersistentInteger::~PersistentInteger() {}
+
+void PersistentInteger::Set(int64_t value) {
+ value_ = value;
+ Write();
+}
+
+int64_t PersistentInteger::Get() {
+ // If not synced, then read. If the read fails, it's a good idea to write.
+ if (!synced_ && !Read())
+ Write();
+ return value_;
+}
+
+int64_t PersistentInteger::GetAndClear() {
+ int64_t v = Get();
+ Set(0);
+ return v;
+}
+
+void PersistentInteger::Add(int64_t x) {
+ Set(Get() + x);
+}
+
+void PersistentInteger::Write() {
+ int fd = HANDLE_EINTR(open(backing_file_name_.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC,
+ S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH));
+ PCHECK(fd >= 0) << "cannot open " << backing_file_name_ << " for writing";
+ PCHECK((HANDLE_EINTR(write(fd, &version_, sizeof(version_))) ==
+ sizeof(version_)) &&
+ (HANDLE_EINTR(write(fd, &value_, sizeof(value_))) ==
+ sizeof(value_)))
+ << "cannot write to " << backing_file_name_;
+ close(fd);
+ synced_ = true;
+}
+
+bool PersistentInteger::Read() {
+ int fd = HANDLE_EINTR(open(backing_file_name_.c_str(), O_RDONLY));
+ if (fd < 0) {
+ PLOG(WARNING) << "cannot open " << backing_file_name_ << " for reading";
+ return false;
+ }
+ int32_t version;
+ int64_t value;
+ bool read_succeeded = false;
+ if (HANDLE_EINTR(read(fd, &version, sizeof(version))) == sizeof(version) &&
+ version == version_ &&
+ HANDLE_EINTR(read(fd, &value, sizeof(value))) == sizeof(value)) {
+ value_ = value;
+ read_succeeded = true;
+ synced_ = true;
+ }
+ close(fd);
+ return read_succeeded;
+}
+
+void PersistentInteger::SetTestingMode(bool testing) {
+ testing_ = testing;
+}
+
+
+} // namespace chromeos_metrics
diff --git a/metrics/persistent_integer.h b/metrics/persistent_integer.h
new file mode 100644
index 0000000..b1cfcf4
--- /dev/null
+++ b/metrics/persistent_integer.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_PERSISTENT_INTEGER_H_
+#define METRICS_PERSISTENT_INTEGER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace chromeos_metrics {
+
+// PersistentIntegers is a named 64-bit integer value backed by a file.
+// The in-memory value acts as a write-through cache of the file value.
+// If the backing file doesn't exist or has bad content, the value is 0.
+
+class PersistentInteger {
+ public:
+ explicit PersistentInteger(const std::string& name);
+
+ // Virtual only because of mock.
+ virtual ~PersistentInteger();
+
+ // Sets the value. This writes through to the backing file.
+ void Set(int64_t v);
+
+ // Gets the value. May sync from backing file first.
+ int64_t Get();
+
+ // Returns the name of the object.
+ std::string Name() { return name_; }
+
+ // Convenience function for Get() followed by Set(0).
+ int64_t GetAndClear();
+
+ // Convenience function for v = Get, Set(v + x).
+ // Virtual only because of mock.
+ virtual void Add(int64_t x);
+
+ // After calling with |testing| = true, changes some behavior for the purpose
+ // of testing. For instance: instances created while testing use the current
+ // directory for the backing files.
+ static void SetTestingMode(bool testing);
+
+ private:
+ static const int kVersion = 1001;
+
+ // Writes |value_| to the backing file, creating it if necessary.
+ void Write();
+
+ // Reads the value from the backing file, stores it in |value_|, and returns
+ // true if the backing file is valid. Returns false otherwise, and creates
+ // a valid backing file as a side effect.
+ bool Read();
+
+ int64_t value_;
+ int32_t version_;
+ std::string name_;
+ std::string backing_file_name_;
+ bool synced_;
+ static bool testing_;
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_PERSISTENT_INTEGER_H_
diff --git a/metrics/persistent_integer_mock.h b/metrics/persistent_integer_mock.h
new file mode 100644
index 0000000..2061e55
--- /dev/null
+++ b/metrics/persistent_integer_mock.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_PERSISTENT_INTEGER_MOCK_H_
+#define METRICS_PERSISTENT_INTEGER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "metrics/persistent_integer.h"
+
+namespace chromeos_metrics {
+
+class PersistentIntegerMock : public PersistentInteger {
+ public:
+ explicit PersistentIntegerMock(const std::string& name)
+ : PersistentInteger(name) {}
+ MOCK_METHOD1(Add, void(int64_t count));
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_PERSISTENT_INTEGER_MOCK_H_
diff --git a/metrics/persistent_integer_test.cc b/metrics/persistent_integer_test.cc
new file mode 100644
index 0000000..a56aede
--- /dev/null
+++ b/metrics/persistent_integer_test.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include <base/compiler_specific.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+
+#include "metrics/persistent_integer.h"
+
+const char kBackingFileName[] = "1.pibakf";
+const char kBackingFilePattern[] = "*.pibakf";
+
+using chromeos_metrics::PersistentInteger;
+
+class PersistentIntegerTest : public testing::Test {
+ void SetUp() override {
+ // Set testing mode.
+ chromeos_metrics::PersistentInteger::SetTestingMode(true);
+ }
+
+ void TearDown() override {
+ // Remove backing files. The convention is that they all end in ".pibakf".
+ base::FileEnumerator f_enum(base::FilePath("."),
+ false,
+ base::FileEnumerator::FILES,
+ FILE_PATH_LITERAL(kBackingFilePattern));
+ for (base::FilePath name = f_enum.Next();
+ !name.empty();
+ name = f_enum.Next()) {
+ base::DeleteFile(name, false);
+ }
+ }
+};
+
+TEST_F(PersistentIntegerTest, BasicChecks) {
+ scoped_ptr<PersistentInteger> pi(new PersistentInteger(kBackingFileName));
+
+ // Test initialization.
+ EXPECT_EQ(0, pi->Get());
+ EXPECT_EQ(kBackingFileName, pi->Name()); // boring
+
+ // Test set and add.
+ pi->Set(2);
+ pi->Add(3);
+ EXPECT_EQ(5, pi->Get());
+
+ // Test persistence.
+ pi.reset(new PersistentInteger(kBackingFileName));
+ EXPECT_EQ(5, pi->Get());
+
+ // Test GetAndClear.
+ EXPECT_EQ(5, pi->GetAndClear());
+ EXPECT_EQ(pi->Get(), 0);
+
+ // Another persistence test.
+ pi.reset(new PersistentInteger(kBackingFileName));
+ EXPECT_EQ(0, pi->Get());
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/metrics/platform2_preinstall.sh b/metrics/platform2_preinstall.sh
new file mode 100755
index 0000000..ccf353f
--- /dev/null
+++ b/metrics/platform2_preinstall.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+
+OUT=$1
+shift
+for v; do
+ sed -e "s/@BSLOT@/${v}/g" libmetrics.pc.in > "${OUT}/lib/libmetrics-${v}.pc"
+done
diff --git a/metrics/serialization/metric_sample.cc b/metrics/serialization/metric_sample.cc
new file mode 100644
index 0000000..5447497
--- /dev/null
+++ b/metrics/serialization/metric_sample.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/serialization/metric_sample.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+
+namespace metrics {
+
+MetricSample::MetricSample(MetricSample::SampleType sample_type,
+ const std::string& metric_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count)
+ : type_(sample_type),
+ name_(metric_name),
+ sample_(sample),
+ min_(min),
+ max_(max),
+ bucket_count_(bucket_count) {
+}
+
+MetricSample::~MetricSample() {
+}
+
+bool MetricSample::IsValid() const {
+ return name().find(' ') == std::string::npos &&
+ name().find('\0') == std::string::npos && !name().empty();
+}
+
+std::string MetricSample::ToString() const {
+ if (type_ == CRASH) {
+ return base::StringPrintf("crash%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ } else if (type_ == SPARSE_HISTOGRAM) {
+ return base::StringPrintf("sparsehistogram%c%s %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ '\0');
+ } else if (type_ == LINEAR_HISTOGRAM) {
+ return base::StringPrintf("linearhistogram%c%s %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ max_,
+ '\0');
+ } else if (type_ == HISTOGRAM) {
+ return base::StringPrintf("histogram%c%s %d %d %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ min_,
+ max_,
+ bucket_count_,
+ '\0');
+ } else {
+ // The type can only be USER_ACTION.
+ CHECK_EQ(type_, USER_ACTION);
+ return base::StringPrintf("useraction%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ }
+}
+
+int MetricSample::sample() const {
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, CRASH);
+ return sample_;
+}
+
+int MetricSample::min() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return min_;
+}
+
+int MetricSample::max() const {
+ CHECK_NE(type_, CRASH);
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, SPARSE_HISTOGRAM);
+ return max_;
+}
+
+int MetricSample::bucket_count() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return bucket_count_;
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::CrashSample(
+ const std::string& crash_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(CRASH, crash_name, 0, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count) {
+ return scoped_ptr<MetricSample>(new MetricSample(
+ HISTOGRAM, histogram_name, sample, min, max, bucket_count));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ base::SplitString(serialized_histogram, ' ', &parts);
+
+ if (parts.size() != 5)
+ return scoped_ptr<MetricSample>();
+ int sample, min, max, bucket_count;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &min) ||
+ !base::StringToInt(parts[3], &max) ||
+ !base::StringToInt(parts[4], &bucket_count)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return HistogramSample(parts[0], sample, min, max, bucket_count);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseSparseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ base::SplitString(serialized_histogram, ' ', &parts);
+ if (parts.size() != 2)
+ return scoped_ptr<MetricSample>();
+ int sample;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample))
+ return scoped_ptr<MetricSample>();
+
+ return SparseHistogramSample(parts[0], sample);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseLinearHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ int sample, max;
+ base::SplitString(serialized_histogram, ' ', &parts);
+ if (parts.size() != 3)
+ return scoped_ptr<MetricSample>();
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &max)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return LinearHistogramSample(parts[0], sample, max);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::UserActionSample(
+ const std::string& action_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(USER_ACTION, action_name, 0, 0, 0, 0));
+}
+
+bool MetricSample::IsEqual(const MetricSample& metric) {
+ return type_ == metric.type_ && name_ == metric.name_ &&
+ sample_ == metric.sample_ && min_ == metric.min_ &&
+ max_ == metric.max_ && bucket_count_ == metric.bucket_count_;
+}
+
+} // namespace metrics
diff --git a/metrics/serialization/metric_sample.h b/metrics/serialization/metric_sample.h
new file mode 100644
index 0000000..877114d
--- /dev/null
+++ b/metrics/serialization/metric_sample.h
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+#define METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace metrics {
+
+// This class is used by libmetrics (ChromeOS) to serialize
+// and deserialize measurements to send them to a metrics sending service.
+// It is meant to be a simple container with serialization functions.
+class MetricSample {
+ public:
+ // Types of metric sample used.
+ enum SampleType {
+ CRASH,
+ HISTOGRAM,
+ LINEAR_HISTOGRAM,
+ SPARSE_HISTOGRAM,
+ USER_ACTION
+ };
+
+ ~MetricSample();
+
+ // Returns true if the sample is valid (can be serialized without ambiguity).
+ //
+ // This function should be used to filter bad samples before serializing them.
+ bool IsValid() const;
+
+ // Getters for type and name. All types of metrics have these so we do not
+ // need to check the type.
+ SampleType type() const { return type_; }
+ const std::string& name() const { return name_; }
+
+ // Getters for sample, min, max, bucket_count.
+ // Check the metric type to make sure the request make sense. (ex: a crash
+ // sample does not have a bucket_count so we crash if we call bucket_count()
+ // on it.)
+ int sample() const;
+ int min() const;
+ int max() const;
+ int bucket_count() const;
+
+ // Returns a serialized version of the sample.
+ //
+ // The serialized message for each type is:
+ // crash: crash\0|name_|\0
+ // user action: useraction\0|name_|\0
+ // histogram: histogram\0|name_| |sample_| |min_| |max_| |bucket_count_|\0
+ // sparsehistogram: sparsehistogram\0|name_| |sample_|\0
+ // linearhistogram: linearhistogram\0|name_| |sample_| |max_|\0
+ std::string ToString() const;
+
+ // Builds a crash sample.
+ static scoped_ptr<MetricSample> CrashSample(const std::string& crash_name);
+
+ // Builds a histogram sample.
+ static scoped_ptr<MetricSample> HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count);
+ // Deserializes a histogram sample.
+ static scoped_ptr<MetricSample> ParseHistogram(const std::string& serialized);
+
+ // Builds a sparse histogram sample.
+ static scoped_ptr<MetricSample> SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample);
+ // Deserializes a sparse histogram sample.
+ static scoped_ptr<MetricSample> ParseSparseHistogram(
+ const std::string& serialized);
+
+ // Builds a linear histogram sample.
+ static scoped_ptr<MetricSample> LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max);
+ // Deserializes a linear histogram sample.
+ static scoped_ptr<MetricSample> ParseLinearHistogram(
+ const std::string& serialized);
+
+ // Builds a user action sample.
+ static scoped_ptr<MetricSample> UserActionSample(
+ const std::string& action_name);
+
+ // Returns true if sample and this object represent the same sample (type,
+ // name, sample, min, max, bucket_count match).
+ bool IsEqual(const MetricSample& sample);
+
+ private:
+ MetricSample(SampleType sample_type,
+ const std::string& metric_name,
+ const int sample,
+ const int min,
+ const int max,
+ const int bucket_count);
+
+ const SampleType type_;
+ const std::string name_;
+ const int sample_;
+ const int min_;
+ const int max_;
+ const int bucket_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricSample);
+};
+
+} // namespace metrics
+
+#endif // METRICS_SERIALIZATION_METRIC_SAMPLE_H_
diff --git a/metrics/serialization/serialization_utils.cc b/metrics/serialization/serialization_utils.cc
new file mode 100644
index 0000000..9aa076a
--- /dev/null
+++ b/metrics/serialization/serialization_utils.cc
@@ -0,0 +1,221 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/serialization/serialization_utils.h"
+
+#include <sys/file.h>
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "metrics/serialization/metric_sample.h"
+
+#define READ_WRITE_ALL_FILE_FLAGS \
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+namespace metrics {
+namespace {
+
+// Reads the next message from |file_descriptor| into |message|.
+//
+// |message| will be set to the empty string if no message could be read (EOF)
+// or the message was badly constructed.
+//
+// Returns false if no message can be read from this file anymore (EOF or
+// unrecoverable error).
+bool ReadMessage(int fd, std::string* message) {
+ CHECK(message);
+
+ int result;
+ int32_t message_size;
+ const int32_t message_hdr_size = sizeof(message_size);
+ // The file containing the metrics do not leave the device so the writer and
+ // the reader will always have the same endianness.
+ result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
+ if (result < 0) {
+ DPLOG(ERROR) << "reading metrics message header";
+ return false;
+ }
+ if (result == 0) {
+ // This indicates a normal EOF.
+ return false;
+ }
+ if (result < message_hdr_size) {
+ DLOG(ERROR) << "bad read size " << result << ", expecting "
+ << sizeof(message_size);
+ return false;
+ }
+
+ // kMessageMaxLength applies to the entire message: the 4-byte
+ // length field and the content.
+ if (message_size > SerializationUtils::kMessageMaxLength) {
+ DLOG(ERROR) << "message too long : " << message_size;
+ if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) {
+ DLOG(ERROR) << "error while skipping message. abort";
+ return false;
+ }
+ // Badly formatted message was skipped. Treat the badly formatted sample as
+ // an empty sample.
+ message->clear();
+ return true;
+ }
+
+ if (message_size < message_hdr_size) {
+ DLOG(ERROR) << "message too short : " << message_size;
+ return false;
+ }
+
+ message_size -= message_hdr_size; // The message size includes itself.
+ char buffer[SerializationUtils::kMessageMaxLength];
+ if (!base::ReadFromFD(fd, buffer, message_size)) {
+ DPLOG(ERROR) << "reading metrics message body";
+ return false;
+ }
+ *message = std::string(buffer, message_size);
+ return true;
+}
+
+} // namespace
+
+scoped_ptr<MetricSample> SerializationUtils::ParseSample(
+ const std::string& sample) {
+ if (sample.empty())
+ return scoped_ptr<MetricSample>();
+
+ std::vector<std::string> parts;
+ base::SplitString(sample, '\0', &parts);
+ // We should have two null terminated strings so split should produce
+ // three chunks.
+ if (parts.size() != 3) {
+ DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
+ << " parts (expected 3)";
+ return scoped_ptr<MetricSample>();
+ }
+ const std::string& name = parts[0];
+ const std::string& value = parts[1];
+
+ if (base::LowerCaseEqualsASCII(name, "crash")) {
+ return MetricSample::CrashSample(value);
+ } else if (base::LowerCaseEqualsASCII(name, "histogram")) {
+ return MetricSample::ParseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "linearhistogram")) {
+ return MetricSample::ParseLinearHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "sparsehistogram")) {
+ return MetricSample::ParseSparseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "useraction")) {
+ return MetricSample::UserActionSample(value);
+ } else {
+ DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
+ }
+ return scoped_ptr<MetricSample>();
+}
+
+void SerializationUtils::ReadAndTruncateMetricsFromFile(
+ const std::string& filename,
+ ScopedVector<MetricSample>* metrics) {
+ struct stat stat_buf;
+ int result;
+
+ result = stat(filename.c_str(), &stat_buf);
+ if (result < 0) {
+ if (errno != ENOENT)
+ DPLOG(ERROR) << filename << ": bad metrics file stat";
+
+ // Nothing to collect---try later.
+ return;
+ }
+ if (stat_buf.st_size == 0) {
+ // Also nothing to collect.
+ return;
+ }
+ base::ScopedFD fd(open(filename.c_str(), O_RDWR));
+ if (fd.get() < 0) {
+ DPLOG(ERROR) << filename << ": cannot open";
+ return;
+ }
+ result = flock(fd.get(), LOCK_EX);
+ if (result < 0) {
+ DPLOG(ERROR) << filename << ": cannot lock";
+ return;
+ }
+
+ // This processes all messages in the log. When all messages are
+ // read and processed, or an error occurs, truncate the file to zero size.
+ for (;;) {
+ std::string message;
+
+ if (!ReadMessage(fd.get(), &message))
+ break;
+
+ scoped_ptr<MetricSample> sample = ParseSample(message);
+ if (sample)
+ metrics->push_back(sample.release());
+ }
+
+ result = ftruncate(fd.get(), 0);
+ if (result < 0)
+ DPLOG(ERROR) << "truncate metrics log";
+
+ result = flock(fd.get(), LOCK_UN);
+ if (result < 0)
+ DPLOG(ERROR) << "unlock metrics log";
+}
+
+bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
+ const std::string& filename) {
+ if (!sample.IsValid())
+ return false;
+
+ base::ScopedFD file_descriptor(open(filename.c_str(),
+ O_WRONLY | O_APPEND | O_CREAT,
+ READ_WRITE_ALL_FILE_FLAGS));
+
+ if (file_descriptor.get() < 0) {
+ DPLOG(ERROR) << filename << ": cannot open";
+ return false;
+ }
+
+ fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
+ // Grab a lock to avoid chrome truncating the file
+ // underneath us. Keep the file locked as briefly as possible.
+ // Freeing file_descriptor will close the file and and remove the lock.
+ if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
+ DPLOG(ERROR) << filename << ": cannot lock";
+ return false;
+ }
+
+ std::string msg = sample.ToString();
+ int32 size = msg.length() + sizeof(int32);
+ if (size > kMessageMaxLength) {
+ DLOG(ERROR) << "cannot write message: too long";
+ return false;
+ }
+
+ // The file containing the metrics samples will only be read by programs on
+ // the same device so we do not check endianness.
+ if (!base::WriteFileDescriptor(file_descriptor.get(),
+ reinterpret_cast<char*>(&size),
+ sizeof(size))) {
+ DPLOG(ERROR) << "error writing message length";
+ return false;
+ }
+
+ if (!base::WriteFileDescriptor(
+ file_descriptor.get(), msg.c_str(), msg.size())) {
+ DPLOG(ERROR) << "error writing message";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace metrics
diff --git a/metrics/serialization/serialization_utils.h b/metrics/serialization/serialization_utils.h
new file mode 100644
index 0000000..5af6166
--- /dev/null
+++ b/metrics/serialization/serialization_utils.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+#define METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+
+namespace metrics {
+
+class MetricSample;
+
+// Metrics helpers to serialize and deserialize metrics collected by
+// ChromeOS.
+namespace SerializationUtils {
+
+// Deserializes a sample passed as a string and return a sample.
+// The return value will either be a scoped_ptr to a Metric sample (if the
+// deserialization was successful) or a NULL scoped_ptr.
+scoped_ptr<MetricSample> ParseSample(const std::string& sample);
+
+// Reads all samples from a file and truncate the file when done.
+void ReadAndTruncateMetricsFromFile(const std::string& filename,
+ ScopedVector<MetricSample>* metrics);
+
+// Serializes a sample and write it to filename.
+// The format for the message is:
+// message_size, serialized_message
+// where
+// * message_size is the total length of the message (message_size +
+// serialized_message) on 4 bytes
+// * serialized_message is the serialized version of sample (using ToString)
+//
+// NB: the file will never leave the device so message_size will be written
+// with the architecture's endianness.
+bool WriteMetricToFile(const MetricSample& sample, const std::string& filename);
+
+// Maximum length of a serialized message
+static const int kMessageMaxLength = 1024;
+
+} // namespace SerializationUtils
+} // namespace metrics
+
+#endif // METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
diff --git a/metrics/serialization/serialization_utils_unittest.cc b/metrics/serialization/serialization_utils_unittest.cc
new file mode 100644
index 0000000..34d76cf
--- /dev/null
+++ b/metrics/serialization/serialization_utils_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/serialization/serialization_utils.h"
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "metrics/serialization/metric_sample.h"
+
+namespace metrics {
+namespace {
+
+class SerializationUtilsTest : public testing::Test {
+ protected:
+ SerializationUtilsTest() {
+ bool success = temporary_dir.CreateUniqueTempDir();
+ if (success) {
+ base::FilePath dir_path = temporary_dir.path();
+ filename = dir_path.value() + "chromeossampletest";
+ filepath = base::FilePath(filename);
+ }
+ }
+
+ void SetUp() override { base::DeleteFile(filepath, false); }
+
+ void TestSerialization(MetricSample* sample) {
+ std::string serialized(sample->ToString());
+ ASSERT_EQ('\0', serialized[serialized.length() - 1]);
+ scoped_ptr<MetricSample> deserialized =
+ SerializationUtils::ParseSample(serialized);
+ ASSERT_TRUE(deserialized);
+ EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
+ }
+
+ std::string filename;
+ base::ScopedTempDir temporary_dir;
+ base::FilePath filepath;
+};
+
+TEST_F(SerializationUtilsTest, CrashSerializeTest) {
+ TestSerialization(MetricSample::CrashSample("test").get());
+}
+
+TEST_F(SerializationUtilsTest, HistogramSerializeTest) {
+ TestSerialization(
+ MetricSample::HistogramSample("myhist", 13, 1, 100, 10).get());
+}
+
+TEST_F(SerializationUtilsTest, LinearSerializeTest) {
+ TestSerialization(
+ MetricSample::LinearHistogramSample("linearhist", 12, 30).get());
+}
+
+TEST_F(SerializationUtilsTest, SparseSerializeTest) {
+ TestSerialization(MetricSample::SparseHistogramSample("mysparse", 30).get());
+}
+
+TEST_F(SerializationUtilsTest, UserActionSerializeTest) {
+ TestSerialization(MetricSample::UserActionSample("myaction").get());
+}
+
+TEST_F(SerializationUtilsTest, IllegalNameAreFilteredTest) {
+ scoped_ptr<MetricSample> sample1 =
+ MetricSample::SparseHistogramSample("no space", 10);
+ scoped_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
+ base::StringPrintf("here%cbhe", '\0'), 1, 3);
+
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename));
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename));
+ int64 size = 0;
+
+ ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size));
+
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, BadInputIsCaughtTest) {
+ std::string input(
+ base::StringPrintf("sparsehistogram%cname foo%c", '\0', '\0'));
+ EXPECT_EQ(NULL, MetricSample::ParseSparseHistogram(input).get());
+}
+
+TEST_F(SerializationUtilsTest, MessageSeparatedByZero) {
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ // 4 bytes for the size
+ // 5 bytes for crash
+ // 7 bytes for mycrash
+ // 2 bytes for the \0
+ // -> total of 18
+ EXPECT_EQ(size, 18);
+}
+
+TEST_F(SerializationUtilsTest, MessagesTooLongAreDiscardedTest) {
+ // Creates a message that is bigger than the maximum allowed size.
+ // As we are adding extra character (crash, \0s, etc), if the name is
+ // kMessageMaxLength long, it will be too long.
+ std::string name(SerializationUtils::kMessageMaxLength, 'c');
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample(name);
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename));
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, ReadLongMessageTest) {
+ base::File test_file(filepath,
+ base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
+ std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
+
+ int32 message_size = message.length() + sizeof(int32);
+ test_file.WriteAtCurrentPos(reinterpret_cast<const char*>(&message_size),
+ sizeof(message_size));
+ test_file.WriteAtCurrentPos(message.c_str(), message.length());
+ test_file.Close();
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("test");
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+
+ ScopedVector<MetricSample> samples;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+ ASSERT_EQ(size_t(1), samples.size());
+ ASSERT_TRUE(samples[0] != NULL);
+ EXPECT_TRUE(crash->IsEqual(*samples[0]));
+}
+
+TEST_F(SerializationUtilsTest, WriteReadTest) {
+ scoped_ptr<MetricSample> hist =
+ MetricSample::HistogramSample("myhist", 1, 2, 3, 4);
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+ scoped_ptr<MetricSample> lhist =
+ MetricSample::LinearHistogramSample("linear", 1, 10);
+ scoped_ptr<MetricSample> shist =
+ MetricSample::SparseHistogramSample("mysparse", 30);
+ scoped_ptr<MetricSample> action = MetricSample::UserActionSample("myaction");
+
+ SerializationUtils::WriteMetricToFile(*hist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ SerializationUtils::WriteMetricToFile(*lhist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*shist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*action.get(), filename);
+ ScopedVector<MetricSample> vect;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect);
+ ASSERT_EQ(vect.size(), size_t(5));
+ for (int i = 0; i < 5; i++) {
+ ASSERT_TRUE(vect[0] != NULL);
+ }
+ EXPECT_TRUE(hist->IsEqual(*vect[0]));
+ EXPECT_TRUE(crash->IsEqual(*vect[1]));
+ EXPECT_TRUE(lhist->IsEqual(*vect[2]));
+ EXPECT_TRUE(shist->IsEqual(*vect[3]));
+ EXPECT_TRUE(action->IsEqual(*vect[4]));
+
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ ASSERT_EQ(0, size);
+}
+
+} // namespace
+} // namespace metrics
diff --git a/metrics/syslog_parser.sh b/metrics/syslog_parser.sh
new file mode 100755
index 0000000..7d064be
--- /dev/null
+++ b/metrics/syslog_parser.sh
@@ -0,0 +1,69 @@
+#! /bin/sh
+
+# This script parses /var/log/syslog for messages from programs that log
+# uptime and disk stats (number of sectors read). It then outputs
+# these stats in a format usable by the metrics collector, which forwards
+# them to autotest and UMA.
+
+# To add a new metric add a line below, as PROGRAM_NAME METRIC_NAME.
+# PROGRAM_NAME is the name of the job whose start time we
+# are interested in. METRIC_NAME is the prefix we want to use for
+# reporting to UMA and autotest. The script prepends "Time" and
+# "Sectors" to METRIC_NAME for the two available measurements, uptime
+# and number of sectors read thus far.
+
+# You will need to emit messages similar to the following in order to add a
+# a metric using this process. You will need to emit both a start and stop
+# time and the metric reported will be the difference in values
+
+# Nov 15 08:05 localhost PROGRAM_NAME[822]: start METRIC_NAME time 12 sectors 56
+# Nov 15 08:05 localhost PROGRAM_NAME[822]: stop METRIC_NAME time 24 sectors 68
+
+# If you add metrics without a start, it is assumed you are requesting the
+# time differece from system start
+
+# Metrics we are interested in measuring
+METRICS="
+upstart start_x
+"
+
+first=1
+program=""
+
+# Get the metrics for all things
+for m in $METRICS
+do
+ if [ $first -eq 1 ]
+ then
+ first=0
+ program_name=$m
+ else
+ first=1
+ metrics_name=$m
+
+ # Example of line from /var/log/messages:
+ # Nov 15 08:05:42 localhost connmand[822]: start metric time 12 sectors 56
+ # "upstart:" is $5, 1234 is $9, etc.
+ program="${program}/$program_name([[0-9]+]:|:) start $metrics_name/\
+ {
+ metrics_start[\"${metrics_name}Time\"] = \$9;
+ metrics_start[\"${metrics_name}Sectors\"] = \$11;
+ }"
+ program="${program}/$program_name([[0-9]+]:|:) stop $metrics_name/\
+ {
+ metrics_stop[\"${metrics_name}Time\"] = \$9;
+ metrics_stop[\"${metrics_name}Sectors\"] = \$11;
+ }"
+ fi
+done
+
+# Do all the differencing here
+program="${program}\
+END{
+ for (i in metrics_stop) {
+ value_time = metrics_stop[i] - metrics_start[i];
+ print i \"=\" value_time;
+ }
+}"
+
+exec awk "$program" /var/log/syslog
diff --git a/metrics/timer.cc b/metrics/timer.cc
new file mode 100644
index 0000000..99f68fe
--- /dev/null
+++ b/metrics/timer.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/timer.h"
+
+#include <string>
+
+#include <base/memory/scoped_ptr.h>
+
+#include "metrics/metrics_library.h"
+
+namespace chromeos_metrics {
+
+base::TimeTicks ClockWrapper::GetCurrentTime() const {
+ return base::TimeTicks::Now();
+}
+
+Timer::Timer()
+ : timer_state_(kTimerStopped),
+ clock_wrapper_(new ClockWrapper()) {}
+
+bool Timer::Start() {
+ elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero.
+ start_time_ = clock_wrapper_->GetCurrentTime();
+ timer_state_ = kTimerRunning;
+ return true;
+}
+
+bool Timer::Stop() {
+ if (timer_state_ == kTimerStopped)
+ return false;
+ if (timer_state_ == kTimerRunning)
+ elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
+ timer_state_ = kTimerStopped;
+ return true;
+}
+
+bool Timer::Pause() {
+ switch (timer_state_) {
+ case kTimerStopped:
+ if (!Start())
+ return false;
+ timer_state_ = kTimerPaused;
+ return true;
+ case kTimerRunning:
+ timer_state_ = kTimerPaused;
+ elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Timer::Resume() {
+ switch (timer_state_) {
+ case kTimerStopped:
+ return Start();
+ case kTimerPaused:
+ start_time_ = clock_wrapper_->GetCurrentTime();
+ timer_state_ = kTimerRunning;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Timer::Reset() {
+ elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero.
+ timer_state_ = kTimerStopped;
+ return true;
+}
+
+bool Timer::HasStarted() const {
+ return timer_state_ != kTimerStopped;
+}
+
+bool Timer::GetElapsedTime(base::TimeDelta* elapsed_time) const {
+ if (start_time_.is_null() || !elapsed_time)
+ return false;
+ *elapsed_time = elapsed_time_;
+ if (timer_state_ == kTimerRunning) {
+ *elapsed_time += clock_wrapper_->GetCurrentTime() - start_time_;
+ }
+ return true;
+}
+
+// static
+MetricsLibraryInterface* TimerReporter::metrics_lib_ = nullptr;
+
+TimerReporter::TimerReporter(const std::string& histogram_name, int min,
+ int max, int num_buckets)
+ : histogram_name_(histogram_name),
+ min_(min),
+ max_(max),
+ num_buckets_(num_buckets) {}
+
+bool TimerReporter::ReportMilliseconds() const {
+ base::TimeDelta elapsed_time;
+ if (!metrics_lib_ || !GetElapsedTime(&elapsed_time)) return false;
+ return metrics_lib_->SendToUMA(histogram_name_,
+ elapsed_time.InMilliseconds(),
+ min_,
+ max_,
+ num_buckets_);
+}
+
+} // namespace chromeos_metrics
diff --git a/metrics/timer.h b/metrics/timer.h
new file mode 100644
index 0000000..52cc578
--- /dev/null
+++ b/metrics/timer.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Timer - class that provides timer tracking.
+
+#ifndef METRICS_TIMER_H_
+#define METRICS_TIMER_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+class MetricsLibraryInterface;
+
+namespace chromeos_metrics {
+
+class TimerInterface {
+ public:
+ virtual ~TimerInterface() {}
+
+ virtual bool Start() = 0;
+ virtual bool Stop() = 0;
+ virtual bool Reset() = 0;
+ virtual bool HasStarted() const = 0;
+};
+
+// Wrapper for calls to the system clock.
+class ClockWrapper {
+ public:
+ ClockWrapper() {}
+ virtual ~ClockWrapper() {}
+
+ // Returns the current time from the system.
+ virtual base::TimeTicks GetCurrentTime() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ClockWrapper);
+};
+
+// Implements a Timer.
+class Timer : public TimerInterface {
+ public:
+ Timer();
+ virtual ~Timer() {}
+
+ // Starts the timer. If a timer is already running, also resets current
+ // timer. Always returns true.
+ virtual bool Start();
+
+ // Stops the timer and calculates the total time elapsed between now and when
+ // Start() was called. Note that this method needs a prior call to Start().
+ // Otherwise, it fails (returns false).
+ virtual bool Stop();
+
+ // Pauses a timer. If the timer is stopped, this call starts the timer in
+ // the paused state. Fails (returns false) if the timer is already paused.
+ virtual bool Pause();
+
+ // Restarts a paused timer (or starts a stopped timer). This method fails
+ // (returns false) if the timer is already running; otherwise, returns true.
+ virtual bool Resume();
+
+ // Resets the timer, erasing the current duration being tracked. Always
+ // returns true.
+ virtual bool Reset();
+
+ // Returns whether the timer has started or not.
+ virtual bool HasStarted() const;
+
+ // Stores the current elapsed time in |elapsed_time|. If timer is stopped,
+ // stores the elapsed time from when Stop() was last called. Otherwise,
+ // calculates and stores the elapsed time since the last Start().
+ // Returns false if the timer was never Start()'ed or if called with a null
+ // pointer argument.
+ virtual bool GetElapsedTime(base::TimeDelta* elapsed_time) const;
+
+ private:
+ enum TimerState { kTimerStopped, kTimerRunning, kTimerPaused };
+ friend class TimerTest;
+ friend class TimerReporterTest;
+ FRIEND_TEST(TimerReporterTest, StartStopReport);
+ FRIEND_TEST(TimerTest, InvalidElapsedTime);
+ FRIEND_TEST(TimerTest, InvalidStop);
+ FRIEND_TEST(TimerTest, PauseResumeStop);
+ FRIEND_TEST(TimerTest, PauseStartStopResume);
+ FRIEND_TEST(TimerTest, PauseStop);
+ FRIEND_TEST(TimerTest, Reset);
+ FRIEND_TEST(TimerTest, ReStart);
+ FRIEND_TEST(TimerTest, ResumeStartStopPause);
+ FRIEND_TEST(TimerTest, SeparatedTimers);
+ FRIEND_TEST(TimerTest, StartPauseResumePauseResumeStop);
+ FRIEND_TEST(TimerTest, StartPauseResumePauseStop);
+ FRIEND_TEST(TimerTest, StartPauseResumeStop);
+ FRIEND_TEST(TimerTest, StartPauseStop);
+ FRIEND_TEST(TimerTest, StartResumeStop);
+ FRIEND_TEST(TimerTest, StartStop);
+
+ // Elapsed time of the last use of the timer.
+ base::TimeDelta elapsed_time_;
+
+ // Starting time value.
+ base::TimeTicks start_time_;
+
+ // Whether the timer is running, stopped, or paused.
+ TimerState timer_state_;
+
+ // Wrapper for the calls to the system clock.
+ scoped_ptr<ClockWrapper> clock_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(Timer);
+};
+
+// Extends the Timer class to report the elapsed time in milliseconds through
+// the UMA metrics library.
+class TimerReporter : public Timer {
+ public:
+ // Initializes the timer by providing a |histogram_name| to report to with
+ // |min|, |max| and |num_buckets| attributes for the histogram.
+ TimerReporter(const std::string& histogram_name, int min, int max,
+ int num_buckets);
+ virtual ~TimerReporter() {}
+
+ // Sets the metrics library used by all instances of this class.
+ static void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+ metrics_lib_ = metrics_lib;
+ }
+
+ // Reports the current duration to UMA, in milliseconds. Returns false if
+ // there is nothing to report, e.g. a metrics library is not set.
+ virtual bool ReportMilliseconds() const;
+
+ // Accessor methods.
+ const std::string& histogram_name() const { return histogram_name_; }
+ int min() const { return min_; }
+ int max() const { return max_; }
+ int num_buckets() const { return num_buckets_; }
+
+ private:
+ friend class TimerReporterTest;
+ FRIEND_TEST(TimerReporterTest, StartStopReport);
+ FRIEND_TEST(TimerReporterTest, InvalidReport);
+
+ static MetricsLibraryInterface* metrics_lib_;
+ std::string histogram_name_;
+ int min_;
+ int max_;
+ int num_buckets_;
+
+ DISALLOW_COPY_AND_ASSIGN(TimerReporter);
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_TIMER_H_
diff --git a/metrics/timer_mock.h b/metrics/timer_mock.h
new file mode 100644
index 0000000..2f2d0f4
--- /dev/null
+++ b/metrics/timer_mock.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_TIMER_MOCK_H_
+#define METRICS_TIMER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "metrics/timer.h"
+
+namespace chromeos_metrics {
+
+class TimerMock : public Timer {
+ public:
+ MOCK_METHOD0(Start, bool());
+ MOCK_METHOD0(Stop, bool());
+ MOCK_METHOD0(Reset, bool());
+ MOCK_CONST_METHOD0(HasStarted, bool());
+ MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
+};
+
+class TimerReporterMock : public TimerReporter {
+ public:
+ TimerReporterMock() : TimerReporter("", 0, 0, 0) {}
+ MOCK_METHOD0(Start, bool());
+ MOCK_METHOD0(Stop, bool());
+ MOCK_METHOD0(Reset, bool());
+ MOCK_CONST_METHOD0(HasStarted, bool());
+ MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
+ MOCK_CONST_METHOD0(ReportMilliseconds, bool());
+ MOCK_CONST_METHOD0(histogram_name, std::string&());
+ MOCK_CONST_METHOD0(min, int());
+ MOCK_CONST_METHOD0(max, int());
+ MOCK_CONST_METHOD0(num_buckets, int());
+};
+
+class ClockWrapperMock : public ClockWrapper {
+ public:
+ MOCK_CONST_METHOD0(GetCurrentTime, base::TimeTicks());
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_TIMER_MOCK_H_
diff --git a/metrics/timer_test.cc b/metrics/timer_test.cc
new file mode 100644
index 0000000..ec6c6bd
--- /dev/null
+++ b/metrics/timer_test.cc
@@ -0,0 +1,452 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <base/memory/scoped_ptr.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metrics/metrics_library_mock.h"
+#include "metrics/timer.h"
+#include "metrics/timer_mock.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace chromeos_metrics {
+
+namespace {
+const int64_t kStime1MSec = 1400;
+const int64_t kEtime1MSec = 3000;
+const int64_t kDelta1MSec = 1600;
+
+const int64_t kStime2MSec = 4200;
+const int64_t kEtime2MSec = 5000;
+const int64_t kDelta2MSec = 800;
+
+const int64_t kStime3MSec = 6600;
+const int64_t kEtime3MSec = 6800;
+const int64_t kDelta3MSec = 200;
+} // namespace
+
+class TimerTest : public testing::Test {
+ public:
+ TimerTest() : clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+ virtual void SetUp() {
+ EXPECT_EQ(Timer::kTimerStopped, timer_.timer_state_);
+ stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
+ etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
+ stime2 += base::TimeDelta::FromMilliseconds(kStime2MSec);
+ etime2 += base::TimeDelta::FromMilliseconds(kEtime2MSec);
+ stime3 += base::TimeDelta::FromMilliseconds(kStime3MSec);
+ etime3 += base::TimeDelta::FromMilliseconds(kEtime3MSec);
+ }
+
+ virtual void TearDown() {}
+
+ Timer timer_;
+ scoped_ptr<ClockWrapperMock> clock_wrapper_mock_;
+ base::TimeTicks stime, etime, stime2, etime2, stime3, etime3;
+};
+
+TEST_F(TimerTest, StartStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, ReStart) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ timer_.Start();
+ base::TimeTicks buffer = timer_.start_time_;
+ timer_.Start();
+ ASSERT_FALSE(timer_.start_time_ == buffer);
+}
+
+TEST_F(TimerTest, Reset) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ timer_.Start();
+ ASSERT_TRUE(timer_.Reset());
+ ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, SeparatedTimers) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime2);
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, InvalidStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_FALSE(timer_.Stop());
+ // Now we try it again, but after a valid start/stop.
+ timer_.Start();
+ timer_.Stop();
+ base::TimeDelta elapsed_time = timer_.elapsed_time_;
+ ASSERT_FALSE(timer_.Stop());
+ ASSERT_TRUE(elapsed_time == timer_.elapsed_time_);
+}
+
+TEST_F(TimerTest, InvalidElapsedTime) {
+ base::TimeDelta elapsed_time;
+ ASSERT_FALSE(timer_.GetElapsedTime(&elapsed_time));
+}
+
+TEST_F(TimerTest, PauseStartStopResume) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2))
+ .WillOnce(Return(stime3))
+ .WillOnce(Return(etime3));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Pause()); // Starts timer paused.
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Start()); // Restarts timer.
+ ASSERT_TRUE(timer_.start_time_ == stime2);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(kDelta3MSec, elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, ResumeStartStopPause) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2))
+ .WillOnce(Return(stime3));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime2);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(0, elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_FALSE(timer_.Resume());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, PauseStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, PauseResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumePauseStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(stime3))
+ .WillOnce(Return(etime3));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+ // Make sure GetElapsedTime works while we're running.
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(kDelta1MSec + kStime3MSec - kStime2MSec,
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ kDelta1MSec + kEtime3MSec - kStime2MSec);
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ kDelta1MSec + kEtime3MSec - kStime2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumePauseResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2))
+ .WillOnce(Return(stime3))
+ .WillOnce(Return(etime3));
+ timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ kDelta1MSec + kDelta2MSec + kDelta3MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+static const char kMetricName[] = "test-timer";
+static const int kMinSample = 0;
+static const int kMaxSample = 120 * 1E6;
+static const int kNumBuckets = 50;
+
+class TimerReporterTest : public testing::Test {
+ public:
+ TimerReporterTest() : timer_reporter_(kMetricName, kMinSample, kMaxSample,
+ kNumBuckets),
+ clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+ virtual void SetUp() {
+ timer_reporter_.set_metrics_lib(&lib_);
+ EXPECT_EQ(timer_reporter_.histogram_name_, kMetricName);
+ EXPECT_EQ(timer_reporter_.min_, kMinSample);
+ EXPECT_EQ(timer_reporter_.max_, kMaxSample);
+ EXPECT_EQ(timer_reporter_.num_buckets_, kNumBuckets);
+ stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
+ etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
+ }
+
+ virtual void TearDown() {
+ timer_reporter_.set_metrics_lib(nullptr);
+ }
+
+ TimerReporter timer_reporter_;
+ MetricsLibraryMock lib_;
+ scoped_ptr<ClockWrapperMock> clock_wrapper_mock_;
+ base::TimeTicks stime, etime;
+};
+
+TEST_F(TimerReporterTest, StartStopReport) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_reporter_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+ EXPECT_CALL(lib_, SendToUMA(kMetricName, kDelta1MSec, kMinSample, kMaxSample,
+ kNumBuckets)).WillOnce(Return(true));
+ ASSERT_TRUE(timer_reporter_.Start());
+ ASSERT_TRUE(timer_reporter_.Stop());
+ ASSERT_TRUE(timer_reporter_.ReportMilliseconds());
+}
+
+TEST_F(TimerReporterTest, InvalidReport) {
+ ASSERT_FALSE(timer_reporter_.ReportMilliseconds());
+}
+
+} // namespace chromeos_metrics
+
+int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/metrics/uploader/metrics_hashes.cc b/metrics/uploader/metrics_hashes.cc
new file mode 100644
index 0000000..87405a3
--- /dev/null
+++ b/metrics/uploader/metrics_hashes.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/uploader/metrics_hashes.h"
+
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/sys_byteorder.h"
+
+namespace metrics {
+
+namespace {
+
+// Converts the 8-byte prefix of an MD5 hash into a uint64 value.
+inline uint64_t HashToUInt64(const std::string& hash) {
+ uint64_t value;
+ DCHECK_GE(hash.size(), sizeof(value));
+ memcpy(&value, hash.data(), sizeof(value));
+ return base::HostToNet64(value);
+}
+
+} // namespace
+
+uint64_t HashMetricName(const std::string& name) {
+ // Create an MD5 hash of the given |name|, represented as a byte buffer
+ // encoded as an std::string.
+ base::MD5Context context;
+ base::MD5Init(&context);
+ base::MD5Update(&context, name);
+
+ base::MD5Digest digest;
+ base::MD5Final(&digest, &context);
+
+ std::string hash_str(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
+ return HashToUInt64(hash_str);
+}
+
+} // namespace metrics
diff --git a/metrics/uploader/metrics_hashes.h b/metrics/uploader/metrics_hashes.h
new file mode 100644
index 0000000..8679077
--- /dev/null
+++ b/metrics/uploader/metrics_hashes.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_METRICS_HASHES_H_
+#define METRICS_UPLOADER_METRICS_HASHES_H_
+
+#include <string>
+
+namespace metrics {
+
+// Computes a uint64 hash of a given string based on its MD5 hash. Suitable for
+// metric names.
+uint64_t HashMetricName(const std::string& name);
+
+} // namespace metrics
+
+#endif // METRICS_UPLOADER_METRICS_HASHES_H_
diff --git a/metrics/uploader/metrics_hashes_unittest.cc b/metrics/uploader/metrics_hashes_unittest.cc
new file mode 100644
index 0000000..f7e390f
--- /dev/null
+++ b/metrics/uploader/metrics_hashes_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/uploader/metrics_hashes.h"
+
+#include <base/format_macros.h>
+#include <base/macros.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+namespace metrics {
+
+// Make sure our ID hashes are the same as what we see on the server side.
+TEST(MetricsUtilTest, HashMetricName) {
+ static const struct {
+ std::string input;
+ std::string output;
+ } cases[] = {
+ {"Back", "0x0557fa923dcee4d0"},
+ {"Forward", "0x67d2f6740a8eaebf"},
+ {"NewTab", "0x290eb683f96572f1"},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ uint64_t hash = HashMetricName(cases[i].input);
+ std::string hash_hex = base::StringPrintf("0x%016" PRIx64, hash);
+ EXPECT_EQ(cases[i].output, hash_hex);
+ }
+}
+
+} // namespace metrics
diff --git a/metrics/uploader/metrics_log.cc b/metrics/uploader/metrics_log.cc
new file mode 100644
index 0000000..4d493b8
--- /dev/null
+++ b/metrics/uploader/metrics_log.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/metrics_log.h"
+
+#include <string>
+
+#include "metrics/uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_setter.h"
+
+// We use default values for the MetricsLogBase constructor as the setter will
+// override them.
+MetricsLog::MetricsLog()
+ : MetricsLogBase("", 0, metrics::MetricsLogBase::ONGOING_LOG, "") {
+}
+
+void MetricsLog::IncrementUserCrashCount() {
+ metrics::SystemProfileProto::Stability* stability(
+ uma_proto()->mutable_system_profile()->mutable_stability());
+ int current = stability->other_user_crash_count();
+ stability->set_other_user_crash_count(current + 1);
+}
+
+void MetricsLog::IncrementKernelCrashCount() {
+ metrics::SystemProfileProto::Stability* stability(
+ uma_proto()->mutable_system_profile()->mutable_stability());
+ int current = stability->kernel_crash_count();
+ stability->set_kernel_crash_count(current + 1);
+}
+
+void MetricsLog::IncrementUncleanShutdownCount() {
+ metrics::SystemProfileProto::Stability* stability(
+ uma_proto()->mutable_system_profile()->mutable_stability());
+ int current = stability->unclean_system_shutdown_count();
+ stability->set_unclean_system_shutdown_count(current + 1);
+}
+
+void MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) {
+ profile_setter->Populate(uma_proto());
+}
diff --git a/metrics/uploader/metrics_log.h b/metrics/uploader/metrics_log.h
new file mode 100644
index 0000000..5796325
--- /dev/null
+++ b/metrics/uploader/metrics_log.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_METRICS_LOG_H_
+#define METRICS_UPLOADER_METRICS_LOG_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "metrics/uploader/metrics_log_base.h"
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService. This is the unit of data that is sent to the server.
+class SystemProfileSetter;
+
+// This class provides base functionality for logging metrics data.
+class MetricsLog : public metrics::MetricsLogBase {
+ public:
+ // The constructor doesn't set any metadata. The metadata is only set by a
+ // SystemProfileSetter.
+ MetricsLog();
+
+ void IncrementUserCrashCount();
+ void IncrementKernelCrashCount();
+ void IncrementUncleanShutdownCount();
+
+ // Populate the system profile with system information using setter.
+ void PopulateSystemProfile(SystemProfileSetter* setter);
+
+ private:
+ FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+ FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+ FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+ FRIEND_TEST(UploadServiceTest, LogUserCrash);
+ FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+#endif // METRICS_UPLOADER_METRICS_LOG_H_
diff --git a/metrics/uploader/metrics_log_base.cc b/metrics/uploader/metrics_log_base.cc
new file mode 100644
index 0000000..7fe1ae1
--- /dev/null
+++ b/metrics/uploader/metrics_log_base.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/uploader/metrics_log_base.h"
+
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "metrics/uploader/metrics_hashes.h"
+#include "metrics/uploader/proto/histogram_event.pb.h"
+#include "metrics/uploader/proto/system_profile.pb.h"
+#include "metrics/uploader/proto/user_action_event.pb.h"
+
+using base::Histogram;
+using base::HistogramBase;
+using base::HistogramSamples;
+using base::SampleCountIterator;
+using base::Time;
+using base::TimeDelta;
+using metrics::HistogramEventProto;
+using metrics::SystemProfileProto;
+using metrics::UserActionEventProto;
+
+namespace metrics {
+namespace {
+
+// Any id less than 16 bytes is considered to be a testing id.
+bool IsTestingID(const std::string& id) {
+ return id.size() < 16;
+}
+
+} // namespace
+
+MetricsLogBase::MetricsLogBase(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ const std::string& version_string)
+ : num_events_(0),
+ locked_(false),
+ log_type_(log_type) {
+ DCHECK_NE(NO_LOG, log_type);
+ if (IsTestingID(client_id))
+ uma_proto_.set_client_id(0);
+ else
+ uma_proto_.set_client_id(Hash(client_id));
+
+ uma_proto_.set_session_id(session_id);
+ uma_proto_.mutable_system_profile()->set_build_timestamp(GetBuildTime());
+ uma_proto_.mutable_system_profile()->set_app_version(version_string);
+}
+
+MetricsLogBase::~MetricsLogBase() {}
+
+// static
+uint64_t MetricsLogBase::Hash(const std::string& value) {
+ uint64_t hash = metrics::HashMetricName(value);
+
+ // The following log is VERY helpful when folks add some named histogram into
+ // the code, but forgot to update the descriptive list of histograms. When
+ // that happens, all we get to see (server side) is a hash of the histogram
+ // name. We can then use this logging to find out what histogram name was
+ // being hashed to a given MD5 value by just running the version of Chromium
+ // in question with --enable-logging.
+ DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]";
+
+ return hash;
+}
+
+// static
+int64_t MetricsLogBase::GetBuildTime() {
+ static int64_t integral_build_time = 0;
+ if (!integral_build_time) {
+ Time time;
+ const char* kDateTime = __DATE__ " " __TIME__ " GMT";
+ bool result = Time::FromString(kDateTime, &time);
+ DCHECK(result);
+ integral_build_time = static_cast<int64_t>(time.ToTimeT());
+ }
+ return integral_build_time;
+}
+
+// static
+int64_t MetricsLogBase::GetCurrentTime() {
+ return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
+void MetricsLogBase::CloseLog() {
+ DCHECK(!locked_);
+ locked_ = true;
+}
+
+void MetricsLogBase::GetEncodedLog(std::string* encoded_log) {
+ DCHECK(locked_);
+ uma_proto_.SerializeToString(encoded_log);
+}
+
+void MetricsLogBase::RecordUserAction(const std::string& key) {
+ DCHECK(!locked_);
+
+ UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+ user_action->set_name_hash(Hash(key));
+ user_action->set_time(GetCurrentTime());
+
+ ++num_events_;
+}
+
+void MetricsLogBase::RecordHistogramDelta(const std::string& histogram_name,
+ const HistogramSamples& snapshot) {
+ DCHECK(!locked_);
+ DCHECK_NE(0, snapshot.TotalCount());
+
+ // We will ignore the MAX_INT/infinite value in the last element of range[].
+
+ HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event();
+ histogram_proto->set_name_hash(Hash(histogram_name));
+ histogram_proto->set_sum(snapshot.sum());
+
+ for (scoped_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done();
+ it->Next()) {
+ HistogramBase::Sample min;
+ HistogramBase::Sample max;
+ HistogramBase::Count count;
+ it->Get(&min, &max, &count);
+ HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+ bucket->set_min(min);
+ bucket->set_max(max);
+ bucket->set_count(count);
+ }
+
+ // Omit fields to save space (see rules in histogram_event.proto comments).
+ for (int i = 0; i < histogram_proto->bucket_size(); ++i) {
+ HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i);
+ if (i + 1 < histogram_proto->bucket_size() &&
+ bucket->max() == histogram_proto->bucket(i + 1).min()) {
+ bucket->clear_max();
+ } else if (bucket->max() == bucket->min() + 1) {
+ bucket->clear_min();
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/metrics/uploader/metrics_log_base.h b/metrics/uploader/metrics_log_base.h
new file mode 100644
index 0000000..e871c0f
--- /dev/null
+++ b/metrics/uploader/metrics_log_base.h
@@ -0,0 +1,110 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService. This is the unit of data that is sent to the server.
+
+#ifndef METRICS_UPLOADER_METRICS_LOG_BASE_H_
+#define METRICS_UPLOADER_METRICS_LOG_BASE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "metrics/uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class HistogramSamples;
+} // namespace base
+
+namespace metrics {
+
+// This class provides base functionality for logging metrics data.
+class MetricsLogBase {
+ public:
+ // TODO(asvitkine): Remove the NO_LOG value.
+ enum LogType {
+ INITIAL_STABILITY_LOG, // The initial log containing stability stats.
+ ONGOING_LOG, // Subsequent logs in a session.
+ NO_LOG, // Placeholder value for when there is no log.
+ };
+
+ // Creates a new metrics log of the specified type.
+ // client_id is the identifier for this profile on this installation
+ // session_id is an integer that's incremented on each application launch
+ MetricsLogBase(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ const std::string& version_string);
+ virtual ~MetricsLogBase();
+
+ // Computes the MD5 hash of the given string, and returns the first 8 bytes of
+ // the hash.
+ static uint64_t Hash(const std::string& value);
+
+ // Get the GMT buildtime for the current binary, expressed in seconds since
+ // January 1, 1970 GMT.
+ // The value is used to identify when a new build is run, so that previous
+ // reliability stats, from other builds, can be abandoned.
+ static int64_t GetBuildTime();
+
+ // Convenience function to return the current time at a resolution in seconds.
+ // This wraps base::TimeTicks, and hence provides an abstract time that is
+ // always incrementing for use in measuring time durations.
+ static int64_t GetCurrentTime();
+
+ // Records a user-initiated action.
+ void RecordUserAction(const std::string& key);
+
+ // Record any changes in a given histogram for transmission.
+ void RecordHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot);
+
+ // Stop writing to this record and generate the encoded representation.
+ // None of the Record* methods can be called after this is called.
+ void CloseLog();
+
+ // Fills |encoded_log| with the serialized protobuf representation of the
+ // record. Must only be called after CloseLog() has been called.
+ void GetEncodedLog(std::string* encoded_log);
+
+ int num_events() { return num_events_; }
+
+ void set_hardware_class(const std::string& hardware_class) {
+ uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class(
+ hardware_class);
+ }
+
+ LogType log_type() const { return log_type_; }
+
+ protected:
+ bool locked() const { return locked_; }
+
+ metrics::ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
+ const metrics::ChromeUserMetricsExtension* uma_proto() const {
+ return &uma_proto_;
+ }
+
+ // TODO(isherman): Remove this once the XML pipeline is outta here.
+ int num_events_; // the number of events recorded in this log
+
+ private:
+ // locked_ is true when record has been packed up for sending, and should
+ // no longer be written to. It is only used for sanity checking and is
+ // not a real lock.
+ bool locked_;
+
+ // The type of the log, i.e. initial or ongoing.
+ const LogType log_type_;
+
+ // Stores the protocol buffer representation for this log.
+ metrics::ChromeUserMetricsExtension uma_proto_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogBase);
+};
+
+} // namespace metrics
+
+#endif // METRICS_UPLOADER_METRICS_LOG_BASE_H_
diff --git a/metrics/uploader/metrics_log_base_unittest.cc b/metrics/uploader/metrics_log_base_unittest.cc
new file mode 100644
index 0000000..5da428a
--- /dev/null
+++ b/metrics/uploader/metrics_log_base_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/uploader/metrics_log_base.h"
+
+#include <string>
+
+#include <base/metrics/bucket_ranges.h>
+#include <base/metrics/sample_vector.h>
+#include <gtest/gtest.h>
+
+#include "metrics/uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+namespace {
+
+class TestMetricsLogBase : public MetricsLogBase {
+ public:
+ TestMetricsLogBase()
+ : MetricsLogBase("client_id", 1, MetricsLogBase::ONGOING_LOG, "1.2.3.4") {
+ }
+ virtual ~TestMetricsLogBase() {}
+
+ using MetricsLogBase::uma_proto;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsLogBase);
+};
+
+} // namespace
+
+TEST(MetricsLogBaseTest, LogType) {
+ MetricsLogBase log1("id", 0, MetricsLogBase::ONGOING_LOG, "1.2.3");
+ EXPECT_EQ(MetricsLogBase::ONGOING_LOG, log1.log_type());
+
+ MetricsLogBase log2("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "1.2.3");
+ EXPECT_EQ(MetricsLogBase::INITIAL_STABILITY_LOG, log2.log_type());
+}
+
+TEST(MetricsLogBaseTest, EmptyRecord) {
+ MetricsLogBase log("totally bogus client ID", 137,
+ MetricsLogBase::ONGOING_LOG, "bogus version");
+ log.set_hardware_class("sample-class");
+ log.CloseLog();
+
+ std::string encoded;
+ log.GetEncodedLog(&encoded);
+
+ // A couple of fields are hard to mock, so these will be copied over directly
+ // for the expected output.
+ metrics::ChromeUserMetricsExtension parsed;
+ ASSERT_TRUE(parsed.ParseFromString(encoded));
+
+ metrics::ChromeUserMetricsExtension expected;
+ expected.set_client_id(5217101509553811875); // Hashed bogus client ID
+ expected.set_session_id(137);
+ expected.mutable_system_profile()->set_build_timestamp(
+ parsed.system_profile().build_timestamp());
+ expected.mutable_system_profile()->set_app_version("bogus version");
+ expected.mutable_system_profile()->mutable_hardware()->set_hardware_class(
+ "sample-class");
+
+ EXPECT_EQ(expected.SerializeAsString(), encoded);
+}
+
+TEST(MetricsLogBaseTest, HistogramBucketFields) {
+ // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+ base::BucketRanges ranges(8);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 7);
+ ranges.set_range(3, 8);
+ ranges.set_range(4, 9);
+ ranges.set_range(5, 10);
+ ranges.set_range(6, 11);
+ ranges.set_range(7, 12);
+
+ base::SampleVector samples(&ranges);
+ samples.Accumulate(3, 1); // Bucket 1-5.
+ samples.Accumulate(6, 1); // Bucket 5-7.
+ samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped)
+ samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped)
+ samples.Accumulate(11, 1); // Bucket 11-12.
+
+ TestMetricsLogBase log;
+ log.RecordHistogramDelta("Test", samples);
+
+ const metrics::ChromeUserMetricsExtension* uma_proto = log.uma_proto();
+ const metrics::HistogramEventProto& histogram_proto =
+ uma_proto->histogram_event(uma_proto->histogram_event_size() - 1);
+
+ // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+ // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+ ASSERT_EQ(5, histogram_proto.bucket_size());
+
+ // 1-5 becomes 1-/ (max is same as next min).
+ EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+ EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+ // 5-7 stays 5-7 (no optimization possible).
+ EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+ EXPECT_EQ(5, histogram_proto.bucket(1).min());
+ EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+ // 8-9 becomes /-9 (min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+ EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+ // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+ EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+ EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+ // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+ EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+} // namespace metrics
diff --git a/metrics/uploader/mock/mock_system_profile_setter.h b/metrics/uploader/mock/mock_system_profile_setter.h
new file mode 100644
index 0000000..c6e8f0d
--- /dev/null
+++ b/metrics/uploader/mock/mock_system_profile_setter.h
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
+#define METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
+
+#include "uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+// Mock profile setter used for testing.
+class MockSystemProfileSetter : public SystemProfileSetter {
+ public:
+ void Populate(metrics::ChromeUserMetricsExtension* profile_proto) override {}
+};
+
+#endif // METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
diff --git a/metrics/uploader/mock/sender_mock.cc b/metrics/uploader/mock/sender_mock.cc
new file mode 100644
index 0000000..064ec6d
--- /dev/null
+++ b/metrics/uploader/mock/sender_mock.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/mock/sender_mock.h"
+
+SenderMock::SenderMock() {
+ Reset();
+}
+
+bool SenderMock::Send(const std::string& content, const std::string& hash) {
+ send_call_count_ += 1;
+ last_message_ = content;
+ is_good_proto_ = last_message_proto_.ParseFromString(content);
+ return should_succeed_;
+}
+
+void SenderMock::Reset() {
+ send_call_count_ = 0;
+ last_message_ = "";
+ should_succeed_ = true;
+ last_message_proto_.Clear();
+ is_good_proto_ = false;
+}
diff --git a/metrics/uploader/mock/sender_mock.h b/metrics/uploader/mock/sender_mock.h
new file mode 100644
index 0000000..159b645
--- /dev/null
+++ b/metrics/uploader/mock/sender_mock.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+#define METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "metrics/uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/sender.h"
+
+class SenderMock : public Sender {
+ public:
+ SenderMock();
+
+ bool Send(const std::string& content, const std::string& hash) override;
+ void Reset();
+
+ bool is_good_proto() { return is_good_proto_; }
+ int send_call_count() { return send_call_count_; }
+ const std::string last_message() { return last_message_; }
+ metrics::ChromeUserMetricsExtension last_message_proto() {
+ return last_message_proto_;
+ }
+ void set_should_succeed(bool succeed) { should_succeed_ = succeed; }
+
+ private:
+ // Is set to true if the proto was parsed successfully.
+ bool is_good_proto_;
+
+ // If set to true, the Send method will return true to simulate a successful
+ // send.
+ bool should_succeed_;
+
+ // Count of how many times Send was called since the last reset.
+ int send_call_count_;
+
+ // Last message received by Send.
+ std::string last_message_;
+
+ // If is_good_proto is true, last_message_proto is the deserialized
+ // representation of last_message.
+ metrics::ChromeUserMetricsExtension last_message_proto_;
+};
+
+#endif // METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
diff --git a/metrics/uploader/proto/README b/metrics/uploader/proto/README
new file mode 100644
index 0000000..9bd3249
--- /dev/null
+++ b/metrics/uploader/proto/README
@@ -0,0 +1,25 @@
+Copyright 2015 The Chromium OS Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+
+
+This directory contains the protocol buffers used by the standalone metrics
+uploader. Those protobuffers are copied from the chromium protobuffers from
+https://chromium.googlesource.com/chromium/src/+/master/components/metrics/proto/
+at 3bfe5f2b4c03d2cac718d137ed14cd2c6354bfed.
+
+Any change to this protobuf must first be made to the backend's protobuf and be
+compatible with the chromium protobuffers.
+
+
+Q: Why fork the chromium protobuffers ?
+A: The standalone metrics uploader needs chromium os fields that are not defined
+by the chromium protobufs. Instead of pushing chromium os specific changes to
+chromium, we can add them only to chromium os (and to the backend of course).
+
+
+Q: What's the difference between those protobuffers and chromium's protobuffers?
+A: When the protobuffers were copied, some chromium specific protobuffers were
+not imported:
+* omnibox related protobuffers.
+* performance profiling protobuffers (not used in chromium os).
diff --git a/metrics/uploader/proto/chrome_user_metrics_extension.proto b/metrics/uploader/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 0000000..f712fc9
--- /dev/null
+++ b/metrics/uploader/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Protocol buffer for Chrome UMA (User Metrics Analysis).
+//
+// Note: this protobuf must be compatible with the one in chromium.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "ChromeUserMetricsExtensionProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "histogram_event.proto";
+import "system_profile.proto";
+import "user_action_event.proto";
+
+// Next tag: 13
+message ChromeUserMetricsExtension {
+ // The product (i.e. end user application) for a given UMA log.
+ enum Product {
+ // Google Chrome product family.
+ CHROME = 0;
+ }
+ // The product corresponding to this log. The field type is int32 instead of
+ // Product so that downstream users of the Chromium metrics component can
+ // introduce products without needing to make changes to the Chromium code
+ // (though they still need to add the new product to the server-side enum).
+ // Note: The default value is Chrome, so Chrome products will not transmit
+ // this field.
+ optional int32 product = 10 [default = 0];
+
+ // The id of the client install that generated these events.
+ //
+ // For Chrome clients, this id is unique to a top-level (one level above the
+ // "Default" directory) Chrome user data directory [1], and so is shared among
+ // all Chrome user profiles contained in this user data directory.
+ // An id of 0 is reserved for test data (monitoring and internal testing) and
+ // should normally be ignored in analysis of the data.
+ // [1] http://www.chromium.org/user-experience/user-data-directory
+ optional fixed64 client_id = 1;
+
+ // The session id for this user.
+ // Values such as tab ids are only meaningful within a particular session.
+ // The client keeps track of the session id and sends it with each event.
+ // The session id is simply an integer that is incremented each time the user
+ // relaunches Chrome.
+ optional int32 session_id = 2;
+
+ // Information about the user's browser and system configuration.
+ optional SystemProfileProto system_profile = 3;
+
+ // This message will log one or more of the following event types:
+ repeated UserActionEventProto user_action_event = 4;
+ repeated HistogramEventProto histogram_event = 6;
+
+}
diff --git a/metrics/uploader/proto/histogram_event.proto b/metrics/uploader/proto/histogram_event.proto
new file mode 100644
index 0000000..4b68094
--- /dev/null
+++ b/metrics/uploader/proto/histogram_event.proto
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Histogram-collected metrics.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "HistogramEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 4
+message HistogramEventProto {
+ // The name of the histogram, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The sum of all the sample values.
+ // Together with the total count of the sample values, this allows us to
+ // compute the average value. The count of all sample values is just the sum
+ // of the counts of all the buckets.
+ optional int64 sum = 2;
+
+ // The per-bucket data.
+ message Bucket {
+ // Each bucket's range is bounded by min <= x < max.
+ // It is valid to omit one of these two fields in a bucket, but not both.
+ // If the min field is omitted, its value is assumed to be equal to max - 1.
+ // If the max field is omitted, its value is assumed to be equal to the next
+ // bucket's min value (possibly computed per above). The last bucket in a
+ // histogram should always include the max field.
+ optional int64 min = 1;
+ optional int64 max = 2;
+
+ // The bucket's index in the list of buckets, sorted in ascending order.
+ // This field was intended to provide extra redundancy to detect corrupted
+ // records, but was never used. As of M31, it is no longer sent by Chrome
+ // clients to reduce the UMA upload size.
+ optional int32 bucket_index = 3 [deprecated = true];
+
+ // The number of entries in this bucket.
+ optional int64 count = 4;
+ }
+ repeated Bucket bucket = 3;
+}
diff --git a/metrics/uploader/proto/system_profile.proto b/metrics/uploader/proto/system_profile.proto
new file mode 100644
index 0000000..d33ff60
--- /dev/null
+++ b/metrics/uploader/proto/system_profile.proto
@@ -0,0 +1,747 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about the user's brower and system configuration.
+// The system configuration fields are recorded once per client session.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "SystemProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 21
+message SystemProfileProto {
+ // The time when the client was compiled/linked, in seconds since the epoch.
+ optional int64 build_timestamp = 1;
+
+ // A version number string for the application.
+ // Most commonly this is the browser version number found in a user agent
+ // string, and is typically a 4-tuple of numbers separated by periods. In
+ // cases where the user agent version might be ambiguous (example: Linux 64-
+ // bit build, rather than 32-bit build, or a Windows version used in some
+ // special context, such as ChromeFrame running in IE), then this may include
+ // some additional postfix to provide clarification not available in the UA
+ // string.
+ //
+ // An example of a browser version 4-tuple is "5.0.322.0". Currently used
+ // postfixes are:
+ //
+ // "-64": a 64-bit build
+ // "-F": Chrome is running under control of ChromeFrame
+ // "-devel": this is not an official build of Chrome
+ //
+ // A full version number string could look similar to:
+ // "5.0.322.0-F-devel".
+ //
+ // This value, when available, is more trustworthy than the UA string
+ // associated with the request; and including the postfix, may be more
+ // specific.
+ optional string app_version = 2;
+
+ // The brand code or distribution tag assigned to a partner, if available.
+ // Brand codes are only available on Windows. Not every Windows install
+ // though will have a brand code.
+ optional string brand_code = 12;
+
+ // The possible channels for an installation, from least to most stable.
+ enum Channel {
+ CHANNEL_UNKNOWN = 0; // Unknown channel -- perhaps an unofficial build?
+ CHANNEL_CANARY = 1;
+ CHANNEL_DEV = 2;
+ CHANNEL_BETA = 3;
+ CHANNEL_STABLE = 4;
+ }
+ optional Channel channel = 10;
+
+ // True if Chrome build is ASan-instrumented.
+ optional bool is_asan_build = 20 [default = false];
+
+ // The date the user enabled UMA, in seconds since the epoch.
+ // If the user has toggled the UMA enabled state multiple times, this will
+ // be the most recent date on which UMA was enabled.
+ // For privacy, this is rounded to the nearest hour.
+ optional int64 uma_enabled_date = 3;
+
+ // The time when the client was installed, in seconds since the epoch.
+ // For privacy, this is rounded to the nearest hour.
+ optional int64 install_date = 16;
+
+ // The user's selected application locale, i.e. the user interface language.
+ // The locale includes a language code and, possibly, also a country code,
+ // e.g. "en-US".
+ optional string application_locale = 4;
+
+ message BrilloDeviceData {
+ optional string build_target_id = 1;
+ }
+ optional BrilloDeviceData brillo = 21;
+
+ // Information on the user's operating system.
+ message OS {
+ // The user's operating system. This should be one of:
+ // - Android
+ // - Windows NT
+ // - Linux (includes ChromeOS)
+ // - iPhone OS
+ // - Mac OS X
+ optional string name = 1;
+
+ // The version of the OS. The meaning of this field is OS-dependent.
+ optional string version = 2;
+
+ // The fingerprint of the build. This field is used only on Android.
+ optional string fingerprint = 3;
+
+ // Whether the version of iOS appears to be "jailbroken". This field is
+ // used only on iOS. Chrome for iOS detects whether device contains a
+ // DynamicLibraries/ directory. It's a necessary but insufficient indicator
+ // of whether the operating system has been jailbroken.
+ optional bool is_jailbroken = 4;
+ }
+ optional OS os = 5;
+
+ // Next tag for Hardware: 18
+ // Information on the user's hardware.
+ message Hardware {
+ // The CPU architecture (x86, PowerPC, x86_64, ...)
+ optional string cpu_architecture = 1;
+
+ // The amount of RAM present on the system, in megabytes.
+ optional int64 system_ram_mb = 2;
+
+ // The base memory address that chrome.dll was loaded at.
+ // (Logged only on Windows.)
+ optional int64 dll_base = 3;
+
+ // The Chrome OS device hardware class ID is a unique string associated with
+ // each Chrome OS device product revision generally assigned at hardware
+ // qualification time. The hardware class effectively identifies the
+ // configured system components such as CPU, WiFi adapter, etc.
+ //
+ // An example of such a hardware class is "IEC MARIO PONY 6101". An
+ // internal database associates this hardware class with the qualified
+ // device specifications including OEM information, schematics, hardware
+ // qualification reports, test device tags, etc.
+ optional string hardware_class = 4;
+
+ // The number of physical screens.
+ optional int32 screen_count = 5;
+
+ // The screen dimensions of the primary screen, in pixels.
+ optional int32 primary_screen_width = 6;
+ optional int32 primary_screen_height = 7;
+
+ // The device scale factor of the primary screen.
+ optional float primary_screen_scale_factor = 12;
+
+ // Max DPI for any attached screen. (Windows only)
+ optional float max_dpi_x = 9;
+ optional float max_dpi_y = 10;
+
+ // Information on the CPU obtained by CPUID.
+ message CPU {
+ // A 12 character string naming the vendor, e.g. "GeniuneIntel".
+ optional string vendor_name = 1;
+
+ // The signature reported by CPUID (from EAX).
+ optional uint32 signature = 2;
+
+ // Number of logical processors/cores on the current machine.
+ optional uint32 num_cores = 3;
+ }
+ optional CPU cpu = 13;
+
+ // Information on the GPU
+ message Graphics {
+ // The GPU manufacturer's vendor id.
+ optional uint32 vendor_id = 1;
+
+ // The GPU manufacturer's device id for the chip set.
+ optional uint32 device_id = 2;
+
+ // The driver version on the GPU.
+ optional string driver_version = 3;
+
+ // The driver date on the GPU.
+ optional string driver_date = 4;
+
+ // The GL_VENDOR string. An example of a gl_vendor string is
+ // "Imagination Technologies". "" if we are not using OpenGL.
+ optional string gl_vendor = 6;
+
+ // The GL_RENDERER string. An example of a gl_renderer string is
+ // "PowerVR SGX 540". "" if we are not using OpenGL.
+ optional string gl_renderer = 7;
+ }
+ optional Graphics gpu = 8;
+
+ // Information about Bluetooth devices paired with the system.
+ message Bluetooth {
+ // Whether Bluetooth is present on this system.
+ optional bool is_present = 1;
+
+ // Whether Bluetooth is enabled on this system.
+ optional bool is_enabled = 2;
+
+ // Describes a paired device.
+ message PairedDevice {
+ // Assigned class of the device. This is a bitfield according to the
+ // Bluetooth specification available at the following URL:
+ // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/baseband
+ optional uint32 bluetooth_class = 1;
+
+ // Decoded device type.
+ enum Type {
+ DEVICE_UNKNOWN = 0;
+ DEVICE_COMPUTER = 1;
+ DEVICE_PHONE = 2;
+ DEVICE_MODEM = 3;
+ DEVICE_AUDIO = 4;
+ DEVICE_CAR_AUDIO = 5;
+ DEVICE_VIDEO = 6;
+ DEVICE_PERIPHERAL = 7;
+ DEVICE_JOYSTICK = 8;
+ DEVICE_GAMEPAD = 9;
+ DEVICE_KEYBOARD = 10;
+ DEVICE_MOUSE = 11;
+ DEVICE_TABLET = 12;
+ DEVICE_KEYBOARD_MOUSE_COMBO = 13;
+ }
+ optional Type type = 2;
+
+ // Vendor prefix of the Bluetooth address, these are OUI registered by
+ // the IEEE and are encoded with the first byte in bits 16-23, the
+ // second byte in bits 8-15 and the third byte in bits 0-7.
+ //
+ // ie. Google's OUI (00:1A:11) is encoded as 0x00001A11
+ optional uint32 vendor_prefix = 4;
+
+ // The Vendor ID of a device, returned in vendor_id below, can be
+ // either allocated by the Bluetooth SIG or USB IF, providing two
+ // completely overlapping namespaces for identifiers.
+ //
+ // This field should be read along with vendor_id to correctly
+ // identify the vendor. For example Google is identified by either
+ // vendor_id_source = VENDOR_ID_BLUETOOTH, vendor_id = 0x00E0 or
+ // vendor_id_source = VENDOR_ID_USB, vendor_id = 0x18D1.
+ //
+ // If the device does not support the Device ID specification the
+ // unknown value will be set.
+ enum VendorIDSource {
+ VENDOR_ID_UNKNOWN = 0;
+ VENDOR_ID_BLUETOOTH = 1;
+ VENDOR_ID_USB = 2;
+ }
+ optional VendorIDSource vendor_id_source = 8;
+
+ // Vendor ID of the device, where available.
+ optional uint32 vendor_id = 5;
+
+ // Product ID of the device, where available.
+ optional uint32 product_id = 6;
+
+ // Device ID of the device, generally the release or version number in
+ // BCD format, where available.
+ optional uint32 device_id = 7;
+ }
+ repeated PairedDevice paired_device = 3;
+ }
+ optional Bluetooth bluetooth = 11;
+
+ // Whether the internal display produces touch events. Omitted if unknown.
+ // Logged on ChromeOS only.
+ optional bool internal_display_supports_touch = 14;
+
+ // Vendor ids and product ids of external touchscreens.
+ message TouchScreen {
+ // Touch screen vendor id.
+ optional uint32 vendor_id = 1;
+ // Touch screen product id.
+ optional uint32 product_id = 2;
+ }
+ // Lists vendor and product ids of external touchscreens.
+ // Logged on ChromeOS only.
+ repeated TouchScreen external_touchscreen = 15;
+
+ // Drive messages are currently logged on Windows 7+, iOS, and Android.
+ message Drive {
+ // Whether this drive incurs a time penalty when randomly accessed. This
+ // should be true for spinning disks but false for SSDs or other
+ // flash-based drives.
+ optional bool has_seek_penalty = 1;
+ }
+ // The drive that the application executable was loaded from.
+ optional Drive app_drive = 16;
+ // The drive that the current user data directory was loaded from.
+ optional Drive user_data_drive = 17;
+ }
+ optional Hardware hardware = 6;
+
+ // Information about the network connection.
+ message Network {
+ // Set to true if connection_type changed during the lifetime of the log.
+ optional bool connection_type_is_ambiguous = 1;
+
+ // See net::NetworkChangeNotifier::ConnectionType.
+ enum ConnectionType {
+ CONNECTION_UNKNOWN = 0;
+ CONNECTION_ETHERNET = 1;
+ CONNECTION_WIFI = 2;
+ CONNECTION_2G = 3;
+ CONNECTION_3G = 4;
+ CONNECTION_4G = 5;
+ CONNECTION_BLUETOOTH = 6;
+ }
+ // The connection type according to NetworkChangeNotifier.
+ optional ConnectionType connection_type = 2;
+
+ // Set to true if wifi_phy_layer_protocol changed during the lifetime of the log.
+ optional bool wifi_phy_layer_protocol_is_ambiguous = 3;
+
+ // See net::WifiPHYLayerProtocol.
+ enum WifiPHYLayerProtocol {
+ WIFI_PHY_LAYER_PROTOCOL_NONE = 0;
+ WIFI_PHY_LAYER_PROTOCOL_ANCIENT = 1;
+ WIFI_PHY_LAYER_PROTOCOL_A = 2;
+ WIFI_PHY_LAYER_PROTOCOL_B = 3;
+ WIFI_PHY_LAYER_PROTOCOL_G = 4;
+ WIFI_PHY_LAYER_PROTOCOL_N = 5;
+ WIFI_PHY_LAYER_PROTOCOL_UNKNOWN = 6;
+ }
+ // The physical layer mode of the associated wifi access point, if any.
+ optional WifiPHYLayerProtocol wifi_phy_layer_protocol = 4;
+
+ // Describe wifi access point information.
+ message WifiAccessPoint {
+ // Vendor prefix of the access point's BSSID, these are OUIs
+ // (Organizationally Unique Identifiers) registered by
+ // the IEEE and are encoded with the first byte in bits 16-23, the
+ // second byte in bits 8-15 and the third byte in bits 0-7.
+ optional uint32 vendor_prefix = 1;
+
+ // Access point seurity mode definitions.
+ enum SecurityMode {
+ SECURITY_UNKNOWN = 0;
+ SECURITY_WPA = 1;
+ SECURITY_WEP = 2;
+ SECURITY_RSN = 3;
+ SECURITY_802_1X = 4;
+ SECURITY_PSK = 5;
+ SECURITY_NONE = 6;
+ }
+ // The security mode of the access point.
+ optional SecurityMode security_mode = 2;
+
+ // Vendor specific information.
+ message VendorInformation {
+ // The model number, for example "0".
+ optional string model_number = 1;
+
+ // The model name (sometimes the same as the model_number),
+ // for example "WZR-HP-AG300H".
+ optional string model_name = 2;
+
+ // The device name (sometimes the same as the model_number),
+ // for example "Dummynet"
+ optional string device_name = 3;
+
+ // The list of vendor-specific OUIs (Organziationally Unqiue
+ // Identifiers). These are provided by the vendor through WPS
+ // (Wireless Provisioning Service) information elements, which
+ // identifies the content of the element.
+ repeated uint32 element_identifier = 4;
+ }
+ // The wireless access point vendor information.
+ optional VendorInformation vendor_info = 3;
+ }
+ // Information of the wireless AP that device is connected to.
+ optional WifiAccessPoint access_point_info = 5;
+ }
+ optional Network network = 13;
+
+ // Information on the Google Update install that is managing this client.
+ message GoogleUpdate {
+ // Whether the Google Update install is system-level or user-level.
+ optional bool is_system_install = 1;
+
+ // The date at which Google Update last started performing an automatic
+ // update check, in seconds since the Unix epoch.
+ optional int64 last_automatic_start_timestamp = 2;
+
+ // The date at which Google Update last successfully sent an update check
+ // and recieved an intact response from the server, in seconds since the
+ // Unix epoch. (The updates don't need to be successfully installed.)
+ optional int64 last_update_check_timestamp = 3;
+
+ // Describes a product being managed by Google Update. (This can also
+ // describe Google Update itself.)
+ message ProductInfo {
+ // The current version of the product that is installed.
+ optional string version = 1;
+
+ // The date at which Google Update successfully updated this product,
+ // stored in seconds since the Unix epoch. This is updated when an update
+ // is successfully applied, or if the server reports that no update
+ // is available.
+ optional int64 last_update_success_timestamp = 2;
+
+ // The result reported by the product updater on its last run.
+ enum InstallResult {
+ INSTALL_RESULT_SUCCESS = 0;
+ INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1;
+ INSTALL_RESULT_FAILED_MSI_ERROR = 2;
+ INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3;
+ INSTALL_RESULT_EXIT_CODE = 4;
+ }
+ optional InstallResult last_result = 3;
+
+ // The error code reported by the product updater on its last run. This
+ // will typically be a error code specific to the product installer.
+ optional int32 last_error = 4;
+
+ // The extra error code reported by the product updater on its last run.
+ // This will typically be a Win32 error code.
+ optional int32 last_extra_error = 5;
+ }
+ optional ProductInfo google_update_status = 4;
+ optional ProductInfo client_status = 5;
+ }
+ optional GoogleUpdate google_update = 11;
+
+ // Information on all installed plugins.
+ message Plugin {
+ // The plugin's self-reported name and filename (without path).
+ optional string name = 1;
+ optional string filename = 2;
+
+ // The plugin's version.
+ optional string version = 3;
+
+ // True if the plugin is disabled.
+ // If a client has multiple local Chrome user accounts, this is logged based
+ // on the first user account launched during the current session.
+ optional bool is_disabled = 4;
+
+ // True if the plugin is PPAPI.
+ optional bool is_pepper = 5;
+ }
+ repeated Plugin plugin = 7;
+
+ // Figures that can be used to generate application stability metrics.
+ // All values are counts of events since the last time that these
+ // values were reported.
+ // Next tag: 24
+ message Stability {
+ // Total amount of time that the program was running, in seconds,
+ // since the last time a log was recorded, as measured using a client-side
+ // clock implemented via TimeTicks, which guarantees that it is monotonic
+ // and does not jump if the user changes his/her clock. The TimeTicks
+ // implementation also makes the clock not count time the computer is
+ // suspended.
+ optional int64 incremental_uptime_sec = 1;
+
+ // Total amount of time that the program was running, in seconds,
+ // since startup, as measured using a client-side clock implemented
+ // via TimeTicks, which guarantees that it is monotonic and does not
+ // jump if the user changes his/her clock. The TimeTicks implementation
+ // also makes the clock not count time the computer is suspended.
+ // This field was added for M-35.
+ optional int64 uptime_sec = 23;
+
+ // Page loads along with renderer crashes and hangs, since page load count
+ // roughly corresponds to usage.
+ optional int32 page_load_count = 2;
+ optional int32 renderer_crash_count = 3;
+ optional int32 renderer_hang_count = 4;
+
+ // Number of renderer crashes that were for extensions. These crashes are
+ // not counted in renderer_crash_count.
+ optional int32 extension_renderer_crash_count = 5;
+
+ // Number of non-renderer child process crashes.
+ optional int32 child_process_crash_count = 6;
+
+ // Number of times the browser has crashed while logged in as the "other
+ // user" (guest) account.
+ // Logged on ChromeOS only.
+ optional int32 other_user_crash_count = 7;
+
+ // Number of times the kernel has crashed.
+ // Logged on ChromeOS only.
+ optional int32 kernel_crash_count = 8;
+
+ // Number of times the system has shut down uncleanly.
+ // Logged on ChromeOS only.
+ optional int32 unclean_system_shutdown_count = 9;
+
+ //
+ // All the remaining fields in the Stability are recorded at most once per
+ // client session.
+ //
+
+ // The number of times the program was launched.
+ // This will typically be equal to 1. However, it is possible that Chrome
+ // was unable to upload stability metrics for previous launches (e.g. due to
+ // crashing early during startup), and hence this value might be greater
+ // than 1.
+ optional int32 launch_count = 15;
+ // The number of times that it didn't exit cleanly (which we assume to be
+ // mostly crashes).
+ optional int32 crash_count = 16;
+
+ // The number of times the program began, but did not complete, the shutdown
+ // process. (For example, this may occur when Windows is shutting down, and
+ // it only gives the process a few seconds to clean up.)
+ optional int32 incomplete_shutdown_count = 17;
+
+ // The number of times the program was able register with breakpad crash
+ // services.
+ optional int32 breakpad_registration_success_count = 18;
+
+ // The number of times the program failed to register with breakpad crash
+ // services. If crash registration fails then when the program crashes no
+ // crash report will be generated.
+ optional int32 breakpad_registration_failure_count = 19;
+
+ // The number of times the program has run under a debugger. This should
+ // be an exceptional condition. Running under a debugger prevents crash
+ // dumps from being generated.
+ optional int32 debugger_present_count = 20;
+
+ // The number of times the program has run without a debugger attached.
+ // This should be most common scenario and should be very close to
+ // |launch_count|.
+ optional int32 debugger_not_present_count = 21;
+
+ // Stability information for all installed plugins.
+ message PluginStability {
+ // The relevant plugin's information (name, etc.)
+ optional Plugin plugin = 1;
+
+ // The number of times this plugin's process was launched.
+ optional int32 launch_count = 2;
+
+ // The number of times this plugin was instantiated on a web page.
+ // This will be >= |launch_count|.
+ // (A page load with multiple sections drawn by this plugin will
+ // increase this count multiple times.)
+ optional int32 instance_count = 3;
+
+ // The number of times this plugin process crashed.
+ // This value will be <= |launch_count|.
+ optional int32 crash_count = 4;
+
+ // The number of times this plugin could not be loaded.
+ optional int32 loading_error_count = 5;
+ }
+ repeated PluginStability plugin_stability = 22;
+ }
+ optional Stability stability = 8;
+
+ // Description of a field trial or experiment that the user is currently
+ // enrolled in.
+ // All metrics reported in this upload can potentially be influenced by the
+ // field trial.
+ message FieldTrial {
+ // The name of the field trial, as a 32-bit identifier.
+ // Currently, the identifier is a hash of the field trial's name.
+ optional fixed32 name_id = 1;
+
+ // The user's group within the field trial, as a 32-bit identifier.
+ // Currently, the identifier is a hash of the group's name.
+ optional fixed32 group_id = 2;
+ }
+ repeated FieldTrial field_trial = 9;
+
+ // Information about the A/V output device(s) (typically just a TV).
+ // However, a configuration may have one or more intermediate A/V devices
+ // between the source device and the TV (e.g. an A/V receiver, video
+ // processor, etc.).
+ message ExternalAudioVideoDevice {
+ // The manufacturer name (possibly encoded as a 3-letter code, e.g. "YMH"
+ // for Yamaha).
+ optional string manufacturer_name = 1;
+
+ // The model name (e.g. "RX-V1900"). Some devices may report generic names
+ // like "receiver" or use the full manufacturer name (e.g "PHILIPS").
+ optional string model_name = 2;
+
+ // The product code (e.g. "0218").
+ optional string product_code = 3;
+
+ // The device types. A single device can have multiple types (e.g. a set-top
+ // box could be both a tuner and a player). The same type may even be
+ // repeated (e.g a device that reports two tuners).
+ enum AVDeviceType {
+ AV_DEVICE_TYPE_UNKNOWN = 0;
+ AV_DEVICE_TYPE_TV = 1;
+ AV_DEVICE_TYPE_RECORDER = 2;
+ AV_DEVICE_TYPE_TUNER = 3;
+ AV_DEVICE_TYPE_PLAYER = 4;
+ AV_DEVICE_TYPE_AUDIO_SYSTEM = 5;
+ }
+ repeated AVDeviceType av_device_type = 4;
+
+ // The year of manufacture.
+ optional int32 manufacture_year = 5;
+
+ // The week of manufacture.
+ // Note: per the Wikipedia EDID article, numbering for this field may not
+ // be consistent between manufacturers.
+ optional int32 manufacture_week = 6;
+
+ // Max horizontal resolution in pixels.
+ optional int32 horizontal_resolution = 7;
+
+ // Max vertical resolution in pixels.
+ optional int32 vertical_resolution = 8;
+
+ // Audio capabilities of the device.
+ // Ref: http://en.wikipedia.org/wiki/Extended_display_identification_data
+ message AudioDescription {
+ // Audio format
+ enum AudioFormat {
+ AUDIO_FORMAT_UNKNOWN = 0;
+ AUDIO_FORMAT_LPCM = 1;
+ AUDIO_FORMAT_AC_3 = 2;
+ AUDIO_FORMAT_MPEG1 = 3;
+ AUDIO_FORMAT_MP3 = 4;
+ AUDIO_FORMAT_MPEG2 = 5;
+ AUDIO_FORMAT_AAC = 6;
+ AUDIO_FORMAT_DTS = 7;
+ AUDIO_FORMAT_ATRAC = 8;
+ AUDIO_FORMAT_ONE_BIT = 9;
+ AUDIO_FORMAT_DD_PLUS = 10;
+ AUDIO_FORMAT_DTS_HD = 11;
+ AUDIO_FORMAT_MLP_DOLBY_TRUEHD = 12;
+ AUDIO_FORMAT_DST_AUDIO = 13;
+ AUDIO_FORMAT_MICROSOFT_WMA_PRO = 14;
+ }
+ optional AudioFormat audio_format = 1;
+
+ // Number of channels (e.g. 1, 2, 8, etc.).
+ optional int32 num_channels = 2;
+
+ // Supported sample frequencies in Hz (e.g. 32000, 44100, etc.).
+ // Multiple frequencies may be specified.
+ repeated int32 sample_frequency_hz = 3;
+
+ // Maximum bit rate in bits/s.
+ optional int32 max_bit_rate_per_second = 4;
+
+ // Bit depth (e.g. 16, 20, 24, etc.).
+ optional int32 bit_depth = 5;
+ }
+ repeated AudioDescription audio_description = 9;
+
+ // The position in AV setup.
+ // A value of 0 means this device is the TV.
+ // A value of 1 means this device is directly connected to one of
+ // the TV's inputs.
+ // Values > 1 indicate there are 1 or more devices between this device
+ // and the TV.
+ optional int32 position_in_setup = 10;
+
+ // Whether this device is in the path to the TV.
+ optional bool is_in_path_to_tv = 11;
+
+ // The CEC version the device supports.
+ // CEC stands for Consumer Electronics Control, a part of the HDMI
+ // specification. Not all HDMI devices support CEC.
+ // Only devices that support CEC will report a value here.
+ optional int32 cec_version = 12;
+
+ // This message reports CEC commands seen by a device.
+ // After each log is sent, this information is cleared and gathered again.
+ // By collecting CEC status information by opcode we can determine
+ // which CEC features can be supported.
+ message CECCommand {
+ // The CEC command opcode. CEC supports up to 256 opcodes.
+ // We add only one CECCommand message per unique opcode. Only opcodes
+ // seen by the device will be reported. The remainder of the message
+ // accumulates status for this opcode (and device).
+ optional int32 opcode = 1;
+
+ // The total number of commands received from the external device.
+ optional int32 num_received_direct = 2;
+
+ // The number of commands received from the external device as part of a
+ // broadcast message.
+ optional int32 num_received_broadcast = 3;
+
+ // The total number of commands sent to the external device.
+ optional int32 num_sent_direct = 4;
+
+ // The number of commands sent to the external device as part of a
+ // broadcast message.
+ optional int32 num_sent_broadcast = 5;
+
+ // The number of aborted commands for unknown reasons.
+ optional int32 num_aborted_unknown_reason = 6;
+
+ // The number of aborted commands because of an unrecognized opcode.
+ optional int32 num_aborted_unrecognized = 7;
+ }
+ repeated CECCommand cec_command = 13;
+ }
+ repeated ExternalAudioVideoDevice external_audio_video_device = 14;
+
+ // Information about the current wireless access point. Collected directly
+ // from the wireless access point via standard apis if the device is
+ // connected to the Internet wirelessly. Introduced for Chrome on TV devices
+ // but also can be collected by ChromeOS, Android or other clients.
+ message ExternalAccessPoint {
+ // The manufacturer name, for example "ASUSTeK Computer Inc.".
+ optional string manufacturer = 1;
+
+ // The model name, for example "Wi-Fi Protected Setup Router".
+ optional string model_name = 2;
+
+ // The model number, for example "RT-N16".
+ optional string model_number = 3;
+
+ // The device name (sometime same as model_number), for example "RT-N16".
+ optional string device_name = 4;
+ }
+ optional ExternalAccessPoint external_access_point = 15;
+
+ // Number of users currently signed into a multiprofile session.
+ // A zero value indicates that the user count changed while the log is open.
+ // Logged only on ChromeOS.
+ optional uint32 multi_profile_user_count = 17;
+
+ // Information about extensions that are installed, masked to provide better
+ // privacy. Only extensions from a single profile are reported; this will
+ // generally be the profile used when the browser is started. The profile
+ // reported on will remain consistent at least until the browser is
+ // relaunched (or the profile is deleted by the user).
+ //
+ // Each client first picks a value for client_key derived from its UMA
+ // client_id:
+ // client_key = client_id % 4096
+ // Then, each installed extension is mapped into a hash bucket according to
+ // bucket = CityHash64(StringPrintf("%d:%s",
+ // client_key, extension_id)) % 1024
+ // The client reports the set of hash buckets occupied by all installed
+ // extensions. If multiple extensions map to the same bucket, that bucket is
+ // still only reported once.
+ repeated int32 occupied_extension_bucket = 18;
+
+ // The state of loaded extensions for this system. The system can have either
+ // no applicable extensions, extensions only from the webstore and verified by
+ // the webstore, extensions only from the webstore but not verified, or
+ // extensions not from the store. If there is a single off-store extension,
+ // then HAS_OFFSTORE is reported. This should be kept in sync with the
+ // corresponding enum in chrome/browser/metrics/extensions_metrics_provider.cc
+ enum ExtensionsState {
+ NO_EXTENSIONS = 0;
+ NO_OFFSTORE_VERIFIED = 1;
+ NO_OFFSTORE_UNVERIFIED = 2;
+ HAS_OFFSTORE = 3;
+ }
+ optional ExtensionsState offstore_extensions_state = 19;
+}
diff --git a/metrics/uploader/proto/user_action_event.proto b/metrics/uploader/proto/user_action_event.proto
new file mode 100644
index 0000000..30a9318
--- /dev/null
+++ b/metrics/uploader/proto/user_action_event.proto
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an event that occurs in response to a user action,
+// e.g. an interaction with a browser UI element.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "UserActionEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 3
+message UserActionEventProto {
+ // The name of the action, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The timestamp for the event, in seconds since the epoch.
+ optional int64 time = 2;
+}
diff --git a/metrics/uploader/sender.h b/metrics/uploader/sender.h
new file mode 100644
index 0000000..5211834
--- /dev/null
+++ b/metrics/uploader/sender.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_SENDER_H_
+#define METRICS_UPLOADER_SENDER_H_
+
+#include <string>
+
+// Abstract class for a Sender that uploads a metrics message.
+class Sender {
+ public:
+ virtual ~Sender() {}
+ // Sends a message |content| with its sha1 hash |hash|
+ virtual bool Send(const std::string& content, const std::string& hash) = 0;
+};
+
+#endif // METRICS_UPLOADER_SENDER_H_
diff --git a/metrics/uploader/sender_http.cc b/metrics/uploader/sender_http.cc
new file mode 100644
index 0000000..8488b66
--- /dev/null
+++ b/metrics/uploader/sender_http.cc
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/uploader/sender_http.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <chromeos/http/http_utils.h>
+#include <chromeos/mime_utils.h>
+
+HttpSender::HttpSender(const std::string server_url)
+ : server_url_(server_url) {}
+
+bool HttpSender::Send(const std::string& content,
+ const std::string& content_hash) {
+ const std::string hash =
+ base::HexEncode(content_hash.data(), content_hash.size());
+
+ chromeos::http::HeaderList headers = {{"X-Chrome-UMA-Log-SHA1", hash}};
+ chromeos::ErrorPtr error;
+ auto response = chromeos::http::PostTextAndBlock(
+ server_url_,
+ content,
+ chromeos::mime::application::kWwwFormUrlEncoded,
+ headers,
+ chromeos::http::Transport::CreateDefault(),
+ &error);
+ if (!response || response->ExtractDataAsString() != "OK") {
+ if (error) {
+ DLOG(ERROR) << "Failed to send data: " << error->GetMessage();
+ }
+ return false;
+ }
+ return true;
+}
diff --git a/metrics/uploader/sender_http.h b/metrics/uploader/sender_http.h
new file mode 100644
index 0000000..4880b28
--- /dev/null
+++ b/metrics/uploader/sender_http.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_SENDER_HTTP_H_
+#define METRICS_UPLOADER_SENDER_HTTP_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "metrics/uploader/sender.h"
+
+// Sender implemented using http_utils from libchromeos
+class HttpSender : public Sender {
+ public:
+ explicit HttpSender(std::string server_url);
+ ~HttpSender() override = default;
+ // Sends |content| whose SHA1 hash is |hash| to server_url with a synchronous
+ // POST request to server_url.
+ bool Send(const std::string& content, const std::string& hash) override;
+
+ private:
+ const std::string server_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpSender);
+};
+
+#endif // METRICS_UPLOADER_SENDER_HTTP_H_
diff --git a/metrics/uploader/system_profile_cache.cc b/metrics/uploader/system_profile_cache.cc
new file mode 100644
index 0000000..ea4a38c
--- /dev/null
+++ b/metrics/uploader/system_profile_cache.cc
@@ -0,0 +1,238 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/uploader/system_profile_cache.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/guid.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/sys_info.h"
+#include "metrics/persistent_integer.h"
+#include "metrics/uploader/metrics_log_base.h"
+#include "metrics/uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "vboot/crossystem.h"
+
+namespace {
+
+const char kPersistentGUIDFile[] = "/var/lib/metrics/Sysinfo.GUID";
+const char kPersistentSessionIdFilename[] = "Sysinfo.SessionId";
+const char kProductIdFieldName[] = "GOOGLE_METRICS_PRODUCT_ID";
+
+} // namespace
+
+std::string ChannelToString(
+ const metrics::SystemProfileProto_Channel& channel) {
+ switch (channel) {
+ case metrics::SystemProfileProto::CHANNEL_STABLE:
+ return "STABLE";
+ case metrics::SystemProfileProto::CHANNEL_DEV:
+ return "DEV";
+ case metrics::SystemProfileProto::CHANNEL_BETA:
+ return "BETA";
+ case metrics::SystemProfileProto::CHANNEL_CANARY:
+ return "CANARY";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+SystemProfileCache::SystemProfileCache()
+ : initialized_(false),
+ testing_(false),
+ config_root_("/"),
+ session_id_(new chromeos_metrics::PersistentInteger(
+ kPersistentSessionIdFilename)) {
+}
+
+SystemProfileCache::SystemProfileCache(bool testing,
+ const std::string& config_root)
+ : initialized_(false),
+ testing_(testing),
+ config_root_(config_root),
+ session_id_(new chromeos_metrics::PersistentInteger(
+ kPersistentSessionIdFilename)) {
+}
+
+bool SystemProfileCache::Initialize() {
+ CHECK(!initialized_)
+ << "this should be called only once in the metrics_daemon lifetime.";
+
+ std::string chromeos_version;
+ std::string board;
+ std::string build_type;
+ if (!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_NAME",
+ &profile_.os_name) ||
+ !base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_VERSION",
+ &profile_.os_version) ||
+ !base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BOARD", &board) ||
+ !base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BUILD_TYPE",
+ &build_type) ||
+ !GetChromeOSVersion(&chromeos_version) ||
+ !GetHardwareId(&profile_.hardware_class)) {
+ DLOG(ERROR) << "failing to initialize profile cache";
+ return false;
+ }
+
+ std::string channel_string;
+ base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_TRACK", &channel_string);
+ profile_.channel = ProtoChannelFromString(channel_string);
+
+ profile_.app_version = chromeos_version + " (" + build_type + ")" +
+ ChannelToString(profile_.channel) + " " + board;
+
+ // If the product id is not defined, use the default one from the protobuf.
+ profile_.product_id = metrics::ChromeUserMetricsExtension::CHROME;
+ if (GetProductId(&profile_.product_id)) {
+ DLOG(INFO) << "Set the product id to " << profile_.product_id;
+ }
+
+ profile_.client_id =
+ testing_ ? "client_id_test" : GetPersistentGUID(kPersistentGUIDFile);
+
+ // Increment the session_id everytime we initialize this. If metrics_daemon
+ // does not crash, this should correspond to the number of reboots of the
+ // system.
+ // TODO(bsimonnet): Change this to map to the number of time system-services
+ // is started.
+ session_id_->Add(1);
+ profile_.session_id = static_cast<int32_t>(session_id_->Get());
+
+ initialized_ = true;
+ return initialized_;
+}
+
+bool SystemProfileCache::InitializeOrCheck() {
+ return initialized_ || Initialize();
+}
+
+void SystemProfileCache::Populate(
+ metrics::ChromeUserMetricsExtension* metrics_proto) {
+ CHECK(metrics_proto);
+ CHECK(InitializeOrCheck())
+ << "failed to initialize system information.";
+
+ // The client id is hashed before being sent.
+ metrics_proto->set_client_id(
+ metrics::MetricsLogBase::Hash(profile_.client_id));
+ metrics_proto->set_session_id(profile_.session_id);
+
+ // Sets the product id.
+ metrics_proto->set_product(profile_.product_id);
+
+ metrics::SystemProfileProto* profile_proto =
+ metrics_proto->mutable_system_profile();
+ profile_proto->mutable_hardware()->set_hardware_class(
+ profile_.hardware_class);
+ profile_proto->set_app_version(profile_.app_version);
+ profile_proto->set_channel(profile_.channel);
+
+ metrics::SystemProfileProto_OS* os = profile_proto->mutable_os();
+ os->set_name(profile_.os_name);
+ os->set_version(profile_.os_version);
+}
+
+std::string SystemProfileCache::GetPersistentGUID(const std::string& filename) {
+ std::string guid;
+ base::FilePath filepath(filename);
+ if (!base::ReadFileToString(filepath, &guid)) {
+ guid = base::GenerateGUID();
+ // If we can't read or write the file, the guid will not be preserved during
+ // the next reboot. Crash.
+ CHECK(base::WriteFile(filepath, guid.c_str(), guid.size()));
+ }
+ return guid;
+}
+
+bool SystemProfileCache::GetChromeOSVersion(std::string* version) {
+ if (testing_) {
+ *version = "0.0.0.0";
+ return true;
+ }
+
+ std::string milestone, build, branch, patch;
+ unsigned tmp;
+ if (base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_CHROME_MILESTONE",
+ &milestone) &&
+ base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BUILD_NUMBER",
+ &build) &&
+ base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BRANCH_NUMBER",
+ &branch) &&
+ base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_PATCH_NUMBER",
+ &patch)) {
+ // Convert to uint to ensure those fields are positive numbers.
+ if (base::StringToUint(milestone, &tmp) &&
+ base::StringToUint(build, &tmp) &&
+ base::StringToUint(branch, &tmp) &&
+ base::StringToUint(patch, &tmp)) {
+ std::vector<std::string> parts = {milestone, build, branch, patch};
+ *version = JoinString(parts, '.');
+ return true;
+ }
+ DLOG(INFO) << "The milestone, build, branch or patch is not a positive "
+ << "number.";
+ return false;
+ }
+ DLOG(INFO) << "Field missing from /etc/lsb-release";
+ return false;
+}
+
+bool SystemProfileCache::GetHardwareId(std::string* hwid) {
+ CHECK(hwid);
+
+ if (testing_) {
+ // if we are in test mode, we do not call crossystem directly.
+ DLOG(INFO) << "skipping hardware id";
+ *hwid = "";
+ return true;
+ }
+
+ char buffer[128];
+ if (buffer != VbGetSystemPropertyString("hwid", buffer, sizeof(buffer))) {
+ LOG(ERROR) << "error getting hwid";
+ return false;
+ }
+
+ *hwid = std::string(buffer);
+ return true;
+}
+
+bool SystemProfileCache::GetProductId(int* product_id) const {
+ chromeos::OsReleaseReader reader;
+ if (testing_) {
+ base::FilePath root(config_root_);
+ reader.LoadTestingOnly(root);
+ } else {
+ reader.Load();
+ }
+
+ std::string id;
+ if (reader.GetString(kProductIdFieldName, &id)) {
+ CHECK(base::StringToInt(id, product_id)) << "Failed to convert product_id "
+ << id << " to int.";
+ return true;
+ }
+ return false;
+}
+
+metrics::SystemProfileProto_Channel SystemProfileCache::ProtoChannelFromString(
+ const std::string& channel) {
+
+ if (channel == "stable-channel") {
+ return metrics::SystemProfileProto::CHANNEL_STABLE;
+ } else if (channel == "dev-channel") {
+ return metrics::SystemProfileProto::CHANNEL_DEV;
+ } else if (channel == "beta-channel") {
+ return metrics::SystemProfileProto::CHANNEL_BETA;
+ } else if (channel == "canary-channel") {
+ return metrics::SystemProfileProto::CHANNEL_CANARY;
+ }
+
+ DLOG(INFO) << "unknown channel: " << channel;
+ return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
+}
diff --git a/metrics/uploader/system_profile_cache.h b/metrics/uploader/system_profile_cache.h
new file mode 100644
index 0000000..e7a7337
--- /dev/null
+++ b/metrics/uploader/system_profile_cache.h
@@ -0,0 +1,91 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/osrelease_reader.h"
+#include "metrics/persistent_integer.h"
+#include "metrics/uploader/proto/system_profile.pb.h"
+#include "metrics/uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+struct SystemProfile {
+ std::string os_name;
+ std::string os_version;
+ metrics::SystemProfileProto::Channel channel;
+ std::string app_version;
+ std::string hardware_class;
+ std::string client_id;
+ int32_t session_id;
+ int32_t product_id;
+};
+
+// Retrieves general system informations needed by the protobuf for context and
+// remembers them to avoid expensive calls.
+//
+// The cache is populated lazily. The only method needed is Populate.
+class SystemProfileCache : public SystemProfileSetter {
+ public:
+ SystemProfileCache();
+
+ SystemProfileCache(bool testing, const std::string& config_root);
+
+ // Populates the ProfileSystem protobuf with system information.
+ void Populate(metrics::ChromeUserMetricsExtension* profile_proto) override;
+
+ // Converts a string representation of the channel (|channel|-channel) to a
+ // SystemProfileProto_Channel
+ static metrics::SystemProfileProto_Channel ProtoChannelFromString(
+ const std::string& channel);
+
+ // Gets the persistent GUID and create it if it has not been created yet.
+ static std::string GetPersistentGUID(const std::string& filename);
+
+ private:
+ friend class UploadServiceTest;
+ FRIEND_TEST(UploadServiceTest, ExtractChannelFromDescription);
+ FRIEND_TEST(UploadServiceTest, ReadKeyValueFromFile);
+ FRIEND_TEST(UploadServiceTest, SessionIdIncrementedAtInitialization);
+ FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+
+ // Fetches all informations and populates |profile_|
+ bool Initialize();
+
+ // Initializes |profile_| only if it has not been yet initialized.
+ bool InitializeOrCheck();
+
+ // Gets the hardware ID using crossystem
+ bool GetHardwareId(std::string* hwid);
+
+ // Gets the product ID from the GOOGLE_METRICS_PRODUCT_ID field.
+ bool GetProductId(int* product_id) const;
+
+ // Generate the formatted chromeos version from the fields in
+ // /etc/lsb-release. The format is A.B.C.D where A, B, C and D are positive
+ // integer representing:
+ // * the chrome milestone
+ // * the build number
+ // * the branch number
+ // * the patch number
+ bool GetChromeOSVersion(std::string* version);
+
+ bool initialized_;
+ bool testing_;
+ std::string config_root_;
+ scoped_ptr<chromeos_metrics::PersistentInteger> session_id_;
+ SystemProfile profile_;
+};
+
+#endif // METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
diff --git a/metrics/uploader/system_profile_setter.h b/metrics/uploader/system_profile_setter.h
new file mode 100644
index 0000000..c535664
--- /dev/null
+++ b/metrics/uploader/system_profile_setter.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+// Abstract class used to delegate populating SystemProfileProto with system
+// information to simplify testing.
+class SystemProfileSetter {
+ public:
+ virtual ~SystemProfileSetter() {}
+ // Populates the protobuf with system informations.
+ virtual void Populate(metrics::ChromeUserMetricsExtension* profile_proto) = 0;
+};
+
+#endif // METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
diff --git a/metrics/uploader/upload_service.cc b/metrics/uploader/upload_service.cc
new file mode 100644
index 0000000..92c9e10
--- /dev/null
+++ b/metrics/uploader/upload_service.cc
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/uploader/upload_service.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/memory/scoped_vector.h>
+#include <base/message_loop/message_loop.h>
+#include <base/metrics/histogram.h>
+#include <base/metrics/histogram_base.h>
+#include <base/metrics/histogram_snapshot_manager.h>
+#include <base/metrics/sparse_histogram.h>
+#include <base/metrics/statistics_recorder.h>
+#include <base/sha1.h>
+
+#include "metrics/serialization/metric_sample.h"
+#include "metrics/serialization/serialization_utils.h"
+#include "metrics/uploader/metrics_log.h"
+#include "metrics/uploader/sender_http.h"
+#include "metrics/uploader/system_profile_cache.h"
+
+const int UploadService::kMaxFailedUpload = 10;
+
+UploadService::UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server)
+ : system_profile_setter_(setter),
+ metrics_lib_(metrics_lib),
+ histogram_snapshot_manager_(this),
+ sender_(new HttpSender(server)),
+ testing_(false) {
+}
+
+UploadService::UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server,
+ bool testing)
+ : UploadService(setter, metrics_lib, server) {
+ testing_ = testing;
+}
+
+void UploadService::Init(const base::TimeDelta& upload_interval,
+ const std::string& metrics_file) {
+ base::StatisticsRecorder::Initialize();
+ metrics_file_ = metrics_file;
+
+ if (!testing_) {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback,
+ base::Unretained(this),
+ upload_interval),
+ upload_interval);
+ }
+}
+
+void UploadService::StartNewLog() {
+ CHECK(!staged_log_) << "the staged log should be discarded before starting "
+ "a new metrics log";
+ MetricsLog* log = new MetricsLog();
+ log->PopulateSystemProfile(system_profile_setter_.get());
+ current_log_.reset(log);
+}
+
+void UploadService::UploadEventCallback(const base::TimeDelta& interval) {
+ UploadEvent();
+
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback,
+ base::Unretained(this),
+ interval),
+ interval);
+}
+
+void UploadService::UploadEvent() {
+ if (staged_log_) {
+ // Previous upload failed, retry sending the logs.
+ SendStagedLog();
+ return;
+ }
+
+ // Previous upload successful, reading metrics sample from the file.
+ ReadMetrics();
+ GatherHistograms();
+
+ // No samples found. Exit to avoid sending an empty log.
+ if (!current_log_)
+ return;
+
+ StageCurrentLog();
+ SendStagedLog();
+}
+
+void UploadService::SendStagedLog() {
+ CHECK(staged_log_) << "staged_log_ must exist to be sent";
+
+ // If metrics are not enabled, discard the log and exit.
+ if (!metrics_lib_->AreMetricsEnabled()) {
+ LOG(INFO) << "Metrics disabled. Don't upload metrics samples.";
+ staged_log_.reset();
+ return;
+ }
+
+ std::string log_text;
+ staged_log_->GetEncodedLog(&log_text);
+ if (!sender_->Send(log_text, base::SHA1HashString(log_text))) {
+ ++failed_upload_count_;
+ if (failed_upload_count_ <= kMaxFailedUpload) {
+ LOG(WARNING) << "log upload failed " << failed_upload_count_
+ << " times. It will be retried later.";
+ return;
+ }
+ LOG(WARNING) << "log failed more than " << kMaxFailedUpload << " times.";
+ } else {
+ LOG(INFO) << "uploaded " << log_text.length() << " bytes";
+ }
+ // Discard staged log.
+ staged_log_.reset();
+}
+
+void UploadService::Reset() {
+ staged_log_.reset();
+ current_log_.reset();
+ failed_upload_count_ = 0;
+}
+
+void UploadService::ReadMetrics() {
+ CHECK(!staged_log_)
+ << "cannot read metrics until the old logs have been discarded";
+
+ ScopedVector<metrics::MetricSample> vector;
+ metrics::SerializationUtils::ReadAndTruncateMetricsFromFile(
+ metrics_file_, &vector);
+
+ int i = 0;
+ for (ScopedVector<metrics::MetricSample>::iterator it = vector.begin();
+ it != vector.end(); it++) {
+ metrics::MetricSample* sample = *it;
+ AddSample(*sample);
+ i++;
+ }
+ DLOG(INFO) << i << " samples read";
+}
+
+void UploadService::AddSample(const metrics::MetricSample& sample) {
+ base::HistogramBase* counter;
+ switch (sample.type()) {
+ case metrics::MetricSample::CRASH:
+ AddCrash(sample.name());
+ break;
+ case metrics::MetricSample::HISTOGRAM:
+ counter = base::Histogram::FactoryGet(
+ sample.name(), sample.min(), sample.max(), sample.bucket_count(),
+ base::Histogram::kUmaTargetedHistogramFlag);
+ counter->Add(sample.sample());
+ break;
+ case metrics::MetricSample::SPARSE_HISTOGRAM:
+ counter = base::SparseHistogram::FactoryGet(
+ sample.name(), base::HistogramBase::kUmaTargetedHistogramFlag);
+ counter->Add(sample.sample());
+ break;
+ case metrics::MetricSample::LINEAR_HISTOGRAM:
+ counter = base::LinearHistogram::FactoryGet(
+ sample.name(),
+ 1,
+ sample.max(),
+ sample.max() + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ counter->Add(sample.sample());
+ break;
+ case metrics::MetricSample::USER_ACTION:
+ GetOrCreateCurrentLog()->RecordUserAction(sample.name());
+ break;
+ default:
+ break;
+ }
+}
+
+void UploadService::AddCrash(const std::string& crash_name) {
+ if (crash_name == "user") {
+ GetOrCreateCurrentLog()->IncrementUserCrashCount();
+ } else if (crash_name == "kernel") {
+ GetOrCreateCurrentLog()->IncrementKernelCrashCount();
+ } else if (crash_name == "uncleanshutdown") {
+ GetOrCreateCurrentLog()->IncrementUncleanShutdownCount();
+ } else {
+ DLOG(ERROR) << "crash name unknown" << crash_name;
+ }
+}
+
+void UploadService::GatherHistograms() {
+ base::StatisticsRecorder::Histograms histograms;
+ base::StatisticsRecorder::GetHistograms(&histograms);
+
+ histogram_snapshot_manager_.PrepareDeltas(
+ base::Histogram::kNoFlags, base::Histogram::kUmaTargetedHistogramFlag);
+}
+
+void UploadService::RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) {
+ GetOrCreateCurrentLog()->RecordHistogramDelta(histogram.histogram_name(),
+ snapshot);
+}
+
+void UploadService::StageCurrentLog() {
+ CHECK(!staged_log_)
+ << "staged logs must be discarded before another log can be staged";
+
+ if (!current_log_) return;
+
+ staged_log_.swap(current_log_);
+ staged_log_->CloseLog();
+ failed_upload_count_ = 0;
+}
+
+MetricsLog* UploadService::GetOrCreateCurrentLog() {
+ if (!current_log_) {
+ StartNewLog();
+ }
+ return current_log_.get();
+}
diff --git a/metrics/uploader/upload_service.h b/metrics/uploader/upload_service.h
new file mode 100644
index 0000000..ebbb54f
--- /dev/null
+++ b/metrics/uploader/upload_service.h
@@ -0,0 +1,153 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_UPLOAD_SERVICE_H_
+#define METRICS_UPLOADER_UPLOAD_SERVICE_H_
+
+#include <string>
+
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+
+#include "metrics/metrics_library.h"
+#include "metrics/uploader/metrics_log.h"
+#include "metrics/uploader/sender.h"
+#include "metrics/uploader/system_profile_cache.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+class CrashSample;
+class HistogramSample;
+class LinearHistogramSample;
+class MetricSample;
+class SparseHistogramSample;
+class UserActionSample;
+}
+
+class SystemProfileSetter;
+
+// Service responsible for uploading the metrics periodically to the server.
+// This service works as a simple 2-state state-machine.
+//
+// The two states are the presence or not of a staged log.
+// A staged log is a compressed protobuffer containing both the aggregated
+// metrics and event and information about the client. (product, hardware id,
+// etc...).
+//
+// At regular intervals, the upload event will be triggered and the following
+// will happen:
+// * if a staged log is present:
+// The previous upload may have failed for various reason. We then retry to
+// upload the same log.
+// - if the upload is successful, we discard the log (therefore
+// transitioning back to no staged log)
+// - if the upload fails, we keep the log to try again later.
+// We do not try to read the metrics that are stored on
+// the disk as we want to avoid storing the metrics in memory.
+//
+// * if no staged logs are present:
+// Read all metrics from the disk, aggregate them and try to send them.
+// - if the upload succeeds, we discard the staged log (transitioning back
+// to the no staged log state)
+// - if the upload fails, we keep the staged log in memory to retry
+// uploading later.
+//
+class UploadService : public base::HistogramFlattener {
+ public:
+ explicit UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server);
+
+ void Init(const base::TimeDelta& upload_interval,
+ const std::string& metrics_file);
+
+ // Starts a new log. The log needs to be regenerated after each successful
+ // launch as it is destroyed when staging the log.
+ void StartNewLog();
+
+ // Event callback for handling MessageLoop events.
+ void UploadEventCallback(const base::TimeDelta& interval);
+
+ // Triggers an upload event.
+ void UploadEvent();
+
+ // Sends the staged log.
+ void SendStagedLog();
+
+ // Implements inconsistency detection to match HistogramFlattener's
+ // interface.
+ void InconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override {}
+ void UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override {}
+ void InconsistencyDetectedInLoggedCount(int amount) override {}
+
+ private:
+ friend class UploadServiceTest;
+
+ FRIEND_TEST(UploadServiceTest, CanSendMultipleTimes);
+ FRIEND_TEST(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload);
+ FRIEND_TEST(UploadServiceTest, EmptyLogsAreNotSent);
+ FRIEND_TEST(UploadServiceTest, FailedSendAreRetried);
+ FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+ FRIEND_TEST(UploadServiceTest, LogEmptyAfterUpload);
+ FRIEND_TEST(UploadServiceTest, LogEmptyByDefault);
+ FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+ FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+ FRIEND_TEST(UploadServiceTest, LogUserCrash);
+ FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+ FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+
+ // Private constructor for use in unit testing.
+ UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server,
+ bool testing);
+
+ // If a staged log fails to upload more than kMaxFailedUpload times, it
+ // will be discarded.
+ static const int kMaxFailedUpload;
+
+ // Resets the internal state.
+ void Reset();
+
+ // Reads all the metrics from the disk.
+ void ReadMetrics();
+
+ // Adds a generic sample to the current log.
+ void AddSample(const metrics::MetricSample& sample);
+
+ // Adds a crash to the current log.
+ void AddCrash(const std::string& crash_name);
+
+ // Aggregates all histogram available in memory and store them in the current
+ // log.
+ void GatherHistograms();
+
+ // Callback for HistogramSnapshotManager to store the histograms.
+ void RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) override;
+
+ // Compiles all the samples received into a single protobuf and adds all
+ // system information.
+ void StageCurrentLog();
+
+ // Returns the current log. If there is no current log, creates it first.
+ MetricsLog* GetOrCreateCurrentLog();
+
+ scoped_ptr<SystemProfileSetter> system_profile_setter_;
+ MetricsLibraryInterface* metrics_lib_;
+ base::HistogramSnapshotManager histogram_snapshot_manager_;
+ scoped_ptr<Sender> sender_;
+ int failed_upload_count_;
+ scoped_ptr<MetricsLog> current_log_;
+ scoped_ptr<MetricsLog> staged_log_;
+
+ std::string metrics_file_;
+
+ bool testing_;
+};
+
+#endif // METRICS_UPLOADER_UPLOAD_SERVICE_H_
diff --git a/metrics/uploader/upload_service_test.cc b/metrics/uploader/upload_service_test.cc
new file mode 100644
index 0000000..ee17e15
--- /dev/null
+++ b/metrics/uploader/upload_service_test.cc
@@ -0,0 +1,257 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "base/at_exit.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "metrics/metrics_library_mock.h"
+#include "metrics/serialization/metric_sample.h"
+#include "metrics/uploader/metrics_log.h"
+#include "metrics/uploader/mock/mock_system_profile_setter.h"
+#include "metrics/uploader/mock/sender_mock.h"
+#include "metrics/uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "metrics/uploader/proto/histogram_event.pb.h"
+#include "metrics/uploader/proto/system_profile.pb.h"
+#include "metrics/uploader/system_profile_cache.h"
+#include "metrics/uploader/upload_service.h"
+
+static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
+static const char kMetricsFilePath[] = "/var/run/metrics/uma-events";
+
+class UploadServiceTest : public testing::Test {
+ protected:
+ UploadServiceTest()
+ : cache_(true, "/"),
+ upload_service_(new MockSystemProfileSetter(), &metrics_lib_,
+ kMetricsServer, true),
+ exit_manager_(new base::AtExitManager()) {
+ sender_ = new SenderMock;
+ upload_service_.sender_.reset(sender_);
+ upload_service_.Init(base::TimeDelta::FromMinutes(30), kMetricsFilePath);
+ }
+
+ virtual void SetUp() {
+ CHECK(dir_.CreateUniqueTempDir());
+ upload_service_.GatherHistograms();
+ upload_service_.Reset();
+ sender_->Reset();
+
+ chromeos_metrics::PersistentInteger::SetTestingMode(true);
+ cache_.session_id_.reset(new chromeos_metrics::PersistentInteger(
+ dir_.path().Append("session_id").value()));
+ }
+
+ scoped_ptr<metrics::MetricSample> Crash(const std::string& name) {
+ return metrics::MetricSample::CrashSample(name);
+ }
+
+ base::ScopedTempDir dir_;
+ SenderMock* sender_;
+ SystemProfileCache cache_;
+ UploadService upload_service_;
+ MetricsLibraryMock metrics_lib_;
+
+ scoped_ptr<base::AtExitManager> exit_manager_;
+};
+
+// Tests that the right crash increments a values.
+TEST_F(UploadServiceTest, LogUserCrash) {
+ upload_service_.AddSample(*Crash("user").get());
+
+ MetricsLog* log = upload_service_.current_log_.get();
+ metrics::ChromeUserMetricsExtension* proto = log->uma_proto();
+
+ EXPECT_EQ(1, proto->system_profile().stability().other_user_crash_count());
+}
+
+TEST_F(UploadServiceTest, LogUncleanShutdown) {
+ upload_service_.AddSample(*Crash("uncleanshutdown"));
+
+ EXPECT_EQ(1, upload_service_.current_log_
+ ->uma_proto()
+ ->system_profile()
+ .stability()
+ .unclean_system_shutdown_count());
+}
+
+TEST_F(UploadServiceTest, LogKernelCrash) {
+ upload_service_.AddSample(*Crash("kernel"));
+
+ EXPECT_EQ(1, upload_service_.current_log_
+ ->uma_proto()
+ ->system_profile()
+ .stability()
+ .kernel_crash_count());
+}
+
+TEST_F(UploadServiceTest, UnknownCrashIgnored) {
+ upload_service_.AddSample(*Crash("foo"));
+
+ // The log should be empty.
+ EXPECT_FALSE(upload_service_.current_log_);
+}
+
+TEST_F(UploadServiceTest, FailedSendAreRetried) {
+ sender_->set_should_succeed(false);
+
+ upload_service_.AddSample(*Crash("user"));
+ upload_service_.UploadEvent();
+ EXPECT_EQ(1, sender_->send_call_count());
+ std::string sent_string = sender_->last_message();
+
+ upload_service_.UploadEvent();
+ EXPECT_EQ(2, sender_->send_call_count());
+ EXPECT_EQ(sent_string, sender_->last_message());
+}
+
+TEST_F(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload) {
+ sender_->set_should_succeed(false);
+ upload_service_.AddSample(*Crash("user"));
+
+ for (int i = 0; i < UploadService::kMaxFailedUpload; i++) {
+ upload_service_.UploadEvent();
+ }
+
+ EXPECT_TRUE(upload_service_.staged_log_);
+ upload_service_.UploadEvent();
+ EXPECT_FALSE(upload_service_.staged_log_);
+}
+
+TEST_F(UploadServiceTest, EmptyLogsAreNotSent) {
+ upload_service_.UploadEvent();
+ EXPECT_FALSE(upload_service_.current_log_);
+ EXPECT_EQ(0, sender_->send_call_count());
+}
+
+TEST_F(UploadServiceTest, LogEmptyByDefault) {
+ UploadService upload_service(new MockSystemProfileSetter(), &metrics_lib_,
+ kMetricsServer);
+
+ // current_log_ should be initialized later as it needs AtExitManager to exit
+ // in order to gather system information from SysInfo.
+ EXPECT_FALSE(upload_service.current_log_);
+}
+
+TEST_F(UploadServiceTest, CanSendMultipleTimes) {
+ upload_service_.AddSample(*Crash("user"));
+ upload_service_.UploadEvent();
+
+ std::string first_message = sender_->last_message();
+
+ upload_service_.AddSample(*Crash("kernel"));
+ upload_service_.UploadEvent();
+
+ EXPECT_NE(first_message, sender_->last_message());
+}
+
+TEST_F(UploadServiceTest, LogEmptyAfterUpload) {
+ upload_service_.AddSample(*Crash("user"));
+
+ EXPECT_TRUE(upload_service_.current_log_);
+
+ upload_service_.UploadEvent();
+ EXPECT_FALSE(upload_service_.current_log_);
+}
+
+TEST_F(UploadServiceTest, LogContainsAggregatedValues) {
+ scoped_ptr<metrics::MetricSample> histogram =
+ metrics::MetricSample::HistogramSample("foo", 10, 0, 42, 10);
+ upload_service_.AddSample(*histogram.get());
+
+
+ scoped_ptr<metrics::MetricSample> histogram2 =
+ metrics::MetricSample::HistogramSample("foo", 11, 0, 42, 10);
+ upload_service_.AddSample(*histogram2.get());
+
+ upload_service_.GatherHistograms();
+ metrics::ChromeUserMetricsExtension* proto =
+ upload_service_.current_log_->uma_proto();
+ EXPECT_EQ(1, proto->histogram_event().size());
+}
+
+TEST_F(UploadServiceTest, ExtractChannelFromString) {
+ EXPECT_EQ(
+ SystemProfileCache::ProtoChannelFromString(
+ "developer-build"),
+ metrics::SystemProfileProto::CHANNEL_UNKNOWN);
+
+ EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_DEV,
+ SystemProfileCache::ProtoChannelFromString("dev-channel"));
+
+ EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_UNKNOWN,
+ SystemProfileCache::ProtoChannelFromString("dev-channel test"));
+}
+
+TEST_F(UploadServiceTest, ValuesInConfigFileAreSent) {
+ std::string name("os name");
+ std::string content(
+ "CHROMEOS_RELEASE_NAME=" + name +
+ "\nCHROMEOS_RELEASE_VERSION=version\n"
+ "CHROMEOS_RELEASE_DESCRIPTION=description beta-channel test\n"
+ "CHROMEOS_RELEASE_TRACK=beta-channel\n"
+ "CHROMEOS_RELEASE_BUILD_TYPE=developer build\n"
+ "CHROMEOS_RELEASE_BOARD=myboard");
+
+ base::SysInfo::SetChromeOSVersionInfoForTest(content, base::Time());
+ scoped_ptr<metrics::MetricSample> histogram =
+ metrics::MetricSample::SparseHistogramSample("myhistogram", 1);
+ SystemProfileCache* local_cache_ = new SystemProfileCache(true, "/");
+ local_cache_->session_id_.reset(new chromeos_metrics::PersistentInteger(
+ dir_.path().Append("session_id").value()));
+
+ upload_service_.system_profile_setter_.reset(local_cache_);
+ // Reset to create the new log with the profile setter.
+ upload_service_.Reset();
+ upload_service_.AddSample(*histogram.get());
+ upload_service_.UploadEvent();
+
+ EXPECT_EQ(1, sender_->send_call_count());
+ EXPECT_TRUE(sender_->is_good_proto());
+ EXPECT_EQ(1, sender_->last_message_proto().histogram_event().size());
+
+ EXPECT_EQ(name, sender_->last_message_proto().system_profile().os().name());
+ EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_BETA,
+ sender_->last_message_proto().system_profile().channel());
+ EXPECT_NE(0, sender_->last_message_proto().client_id());
+ EXPECT_NE(0,
+ sender_->last_message_proto().system_profile().build_timestamp());
+ EXPECT_NE(0, sender_->last_message_proto().session_id());
+}
+
+TEST_F(UploadServiceTest, PersistentGUID) {
+ std::string tmp_file = dir_.path().Append("tmpfile").value();
+
+ std::string first_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+ std::string second_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+
+ // The GUID are cached.
+ EXPECT_EQ(first_guid, second_guid);
+
+ base::DeleteFile(base::FilePath(tmp_file), false);
+
+ first_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+ base::DeleteFile(base::FilePath(tmp_file), false);
+ second_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+
+ // Random GUIDs are generated (not all the same).
+ EXPECT_NE(first_guid, second_guid);
+}
+
+TEST_F(UploadServiceTest, SessionIdIncrementedAtInitialization) {
+ cache_.Initialize();
+ int session_id = cache_.profile_.session_id;
+ cache_.initialized_ = false;
+ cache_.Initialize();
+ EXPECT_EQ(cache_.profile_.session_id, session_id + 1);
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index de10535..d6dad2d 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -26,7 +26,7 @@
#
# create some directories (some are mount points)
LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
- sbin dev proc sys system data oem acct config storage mnt root)
+ sbin dev proc sys system data oem acct cache config storage mnt root)
include $(BUILD_SYSTEM)/base_rules.mk
diff --git a/rootdir/init.rc b/rootdir/init.rc
index b7a593f..f5febde 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -292,6 +292,7 @@
mkdir /data/mediadrm 0770 mediadrm mediadrm
mkdir /data/adb 0700 root root
+ mkdir /data/anr 0775 system system
# symlink to bugreport storage location
symlink /data/data/com.android.shell/files/bugreports /data/bugreports