Merge "adbd: enable USB SuperSpeed (again)"
diff --git a/.gitignore b/.gitignore
index b25c15b..2f836aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
*~
+*.pyc
diff --git a/adb/Android.mk b/adb/Android.mk
index f030041..e271a63 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -11,6 +11,9 @@
adb_host_clang := true
endif
+adb_host_sanitize :=
+adb_target_sanitize :=
+
adb_version := $(shell git -C $(LOCAL_PATH) rev-parse --short=12 HEAD 2>/dev/null)-android
ADB_COMMON_CFLAGS := \
@@ -19,6 +22,15 @@
-Wno-missing-field-initializers \
-DADB_REVISION='"$(adb_version)"' \
+# Define windows.h and tchar.h Unicode preprocessor symbols so that
+# CreateFile(), _tfopen(), etc. map to versions that take wchar_t*, breaking the
+# build if you accidentally pass char*. Fix by calling like:
+# CreateFileW(widen(utf8).c_str()).
+ADB_COMMON_windows_CFLAGS := \
+ -DUNICODE=1 -D_UNICODE=1 \
+
+ADB_COMMON_CFLAGS += $(ADB_COMMON_$(HOST_OS)_CFLAGS)
+
# libadb
# =========================================================
@@ -48,6 +60,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 \
@@ -59,7 +76,6 @@
usb_linux.cpp \
LIBADB_windows_SRC_FILES := \
- get_my_path_windows.cpp \
sysdeps_win32.cpp \
usb_windows.cpp \
@@ -75,6 +91,7 @@
qemu_tracing.cpp \
usb_linux_client.cpp \
+LOCAL_SANITIZE := $(adb_target_sanitize)
LOCAL_SHARED_LIBRARIES := libbase
# Even though we're building a static library (and thus there's no link step for
@@ -92,6 +109,7 @@
$(LIBADB_$(HOST_OS)_SRC_FILES) \
adb_auth_host.cpp \
+LOCAL_SANITIZE := $(adb_host_sanitize)
LOCAL_SHARED_LIBRARIES := libbase
# Even though we're building a static library (and thus there's no link step for
@@ -109,16 +127,20 @@
LOCAL_MODULE := adbd_test
LOCAL_CFLAGS := -DADB_HOST=0 $(LIBADB_CFLAGS)
LOCAL_SRC_FILES := $(LIBADB_TEST_SRCS)
+LOCAL_SANITIZE := $(adb_target_sanitize)
LOCAL_STATIC_LIBRARIES := libadbd
LOCAL_SHARED_LIBRARIES := liblog libbase libcutils
include $(BUILD_NATIVE_TEST)
-ifneq ($(HOST_OS),windows)
+# adb_test
+# =========================================================
+
include $(CLEAR_VARS)
LOCAL_CLANG := $(adb_host_clang)
LOCAL_MODULE := adb_test
LOCAL_CFLAGS := -DADB_HOST=1 $(LIBADB_CFLAGS)
LOCAL_SRC_FILES := $(LIBADB_TEST_SRCS) services.cpp
+LOCAL_SANITIZE := $(adb_host_sanitize)
LOCAL_SHARED_LIBRARIES := liblog libbase
LOCAL_STATIC_LIBRARIES := \
libadb \
@@ -133,9 +155,13 @@
LOCAL_LDLIBS += -framework CoreFoundation -framework IOKit
endif
-include $(BUILD_HOST_NATIVE_TEST)
+ifeq ($(HOST_OS),windows)
+ LOCAL_LDLIBS += -lws2_32 -luserenv
+ LOCAL_STATIC_LIBRARIES += AdbWinApi
endif
+include $(BUILD_HOST_NATIVE_TEST)
+
# adb device tracker (used by ddms) test tool
# =========================================================
@@ -145,6 +171,7 @@
LOCAL_MODULE := adb_device_tracker_test
LOCAL_CFLAGS := -DADB_HOST=1 $(LIBADB_CFLAGS)
LOCAL_SRC_FILES := test_track_devices.cpp
+LOCAL_SANITIZE := $(adb_host_sanitize)
LOCAL_SHARED_LIBRARIES := liblog libbase
LOCAL_STATIC_LIBRARIES := libadb libcrypto_static libcutils
LOCAL_LDLIBS += -lrt -ldl -lpthread
@@ -166,6 +193,8 @@
endif
ifeq ($(HOST_OS),windows)
+ # Use wmain instead of main
+ LOCAL_LDFLAGS += -municode
LOCAL_LDLIBS += -lws2_32 -lgdi32
EXTRA_STATIC_LIBS := AdbWinApi
endif
@@ -188,6 +217,7 @@
LOCAL_MODULE := adb
LOCAL_MODULE_TAGS := debug
+LOCAL_SANITIZE := $(adb_host_sanitize)
LOCAL_STATIC_LIBRARIES := \
libadb \
libbase \
@@ -238,21 +268,23 @@
-D_GNU_SOURCE \
-Wno-deprecated-declarations \
-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
-LOCAL_CFLAGS += -DALLOW_ADBD_ROOT=1
-endif
+LOCAL_CFLAGS += -DALLOW_ADBD_NO_AUTH=$(if $(filter userdebug eng,$(TARGET_BUILD_VARIANT)),1,0)
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
LOCAL_CFLAGS += -DALLOW_ADBD_DISABLE_VERITY=1
+LOCAL_CFLAGS += -DALLOW_ADBD_ROOT=1
endif
LOCAL_MODULE := adbd
+LOCAL_INIT_RC := adbd.rc
+
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT_SBIN)
LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_SBIN_UNSTRIPPED)
LOCAL_C_INCLUDES += system/extras/ext4_utils
+LOCAL_SANITIZE := $(adb_target_sanitize)
LOCAL_STATIC_LIBRARIES := \
libadbd \
libbase \
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 9c1ead5..29c9481 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -35,12 +35,14 @@
#include <unordered_map>
#include <base/logging.h>
+#include <base/macros.h>
#include <base/stringprintf.h>
#include <base/strings.h>
#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]))
@@ -53,11 +55,11 @@
ADB_MUTEX_DEFINE(D_lock);
-int HOST = 0;
-
#if !ADB_HOST
const char* adb_device_banner = "device";
static android::base::LogdLogger gLogdLogger;
+#else
+const char* adb_device_banner = "host";
#endif
void AdbLogger(android::base::LogId id, android::base::LogSeverity severity,
@@ -69,6 +71,14 @@
#endif
}
+std::string adb_version() {
+ // Don't change the format of this --- it's parsed by ddmlib.
+ return android::base::StringPrintf("Android Debug Bridge version %d.%d.%d\n"
+ "Revision %s\n",
+ ADB_VERSION_MAJOR, ADB_VERSION_MINOR, ADB_SERVER_VERSION,
+ ADB_REVISION);
+}
+
void fatal(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
@@ -115,7 +125,7 @@
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
fprintf(stderr, "--- adb starting (pid %d) ---\n", getpid());
- adb_close(fd);
+ unix_close(fd);
}
#endif
@@ -174,7 +184,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;
}
@@ -191,13 +201,19 @@
void adb_trace_init(char** argv) {
#if !ADB_HOST
- if (isatty(STDOUT_FILENO) == 0) {
- start_device_log();
+ // Don't open log file if no tracing, since this will block
+ // the crypto unmount of /data
+ if (!get_trace_setting().empty()) {
+ if (isatty(STDOUT_FILENO) == 0) {
+ start_device_log();
+ }
}
#endif
setup_trace_mask();
android::base::InitLogging(argv, AdbLogger);
+
+ D("%s", adb_version().c_str());
}
apacket* get_apacket(void)
@@ -291,45 +307,50 @@
send_packet(p, t);
}
-static size_t fill_connect_data(char *buf, size_t bufsize)
-{
-#if ADB_HOST
- return snprintf(buf, bufsize, "host::") + 1;
-#else
- static const char *cnxn_props[] = {
+std::string get_connection_string() {
+ std::vector<std::string> connection_properties;
+
+#if !ADB_HOST
+ static const char* cnxn_props[] = {
"ro.product.name",
"ro.product.model",
"ro.product.device",
};
- static const int num_cnxn_props = ARRAY_SIZE(cnxn_props);
- int i;
- size_t remaining = bufsize;
- size_t len;
- len = snprintf(buf, remaining, "%s::", adb_device_banner);
- remaining -= len;
- buf += len;
- for (i = 0; i < num_cnxn_props; i++) {
+ for (const auto& prop_name : cnxn_props) {
char value[PROPERTY_VALUE_MAX];
- property_get(cnxn_props[i], value, "");
- len = snprintf(buf, remaining, "%s=%s;", cnxn_props[i], value);
- remaining -= len;
- buf += len;
+ property_get(prop_name, value, "");
+ connection_properties.push_back(
+ android::base::StringPrintf("%s=%s", prop_name, value));
}
-
- return bufsize - remaining + 1;
#endif
+
+ connection_properties.push_back(android::base::StringPrintf(
+ "features=%s", android::base::Join(supported_features(), ',').c_str()));
+
+ return android::base::StringPrintf(
+ "%s::%s", adb_device_banner,
+ android::base::Join(connection_properties, ';').c_str());
}
-void send_connect(atransport *t)
-{
+void send_connect(atransport* t) {
D("Calling send_connect \n");
- apacket *cp = get_apacket();
+ apacket* cp = get_apacket();
cp->msg.command = A_CNXN;
- cp->msg.arg0 = A_VERSION;
- cp->msg.arg1 = MAX_PAYLOAD;
- cp->msg.data_length = fill_connect_data((char *)cp->data,
- sizeof(cp->data));
+ cp->msg.arg0 = t->get_protocol_version();
+ cp->msg.arg1 = t->get_max_payload();
+
+ std::string connection_str = get_connection_string();
+ // Connect and auth packets are limited to MAX_PAYLOAD_V1 because we don't
+ // yet know how much data the other size is willing to accept.
+ if (connection_str.length() > MAX_PAYLOAD_V1) {
+ LOG(FATAL) << "Connection banner is too long (length = "
+ << connection_str.length() << ")";
+ }
+
+ memcpy(cp->data, connection_str.c_str(), connection_str.length());
+ cp->msg.data_length = connection_str.length();
+
send_packet(cp, t);
}
@@ -342,8 +363,8 @@
*dst = strdup(src.c_str());
}
-void parse_banner(const char* banner, atransport* t) {
- D("parse_banner: %s\n", banner);
+void parse_banner(const std::string& banner, atransport* t) {
+ D("parse_banner: %s\n", banner.c_str());
// The format is something like:
// "device::ro.product.name=x;ro.product.model=y;ro.product.device=z;".
@@ -366,6 +387,10 @@
qual_overwrite(&t->model, value);
} else if (key == "ro.product.device") {
qual_overwrite(&t->device, value);
+ } else if (key == "features") {
+ for (const auto& feature : android::base::Split(value, ",")) {
+ t->add_feature(feature);
+ }
}
}
}
@@ -393,6 +418,29 @@
}
}
+static void handle_new_connection(atransport* t, apacket* p) {
+ if (t->connection_state != kCsOffline) {
+ t->connection_state = kCsOffline;
+ handle_offline(t);
+ }
+
+ t->update_version(p->msg.arg0, p->msg.arg1);
+ std::string banner(reinterpret_cast<const char*>(p->data),
+ p->msg.data_length);
+ parse_banner(banner, t);
+
+#if ADB_HOST
+ handle_online(t);
+#else
+ if (!auth_required) {
+ handle_online(t);
+ send_connect(t);
+ } else {
+ send_auth_request(t);
+ }
+#endif
+}
+
void handle_packet(apacket *p, atransport *t)
{
asocket *s;
@@ -407,7 +455,9 @@
case A_SYNC:
if(p->msg.arg0){
send_packet(p, t);
- if(HOST) send_connect(t);
+#if ADB_HOST
+ send_connect(t);
+#endif
} else {
t->connection_state = kCsOffline;
handle_offline(t);
@@ -415,21 +465,8 @@
}
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);
- }
-
- parse_banner(reinterpret_cast<const char*>(p->data), t);
-
- if (HOST || !auth_enabled) {
- handle_online(t);
- if(!HOST) send_connect(t);
- } else {
- send_auth_request(t);
- }
+ case A_CNXN: // CONNECT(version, maxdata, "system-id-string")
+ handle_new_connection(t, p);
break;
case A_AUTH:
@@ -545,22 +582,54 @@
/* we create a PIPE that will be used to wait for the server's "OK" */
/* message since the pipe handles must be inheritable, we use a */
/* security attribute */
+ HANDLE nul_read, nul_write;
HANDLE pipe_read, pipe_write;
HANDLE stdout_handle, stderr_handle;
SECURITY_ATTRIBUTES sa;
- STARTUPINFO startup;
+ STARTUPINFOW startup;
PROCESS_INFORMATION pinfo;
- char program_path[ MAX_PATH ];
+ WCHAR program_path[ MAX_PATH ];
int ret;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
+ /* Redirect stdin and stderr to Windows /dev/null. If we instead pass our
+ * stdin/stderr handles and they are console handles, when the adb server
+ * starts up, the C Runtime will see console handles for a process that
+ * isn't connected to a console and it will configure stderr to be closed.
+ * At that point, freopen() could be used to reopen stderr, but it would
+ * take more massaging to fixup the file descriptor number that freopen()
+ * uses. It's simplest to avoid all of this complexity by just redirecting
+ * stdin/stderr to `nul' and then the C Runtime acts as expected.
+ */
+ nul_read = CreateFileW(L"nul", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (nul_read == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "CreateFileW(nul, GENERIC_READ) failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ return -1;
+ }
+
+ nul_write = CreateFileW(L"nul", GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (nul_write == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "CreateFileW(nul, GENERIC_WRITE) failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ CloseHandle(nul_read);
+ return -1;
+ }
+
/* create pipe, and ensure its read handle isn't inheritable */
ret = CreatePipe( &pipe_read, &pipe_write, &sa, 0 );
if (!ret) {
- fprintf(stderr, "CreatePipe() failure, error %ld\n", GetLastError() );
+ fprintf(stderr, "CreatePipe() failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ CloseHandle(nul_read);
+ CloseHandle(nul_write);
return -1;
}
@@ -588,18 +657,40 @@
ZeroMemory( &startup, sizeof(startup) );
startup.cb = sizeof(startup);
- startup.hStdInput = GetStdHandle( STD_INPUT_HANDLE );
- startup.hStdOutput = pipe_write;
- startup.hStdError = GetStdHandle( STD_ERROR_HANDLE );
+ startup.hStdInput = nul_read;
+ startup.hStdOutput = nul_write;
+ startup.hStdError = nul_write;
startup.dwFlags = STARTF_USESTDHANDLES;
ZeroMemory( &pinfo, sizeof(pinfo) );
/* get path of current program */
- GetModuleFileName( NULL, program_path, sizeof(program_path) );
- char args[64];
- snprintf(args, sizeof(args), "adb -P %d fork-server server", server_port);
- ret = CreateProcess(
+ DWORD module_result = GetModuleFileNameW(NULL, program_path,
+ arraysize(program_path));
+ if ((module_result == arraysize(program_path)) || (module_result == 0)) {
+ // String truncation or some other error.
+ fprintf(stderr, "GetModuleFileNameW() failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ return -1;
+ }
+
+ // Verify that the pipe_write handle value can be passed on the command line
+ // as %d and that the rest of adb code can pass it around in an int.
+ const int pipe_write_as_int = cast_handle_to_int(pipe_write);
+ if (cast_int_to_handle(pipe_write_as_int) != pipe_write) {
+ // If this fires, either handle values are larger than 32-bits or else
+ // there is a bug in our casting.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203%28v=vs.85%29.aspx
+ fprintf(stderr, "CreatePipe handle value too large: 0x%p\n",
+ pipe_write);
+ return -1;
+ }
+
+ WCHAR args[64];
+ snwprintf(args, arraysize(args),
+ L"adb -P %d fork-server server --reply-fd %d", server_port,
+ pipe_write_as_int);
+ ret = CreateProcessW(
program_path, /* program path */
args,
/* the fork-server argument will set the
@@ -613,10 +704,13 @@
&startup, /* startup info, i.e. std handles */
&pinfo );
+ CloseHandle( nul_read );
+ CloseHandle( nul_write );
CloseHandle( pipe_write );
if (!ret) {
- fprintf(stderr, "CreateProcess failure, error %ld\n", GetLastError() );
+ fprintf(stderr, "CreateProcess failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
CloseHandle( pipe_read );
return -1;
}
@@ -632,7 +726,8 @@
ret = ReadFile( pipe_read, temp, 3, &count, NULL );
CloseHandle( pipe_read );
if ( !ret ) {
- fprintf(stderr, "could not read ok from ADB Server, error = %ld\n", GetLastError() );
+ fprintf(stderr, "could not read ok from ADB Server, error: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
return -1;
}
if (count != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
@@ -645,7 +740,7 @@
int fd[2];
// set up a pipe so the child can tell us when it is ready.
- // fd[0] will be parent's end, and fd[1] will get mapped to stderr in the child.
+ // fd[0] will be parent's end, and the child will write on fd[1]
if (pipe(fd)) {
fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno);
return -1;
@@ -657,16 +752,14 @@
if (pid == 0) {
// child side of the fork
- // redirect stderr to the pipe
- // we use stderr instead of stdout due to stdout's buffering behavior.
adb_close(fd[0]);
- dup2(fd[1], STDERR_FILENO);
- adb_close(fd[1]);
char str_port[30];
- snprintf(str_port, sizeof(str_port), "%d", server_port);
+ snprintf(str_port, sizeof(str_port), "%d", server_port);
+ char reply_fd[30];
+ snprintf(reply_fd, sizeof(reply_fd), "%d", fd[1]);
// child process
- int result = execl(path, "adb", "-P", str_port, "fork-server", "server", NULL);
+ int result = execl(path, "adb", "-P", str_port, "fork-server", "server", "--reply-fd", reply_fd, NULL);
// this should not return
fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno);
} else {
@@ -729,12 +822,12 @@
if (android::base::StartsWith(service, "killforward:")) {
kill_forward = true;
service += 12;
+ } else {
+ service += 8; // skip past "forward:"
if (android::base::StartsWith(service, "norebind:")) {
no_rebind = true;
service += 9;
}
- } else {
- service += 8;
}
std::vector<std::string> pieces = android::base::Split(service, ";");
@@ -760,11 +853,13 @@
return 1;
}
+ std::string error;
InstallStatus r;
if (kill_forward) {
r = remove_listener(pieces[0].c_str(), transport);
} else {
- r = install_listener(pieces[0], pieces[1].c_str(), transport, no_rebind);
+ r = install_listener(pieces[0], pieces[1].c_str(), transport,
+ no_rebind, &error);
}
if (r == INSTALL_STATUS_OK) {
#if ADB_HOST
@@ -780,10 +875,11 @@
case INSTALL_STATUS_OK: message = "success (!)"; break;
case INSTALL_STATUS_INTERNAL_ERROR: message = "internal error"; break;
case INSTALL_STATUS_CANNOT_BIND:
- message = android::base::StringPrintf("cannot bind to socket: %s", strerror(errno));
+ message = android::base::StringPrintf("cannot bind listener: %s",
+ error.c_str());
break;
case INSTALL_STATUS_CANNOT_REBIND:
- message = android::base::StringPrintf("cannot rebind existing socket: %s", strerror(errno));
+ message = android::base::StringPrintf("cannot rebind existing socket");
break;
case INSTALL_STATUS_LISTENER_NOT_FOUND:
message = android::base::StringPrintf("listener '%s' not found", service);
@@ -795,17 +891,30 @@
return 0;
}
+#if ADB_HOST
+static int SendOkay(int fd, const std::string& s) {
+ SendOkay(fd);
+ SendProtocolString(fd, s);
+ return 0;
+}
+#endif
+
int handle_host_request(const char* service, TransportType type,
const char* serial, int reply_fd, asocket* s) {
if (strcmp(service, "kill") == 0) {
fprintf(stderr, "adb server killed by remote request\n");
fflush(stdout);
SendOkay(reply_fd);
+
+ // At least on Windows, if we exit() without shutdown(SD_SEND) or
+ // closesocket(), the client's next recv() will error-out with
+ // WSAECONNRESET and they'll never read the OKAY.
+ adb_shutdown(reply_fd);
+
exit(0);
}
#if ADB_HOST
- atransport *transport = NULL;
// "transport:" is used for switching transport with a specified serial number
// "transport-usb:" is used for switching transport to the only USB transport
// "transport-local:" is used for switching transport to the only local transport
@@ -824,11 +933,10 @@
serial = service;
}
- std::string error_msg = "unknown failure";
- transport = acquire_one_transport(kCsAny, type, serial, &error_msg);
-
- if (transport) {
- s->transport = transport;
+ std::string error_msg;
+ atransport* t = acquire_one_transport(kCsAny, type, serial, &error_msg);
+ if (t != nullptr) {
+ s->transport = t;
SendOkay(reply_fd);
} else {
SendFail(reply_fd, error_msg);
@@ -843,83 +951,73 @@
D("Getting device list...\n");
std::string device_list = list_transports(long_listing);
D("Sending device list...\n");
- SendOkay(reply_fd);
- SendProtocolString(reply_fd, device_list);
- return 0;
+ return SendOkay(reply_fd, device_list);
}
return 1;
}
+ if (!strcmp(service, "features")) {
+ SendOkay(reply_fd);
+ SendProtocolString(
+ reply_fd, android::base::Join(supported_features(), '\n'));
+ return 0;
+ }
+
// 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");
}
- SendOkay(reply_fd);
- SendProtocolString(reply_fd, buffer);
- return 0;
+ 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
if (!strcmp(service, "version")) {
- SendOkay(reply_fd);
- SendProtocolString(reply_fd, android::base::StringPrintf("%04x", ADB_SERVER_VERSION));
- return 0;
+ return SendOkay(reply_fd, android::base::StringPrintf("%04x", ADB_SERVER_VERSION));
}
- if(!strncmp(service,"get-serialno",strlen("get-serialno"))) {
- const char *out = "unknown";
- transport = acquire_one_transport(kCsAny, type, serial, NULL);
- if (transport && transport->serial) {
- out = transport->serial;
- }
- SendOkay(reply_fd);
- SendProtocolString(reply_fd, out);
- return 0;
+ // These always report "unknown" rather than the actual error, for scripts.
+ if (!strcmp(service, "get-serialno")) {
+ std::string ignored;
+ atransport* t = acquire_one_transport(kCsAny, type, serial, &ignored);
+ return SendOkay(reply_fd, (t && t->serial) ? t->serial : "unknown");
}
- if(!strncmp(service,"get-devpath",strlen("get-devpath"))) {
- const char *out = "unknown";
- transport = acquire_one_transport(kCsAny, type, serial, NULL);
- if (transport && transport->devpath) {
- out = transport->devpath;
- }
- SendOkay(reply_fd);
- SendProtocolString(reply_fd, out);
- return 0;
+ if (!strcmp(service, "get-devpath")) {
+ std::string ignored;
+ atransport* t = acquire_one_transport(kCsAny, type, serial, &ignored);
+ return SendOkay(reply_fd, (t && t->devpath) ? t->devpath : "unknown");
}
+ if (!strcmp(service, "get-state")) {
+ std::string ignored;
+ atransport* t = acquire_one_transport(kCsAny, type, serial, &ignored);
+ return SendOkay(reply_fd, t ? t->connection_state_name() : "unknown");
+ }
+
// indicates a new emulator instance has started
- if (!strncmp(service,"emulator:",9)) {
+ if (!strncmp(service, "emulator:", 9)) {
int port = atoi(service+9);
local_connect(port);
/* we don't even need to send a reply */
return 0;
}
-
- if(!strncmp(service,"get-state",strlen("get-state"))) {
- transport = acquire_one_transport(kCsAny, type, serial, NULL);
- SendOkay(reply_fd);
- SendProtocolString(reply_fd, transport->connection_state_name());
- return 0;
- }
#endif // ADB_HOST
int ret = handle_forward_request(service, type, serial, reply_fd);
diff --git a/adb/adb.h b/adb/adb.h
index 1be83d7..6855f3b 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -20,12 +20,16 @@
#include <limits.h>
#include <sys/types.h>
+#include <string>
+
#include <base/macros.h>
#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
@@ -42,6 +46,8 @@
#define ADB_VERSION_MAJOR 1
#define ADB_VERSION_MINOR 0
+std::string adb_version();
+
// Increment this when we want to force users to start a new adb server.
#define ADB_SERVER_VERSION 32
@@ -137,6 +143,8 @@
/* A socket is bound to atransport */
atransport *transport;
+
+ size_t get_max_payload() const;
};
@@ -183,62 +191,6 @@
kCsUnauthorized,
};
-class atransport {
-public:
- // TODO(danalbert): We expose waaaaaaay too much stuff because this was
- // historically just a struct, but making the whole thing a more idiomatic
- // class in one go is a very large change. Given how bad our testing is,
- // it's better to do this piece by piece.
-
- atransport() {
- auth_fde = {};
- transport_fde = {};
- }
-
- virtual ~atransport() {}
-
- int (*read_from_remote)(apacket* p, atransport* t) = nullptr;
- int (*write_to_remote)(apacket* p, atransport* t) = nullptr;
- void (*close)(atransport* t) = nullptr;
- void (*kick)(atransport* t) = nullptr;
-
- int fd = -1;
- int transport_socket = -1;
- fdevent transport_fde;
- int ref_count = 0;
- uint32_t sync_token = 0;
- ConnectionState connection_state = kCsOffline;
- bool online = false;
- TransportType type = kTransportAny;
-
- // USB handle or socket fd as needed.
- usb_handle* usb = nullptr;
- int sfd = -1;
-
- // Used to identify transports for clients.
- char* serial = nullptr;
- char* product = nullptr;
- char* model = nullptr;
- char* device = nullptr;
- char* devpath = nullptr;
- int adb_port = -1; // Use for emulators (local transport)
- bool kicked = false;
-
- // A list of adisconnect callbacks called when the transport is kicked.
- adisconnect disconnects = {};
-
- void* key = nullptr;
- unsigned char token[TOKEN_SIZE] = {};
- fdevent auth_fde;
- size_t failed_auth_attempts = 0;
-
- const char* connection_state_name() const;
-
-private:
- DISALLOW_COPY_AND_ASSIGN(atransport);
-};
-
-
/* A listener is an entity which binds to a local port
** and, upon receiving a connection on that port, creates
** an asocket to connect the new local connection to a
@@ -284,7 +236,7 @@
void get_my_path(char *s, size_t maxLen);
int launch_server(int server_port);
-int adb_main(int is_daemon, int server_port);
+int adb_main(int is_daemon, int server_port, int ack_reply_fd);
/* initialize a transport object's func pointers and state */
#if ADB_HOST
@@ -344,8 +296,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();
@@ -363,9 +315,11 @@
ConnectionState connection_state(atransport *t);
-extern const char *adb_device_banner;
-extern int HOST;
+extern const char* adb_device_banner;
+
+#if !ADB_HOST
extern int SHELL_EXIT_NOTIFY_FD;
+#endif // !ADB_HOST
#define CHUNK_SIZE (64*1024)
@@ -387,4 +341,6 @@
void send_connect(atransport *t);
+void parse_banner(const std::string&, atransport* t);
+
#endif
diff --git a/adb/adb_auth.cpp b/adb/adb_auth.cpp
index dc01825..8a6b156 100644
--- a/adb/adb_auth.cpp
+++ b/adb/adb_auth.cpp
@@ -28,7 +28,7 @@
#include "adb.h"
#include "transport.h"
-int auth_enabled = 0;
+bool auth_required = true;
void send_auth_request(atransport *t)
{
@@ -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.h b/adb/adb_auth.h
index 1e1978d..a13604a 100644
--- a/adb/adb_auth.h
+++ b/adb/adb_auth.h
@@ -19,7 +19,7 @@
#include "adb.h"
-extern int auth_enabled;
+extern bool auth_required;
int adb_auth_keygen(const char* filename);
void adb_auth_verified(atransport *t);
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 61a3777..e7f82a9 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;
}
@@ -301,11 +301,18 @@
char android_dir[PATH_MAX];
struct stat buf;
#ifdef _WIN32
- char path[PATH_MAX];
+ std::string home_str;
home = getenv("ANDROID_SDK_HOME");
if (!home) {
- SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, 0, path);
- home = path;
+ WCHAR path[MAX_PATH];
+ const HRESULT hr = SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, path);
+ if (FAILED(hr)) {
+ D("SHGetFolderPathW failed: %s\n",
+ SystemErrorCodeToString(hr).c_str());
+ return -1;
+ }
+ home_str = narrow(path);
+ home = home_str.c_str();
}
format = "%s\\%s";
#else
@@ -323,7 +330,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 +346,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,12 +421,15 @@
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");
// TODO(danalbert): ReadFileToString
+ // Note that on Windows, load_file() does not do CR/LF translation, but
+ // ReadFileToString() uses the C Runtime which uses CR/LF translation by
+ // default (by is overridable with _setmode()).
unsigned size;
char* file_data = reinterpret_cast<char*>(load_file(path, &size));
if (file_data == nullptr) {
diff --git a/adb/adb_client.cpp b/adb/adb_client.cpp
index ef9a586..4bf647c 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;
@@ -151,14 +153,22 @@
}
int fd;
+ std::string reason;
if (__adb_server_name) {
- fd = socket_network_client(__adb_server_name, __adb_server_port, SOCK_STREAM);
+ 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;
+ fd = network_loopback_client(__adb_server_port, SOCK_STREAM, &reason);
+ if (fd == -1) {
+ *error = android::base::StringPrintf("cannot connect to daemon: %s",
+ reason.c_str());
+ return -2;
+ }
}
if (memcmp(&service[0],"host",4) != 0 && switch_socket_transport(fd, error)) {
@@ -187,6 +197,7 @@
D("adb_connect: service %s\n", service.c_str());
if (fd == -2 && __adb_server_name) {
fprintf(stderr,"** Cannot start server on remote host\n");
+ // error is the original network connection error
return fd;
} else if (fd == -2) {
fprintf(stdout,"* daemon not running. starting it now on port %d *\n",
@@ -194,6 +205,10 @@
start_server:
if (launch_server(__adb_server_port)) {
fprintf(stderr,"* failed to start daemon *\n");
+ // launch_server() has already printed detailed error info, so just
+ // return a generic error string about the overall adb_connect()
+ // that the caller requested.
+ *error = "cannot connect to daemon";
return -1;
} else {
fprintf(stdout,"* daemon started successfully *\n");
@@ -215,12 +230,16 @@
adb_close(fd);
if (sscanf(&version_string[0], "%04x", &version) != 1) {
- goto error;
+ *error = android::base::StringPrintf(
+ "cannot parse version string: %s",
+ version_string.c_str());
+ return -1;
}
} else {
// if fd is -1, then check for "unknown host service",
- // which would indicate a version of adb that does not support the version command
- if (*error == "unknown host service") {
+ // which would indicate a version of adb that does not support the
+ // version command, in which case we should fall-through to kill it.
+ if (*error != "unknown host service") {
return fd;
}
}
@@ -228,7 +247,13 @@
if (version != ADB_SERVER_VERSION) {
printf("adb server is out of date. killing...\n");
fd = _adb_connect("host:kill", error);
- adb_close(fd);
+ if (fd >= 0) {
+ adb_close(fd);
+ } else {
+ // If we couldn't connect to the server or had some other error,
+ // report it, but still try to start the server.
+ fprintf(stderr, "error: %s\n", error->c_str());
+ }
/* XXX can we better detect its death? */
adb_sleep_ms(2000);
@@ -243,7 +268,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 +302,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_io_test.cpp b/adb/adb_io_test.cpp
index 8fd5cbf..f637073 100644
--- a/adb/adb_io_test.cpp
+++ b/adb/adb_io_test.cpp
@@ -28,30 +28,13 @@
#include <string>
#include "base/file.h"
+#include "base/test_utils.h"
-class TemporaryFile {
- public:
- TemporaryFile() {
- init("/data/local/tmp");
- if (fd == -1) {
- init("/tmp");
- }
- }
-
- ~TemporaryFile() {
- close(fd);
- unlink(filename);
- }
-
- int fd;
- char filename[1024];
-
- private:
- void init(const char* tmp_dir) {
- snprintf(filename, sizeof(filename), "%s/TemporaryFile-XXXXXX", tmp_dir);
- fd = mkstemp(filename);
- }
-};
+// All of these tests fail on Windows because they use the C Runtime open(),
+// but the adb_io APIs expect file descriptors from adb_open(). Also, the
+// android::base file APIs use the C Runtime which uses CR/LF translation by
+// default (changeable with _setmode()), but the adb_io APIs use adb_read()
+// and adb_write() which do no translation.
TEST(io, ReadFdExactly_whole) {
const char expected[] = "Foobar";
diff --git a/adb/adb_listeners.cpp b/adb/adb_listeners.cpp
index a335eec..8fb2d19 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"
@@ -109,27 +110,30 @@
free_listener(reinterpret_cast<alistener*>(listener));
}
-static int local_name_to_fd(const char* name) {
+static int local_name_to_fd(const char* name, std::string* error) {
if (!strncmp("tcp:", name, 4)) {
int port = atoi(name + 4);
if (gListenAll > 0) {
- return socket_inaddr_any_server(port, SOCK_STREAM);
+ return network_inaddr_any_server(port, SOCK_STREAM, error);
} else {
- return socket_loopback_server(port, SOCK_STREAM);
+ return network_loopback_server(port, SOCK_STREAM, error);
}
}
-#ifndef HAVE_WIN32_IPC /* no Unix-domain sockets on Win32 */
+#if !defined(_WIN32) // No Unix-domain sockets on Windows.
// It's nonsensical to support the "reserved" space on the adb host side
if (!strncmp(name, "local:", 6)) {
- return socket_local_server(name + 6, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
+ return network_local_server(name + 6,
+ ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM, error);
} else if (!strncmp(name, "localabstract:", 14)) {
- return socket_local_server(name + 14, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
+ return network_local_server(name + 14,
+ ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM, error);
} else if (!strncmp(name, "localfilesystem:", 16)) {
- return socket_local_server(name + 16, ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM);
+ return network_local_server(name + 16,
+ ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM, error);
}
#endif
- printf("unknown local portname '%s'\n", name);
+ *error = android::base::StringPrintf("unknown local portname '%s'", name);
return -1;
}
@@ -142,8 +146,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;
}
@@ -175,7 +181,8 @@
InstallStatus install_listener(const std::string& local_name,
const char *connect_to,
atransport* transport,
- int no_rebind)
+ int no_rebind,
+ std::string* error)
{
for (alistener* l = listener_list.next; l != &listener_list; l = l->next) {
if (local_name == l->local_name) {
@@ -183,16 +190,19 @@
/* can't repurpose a smartsocket */
if(l->connect_to[0] == '*') {
+ *error = "cannot repurpose smartsocket";
return INSTALL_STATUS_INTERNAL_ERROR;
}
/* can't repurpose a listener if 'no_rebind' is true */
if (no_rebind) {
+ *error = "cannot rebind";
return INSTALL_STATUS_CANNOT_REBIND;
}
cto = strdup(connect_to);
if(cto == 0) {
+ *error = "cannot duplicate string";
return INSTALL_STATUS_INTERNAL_ERROR;
}
@@ -223,9 +233,8 @@
goto nomem;
}
- listener->fd = local_name_to_fd(listener->local_name);
+ listener->fd = local_name_to_fd(listener->local_name, error);
if (listener->fd < 0) {
- printf("cannot bind '%s': %s\n", listener->local_name, strerror(errno));
free(listener->local_name);
free(listener->connect_to);
free(listener);
diff --git a/adb/adb_listeners.h b/adb/adb_listeners.h
index 67deb21..fa98eed 100644
--- a/adb/adb_listeners.h
+++ b/adb/adb_listeners.h
@@ -33,7 +33,8 @@
InstallStatus install_listener(const std::string& local_name,
const char* connect_to,
atransport* transport,
- int no_rebind);
+ int no_rebind,
+ std::string* error);
std::string format_listeners();
diff --git a/adb/adb_utils.cpp b/adb/adb_utils.cpp
index 604bd57..ca843bd 100644
--- a/adb/adb_utils.cpp
+++ b/adb/adb_utils.cpp
@@ -18,6 +18,7 @@
#include "adb_utils.h"
+#include <libgen.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -25,11 +26,15 @@
#include <algorithm>
+#include <base/logging.h>
#include <base/stringprintf.h>
+#include <base/strings.h>
#include "adb_trace.h"
#include "sysdeps.h"
+ADB_MUTEX_DEFINE(dirname_lock);
+
bool getcwd(std::string* s) {
char* cwd = getcwd(nullptr, 0);
if (cwd != nullptr) *s = cwd;
@@ -63,6 +68,89 @@
return result;
}
+std::string adb_basename(const std::string& path) {
+ size_t base = path.find_last_of(OS_PATH_SEPARATORS);
+ return (base != std::string::npos) ? path.substr(base + 1) : path;
+}
+
+std::string adb_dirname(const std::string& path) {
+ // Copy path because dirname may modify the string passed in.
+ std::string parent_storage(path);
+
+ // Use lock because dirname() may write to a process global and return a
+ // pointer to that. Note that this locking strategy only works if all other
+ // callers to dirname in the process also grab this same lock.
+ adb_mutex_lock(&dirname_lock);
+
+ // Note that if std::string uses copy-on-write strings, &str[0] will cause
+ // the copy to be made, so there is no chance of us accidentally writing to
+ // the storage for 'path'.
+ char* parent = dirname(&parent_storage[0]);
+
+ // In case dirname returned a pointer to a process global, copy that string
+ // before leaving the lock.
+ const std::string result(parent);
+
+ adb_mutex_unlock(&dirname_lock);
+
+ return result;
+}
+
+// Given a relative or absolute filepath, create the parent directory hierarchy
+// as needed. Returns true if the hierarchy is/was setup.
+bool mkdirs(const std::string& path) {
+ // TODO: all the callers do unlink && mkdirs && adb_creat ---
+ // that's probably the operation we should expose.
+
+ // Implementation Notes:
+ //
+ // Pros:
+ // - Uses dirname, so does not need to deal with OS_PATH_SEPARATOR.
+ // - On Windows, uses mingw dirname which accepts '/' and '\\', drive letters
+ // (C:\foo), UNC paths (\\server\share\dir\dir\file), and Unicode (when
+ // combined with our adb_mkdir() which takes UTF-8).
+ // - Is optimistic wrt thinking that a deep directory hierarchy will exist.
+ // So it does as few stat()s as possible before doing mkdir()s.
+ // Cons:
+ // - Recursive, so it uses stack space relative to number of directory
+ // components.
+
+ const std::string parent(adb_dirname(path));
+
+ if (directory_exists(parent)) {
+ return true;
+ }
+
+ // If dirname returned the same path as what we passed in, don't go recursive.
+ // This can happen on Windows when walking up the directory hierarchy and not
+ // finding anything that already exists (unlike POSIX that will eventually
+ // find . or /).
+ if (parent == path) {
+ errno = ENOENT;
+ return false;
+ }
+
+ // Recursively make parent directories of 'parent'.
+ if (!mkdirs(parent)) {
+ return false;
+ }
+
+ // Now that the parent directory hierarchy of 'parent' has been ensured,
+ // create parent itself.
+ if (adb_mkdir(parent, 0775) == -1) {
+ // Can't just check for errno == EEXIST because it might be a file that
+ // exists.
+ const int saved_errno = errno;
+ if (directory_exists(parent)) {
+ return true;
+ }
+ errno = saved_errno;
+ return false;
+ }
+
+ return true;
+}
+
void dump_hex(const void* data, size_t byte_count) {
byte_count = std::min(byte_count, size_t(16));
@@ -84,3 +172,56 @@
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;
+}
diff --git a/adb/adb_utils.h b/adb/adb_utils.h
index 84f7d0c..739efcc 100644
--- a/adb/adb_utils.h
+++ b/adb/adb_utils.h
@@ -22,8 +22,26 @@
bool getcwd(std::string* cwd);
bool directory_exists(const std::string& path);
+// Like the regular basename and dirname, but thread-safe on all
+// platforms and capable of correctly handling exotic Windows paths.
+std::string adb_basename(const std::string& path);
+std::string adb_dirname(const std::string& path);
+
+bool mkdirs(const std::string& path);
+
std::string escape_arg(const std::string& s);
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);
+
#endif
diff --git a/adb/adb_utils_test.cpp b/adb/adb_utils_test.cpp
index 052aea5..17c8d0a 100644
--- a/adb/adb_utils_test.cpp
+++ b/adb/adb_utils_test.cpp
@@ -16,12 +16,60 @@
#include "adb_utils.h"
+#ifdef _WIN32
+#include <windows.h>
+#include <userenv.h>
+#endif
+
+#include <string>
+
#include <gtest/gtest.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sysdeps.h"
+
+#include <base/macros.h>
+#include <base/test_utils.h>
+
+#ifdef _WIN32
+static std::string subdir(const char* parent, const char* child) {
+ std::string str(parent);
+ str += OS_PATH_SEPARATOR;
+ str += child;
+ return str;
+}
+#endif
+
TEST(adb_utils, directory_exists) {
+#ifdef _WIN32
+ char profiles_dir[MAX_PATH];
+ DWORD cch = arraysize(profiles_dir);
+
+ // On typical Windows 7, returns C:\Users
+ ASSERT_TRUE(GetProfilesDirectoryA(profiles_dir, &cch));
+
+ ASSERT_TRUE(directory_exists(profiles_dir));
+
+ // On modern (English?) Windows, this is a directory symbolic link to
+ // C:\ProgramData. Symbolic links are rare on Windows and the user requires
+ // a special permission (by default granted to Administrative users) to
+ // create symbolic links.
+ ASSERT_FALSE(directory_exists(subdir(profiles_dir, "All Users")));
+
+ // On modern (English?) Windows, this is a directory junction to
+ // C:\Users\Default. Junctions are used throughout user profile directories
+ // for backwards compatibility and they don't require any special permissions
+ // to create.
+ ASSERT_FALSE(directory_exists(subdir(profiles_dir, "Default User")));
+
+ ASSERT_FALSE(directory_exists(subdir(profiles_dir, "does-not-exist")));
+#else
ASSERT_TRUE(directory_exists("/proc"));
ASSERT_FALSE(directory_exists("/proc/self")); // Symbolic link.
ASSERT_FALSE(directory_exists("/proc/does-not-exist"));
+#endif
}
TEST(adb_utils, escape_arg) {
@@ -50,3 +98,107 @@
ASSERT_EQ(R"('abc(')", escape_arg("abc("));
ASSERT_EQ(R"('abc)')", escape_arg("abc)"));
}
+
+TEST(adb_utils, adb_basename) {
+ EXPECT_EQ("sh", adb_basename("/system/bin/sh"));
+ EXPECT_EQ("sh", adb_basename("sh"));
+}
+
+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));
+}
+
+void test_mkdirs(const std::string basepath) {
+ EXPECT_TRUE(mkdirs(basepath));
+ EXPECT_NE(-1, adb_creat(basepath.c_str(), 0600));
+ EXPECT_FALSE(mkdirs(basepath + "/subdir/"));
+}
+
+TEST(adb_utils, mkdirs) {
+ TemporaryDir td;
+
+ // Absolute paths.
+ test_mkdirs(std::string(td.path) + "/dir/subdir/file");
+
+ // Relative paths.
+ ASSERT_EQ(0, chdir(td.path)) << strerror(errno);
+ test_mkdirs(std::string("relative/subrel/file"));
+}
diff --git a/adb/adbd.rc b/adb/adbd.rc
new file mode 100644
index 0000000..b91d8b5
--- /dev/null
+++ b/adb/adbd.rc
@@ -0,0 +1,14 @@
+on post-fs-data
+ mkdir /data/misc/adb 02750 system shell
+ mkdir /data/adb 0700 root root
+
+# adbd is controlled via property triggers in init.<platform>.usb.rc
+service adbd /sbin/adbd --root_seclabel=u:r:su:s0
+ class core
+ socket adbd stream 660 system system
+ disabled
+ seclabel u:r:adbd:s0
+
+# adbd on at boot in emulator
+on property:ro.kernel.qemu=1
+ start adbd
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index 0cd6670..39bb02b 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -82,21 +82,17 @@
static std::string GetLogFilePath() {
const char log_name[] = "adb.log";
- char temp_path[MAX_PATH - sizeof(log_name) + 1];
+ WCHAR temp_path[MAX_PATH];
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364992%28v=vs.85%29.aspx
- DWORD nchars = GetTempPath(sizeof(temp_path), temp_path);
- CHECK_LE(nchars, sizeof(temp_path));
- if (nchars == 0) {
- // TODO(danalbert): Log the error message from FormatError().
- // Windows unfortunately has two errnos, errno and GetLastError(), so
- // I'm not sure what to do about PLOG here. Probably better to just
- // ignore it and add a simplified version of FormatError() for use in
- // log messages.
- LOG(ERROR) << "Error creating log file";
+ DWORD nchars = GetTempPathW(arraysize(temp_path), temp_path);
+ if ((nchars >= arraysize(temp_path)) || (nchars == 0)) {
+ // If string truncation or some other error.
+ fatal("cannot retrieve temporary file path: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
}
- return std::string(temp_path) + log_name;
+ return narrow(temp_path) + log_name;
}
#else
static const char kNullFileName[] = "/dev/null";
@@ -108,26 +104,40 @@
static void close_stdin() {
int fd = unix_open(kNullFileName, O_RDONLY);
- CHECK_NE(fd, -1);
- dup2(fd, STDIN_FILENO);
- adb_close(fd);
+ if (fd == -1) {
+ fatal("cannot open '%s': %s", kNullFileName, strerror(errno));
+ }
+ if (dup2(fd, STDIN_FILENO) == -1) {
+ fatal("cannot redirect stdin: %s", strerror(errno));
+ }
+ unix_close(fd);
}
static void setup_daemon_logging(void) {
- int fd = unix_open(GetLogFilePath().c_str(), O_WRONLY | O_CREAT | O_APPEND,
+ const std::string log_file_path(GetLogFilePath());
+ int fd = unix_open(log_file_path.c_str(), O_WRONLY | O_CREAT | O_APPEND,
0640);
if (fd == -1) {
- fd = unix_open(kNullFileName, O_WRONLY);
+ fatal("cannot open '%s': %s", log_file_path.c_str(), strerror(errno));
}
- dup2(fd, STDOUT_FILENO);
- dup2(fd, STDERR_FILENO);
- adb_close(fd);
+ if (dup2(fd, STDOUT_FILENO) == -1) {
+ fatal("cannot redirect stdout: %s", strerror(errno));
+ }
+ if (dup2(fd, STDERR_FILENO) == -1) {
+ fatal("cannot redirect stderr: %s", strerror(errno));
+ }
+ unix_close(fd);
+
+#ifdef _WIN32
+ // On Windows, stderr is buffered by default, so switch to non-buffered
+ // to match Linux.
+ setvbuf(stderr, NULL, _IONBF, 0);
+#endif
fprintf(stderr, "--- adb starting (pid %d) ---\n", getpid());
+ LOG(INFO) << adb_version();
}
-int adb_main(int is_daemon, int server_port) {
- HOST = 1;
-
+int adb_main(int is_daemon, int server_port, int ack_reply_fd) {
#if defined(_WIN32)
SetConsoleCtrlHandler(ctrlc_handler, TRUE);
#else
@@ -144,26 +154,36 @@
local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT);
adb_auth_init();
+ std::string error;
std::string local_name = android::base::StringPrintf("tcp:%d", server_port);
- if (install_listener(local_name, "*smartsocket*", nullptr, 0)) {
- LOG(FATAL) << "Could not install *smartsocket* listener";
+ if (install_listener(local_name, "*smartsocket*", nullptr, 0, &error)) {
+ fatal("could not install *smartsocket* listener: %s", error.c_str());
}
+ // Inform our parent that we are up and running.
if (is_daemon) {
- // Inform our parent that we are up and running.
+#if defined(_WIN32)
+ const HANDLE ack_reply_handle = cast_int_to_handle(ack_reply_fd);
+ const CHAR ack[] = "OK\n";
+ const DWORD bytes_to_write = arraysize(ack) - 1;
+ DWORD written = 0;
+ if (!WriteFile(ack_reply_handle, ack, bytes_to_write, &written, NULL)) {
+ fatal("adb: cannot write ACK to handle 0x%p: %s", ack_reply_handle,
+ SystemErrorCodeToString(GetLastError()).c_str());
+ }
+ if (written != bytes_to_write) {
+ fatal("adb: cannot write %lu bytes of ACK: only wrote %lu bytes",
+ bytes_to_write, written);
+ }
+ CloseHandle(ack_reply_handle);
+#else
// TODO(danalbert): Can't use SendOkay because we're sending "OK\n", not
// "OKAY".
- // TODO(danalbert): Why do we use stdout for Windows?
-#if defined(_WIN32)
- int reply_fd = STDOUT_FILENO;
- // Change stdout mode to binary so \n => \r\n translation does not
- // occur. In a moment stdout will be reopened to the daemon log file
- // anyway.
- _setmode(reply_fd, _O_BINARY);
-#else
- int reply_fd = STDERR_FILENO;
+ if (!android::base::WriteStringToFd("OK\n", ack_reply_fd)) {
+ fatal_errno("error writing ACK to fd %d", ack_reply_fd);
+ }
+ unix_close(ack_reply_fd);
#endif
- android::base::WriteStringToFd("OK\n", reply_fd);
close_stdin();
setup_daemon_logging();
}
@@ -174,9 +194,34 @@
return 0;
}
+#ifdef _WIN32
+static bool _argv_is_utf8 = false;
+#endif
+
int main(int argc, char** argv) {
+#ifdef _WIN32
+ if (!_argv_is_utf8) {
+ fatal("_argv_is_utf8 is not set, suggesting that wmain was not "
+ "called. Did you forget to link with -municode?");
+ }
+#endif
+
adb_sysdeps_init();
adb_trace_init(argv);
- D("Handling commandline()\n");
return adb_commandline(argc - 1, const_cast<const char**>(argv + 1));
}
+
+#ifdef _WIN32
+
+extern "C"
+int wmain(int argc, wchar_t **argv) {
+ // Set diagnostic flag to try to detect if the build system was not
+ // configured to call wmain.
+ _argv_is_utf8 = true;
+
+ // Convert args from UTF-16 to UTF-8 and pass that to main().
+ NarrowArgs narrow_args(argc, argv);
+ return main(argc, narrow_args.data());
+}
+
+#endif
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 7fbca31..c7b7675 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -65,16 +65,9 @@
gProductOutPath.c_str(), OS_PATH_SEPARATOR_STR, extra);
}
-static void version(FILE* out) {
- fprintf(out, "Android Debug Bridge version %d.%d.%d\nRevision %s\n",
- ADB_VERSION_MAJOR, ADB_VERSION_MINOR, ADB_SERVER_VERSION, ADB_REVISION);
-}
-
static void help() {
- version(stderr);
-
+ fprintf(stderr, "%s\n", adb_version().c_str());
fprintf(stderr,
- "\n"
" -a - directs adb to listen on all interfaces for a connection\n"
" -d - directs command to the only connected USB device\n"
" returns an error if more than one USB device is present.\n"
@@ -110,7 +103,6 @@
" ('-a' means copy timestamp and mode)\n"
" adb sync [ <directory> ] - copy host->device only if changed\n"
" (-l means list but don't copy)\n"
- " (see 'adb help all')\n"
" adb shell - run remote shell interactively\n"
" adb shell <command> - run remote shell command\n"
" adb emu <command> - run emulator console command\n"
@@ -202,11 +194,12 @@
" adb reboot sideload - reboots the device into the sideload mode in recovery program (adb root required).\n"
" adb reboot sideload-auto-reboot\n"
" - reboots into the sideload mode, then reboots automatically after the sideload regardless of the result.\n"
- " adb reboot-bootloader - reboots the device into the bootloader\n"
+ " adb sideload <file> - sideloads the given package\n"
" adb root - restarts the adbd daemon with root permissions\n"
" adb unroot - restarts the adbd daemon without root permissions\n"
" adb usb - restarts the adbd daemon listening on USB\n"
" adb tcpip <port> - restarts the adbd daemon listening on TCP on the specified port\n"
+ "\n"
"networking:\n"
" adb ppp <tty> [parameters] - Run PPP over USB.\n"
" Note: you should not automatically start a PPP connection.\n"
@@ -221,7 +214,7 @@
" - If it is \"system\", \"vendor\", \"oem\" or \"data\", only the corresponding partition\n"
" is updated.\n"
"\n"
- "environmental variables:\n"
+ "environment variables:\n"
" ADB_TRACE - Print debug information. A comma separated list of the following values\n"
" 1 or all, adb, sockets, packets, rwx, usb, sync, sysdeps, transport, jdwp\n"
" ANDROID_SERIAL - The serial number to connect to. -s takes priority over this if given.\n"
@@ -302,13 +295,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 +350,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);
}
@@ -455,8 +480,8 @@
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data);
if (show_progress) {
- char *x = strrchr(service, ':');
- if(x) service = x + 1;
+ const char* x = strrchr(service, ':');
+ if (x) service = x + 1;
}
while (sz > 0) {
@@ -724,25 +749,6 @@
return send_shell_command(transport, serial, cmd);
}
-static int mkdirs(const char *path)
-{
- int ret;
- char *x = (char *)path + 1;
-
- for(;;) {
- x = adb_dirstart(x);
- if(x == 0) return 0;
- *x = 0;
- ret = adb_mkdir(path, 0775);
- *x = OS_PATH_SEPARATOR;
- if((ret < 0) && (errno != EEXIST)) {
- return ret;
- }
- x++;
- }
- return 0;
-}
-
static int backup(int argc, const char** argv) {
const char* filename = "./backup.ab";
@@ -834,25 +840,25 @@
* Given <hint>, try to construct an absolute path to the
* ANDROID_PRODUCT_OUT dir.
*/
-static std::string find_product_out_path(const char* hint) {
- if (hint == NULL || hint[0] == '\0') {
+static std::string find_product_out_path(const std::string& hint) {
+ if (hint.empty()) {
return "";
}
// If it's already absolute, don't bother doing any work.
- if (adb_is_absolute_host_path(hint)) {
+ if (adb_is_absolute_host_path(hint.c_str())) {
return hint;
}
// If there are any slashes in it, assume it's a relative path;
// make it absolute.
- if (adb_dirstart(hint) != nullptr) {
+ if (hint.find_first_of(OS_PATH_SEPARATORS) != std::string::npos) {
std::string cwd;
if (!getcwd(&cwd)) {
fprintf(stderr, "adb: getcwd failed: %s\n", strerror(errno));
return "";
}
- return android::base::StringPrintf("%s%s%s", cwd.c_str(), OS_PATH_SEPARATOR_STR, hint);
+ return android::base::StringPrintf("%s%c%s", cwd.c_str(), OS_PATH_SEPARATOR, hint.c_str());
}
// It's a string without any slashes. Try to do something with it.
@@ -876,7 +882,7 @@
path += hint;
if (!directory_exists(path)) {
fprintf(stderr, "adb: Couldn't find a product dir based on -p %s; "
- "\"%s\" doesn't exist\n", hint, path.c_str());
+ "\"%s\" doesn't exist\n", hint.c_str(), path.c_str());
return "";
}
return path;
@@ -940,6 +946,7 @@
int is_server = 0;
int r;
TransportType transport_type = kTransportAny;
+ int ack_reply_fd = -1;
// If defined, this should be an absolute path to
// the directory containing all of the various system images
@@ -958,10 +965,10 @@
const char* server_port_str = getenv("ANDROID_ADB_SERVER_PORT");
int server_port = DEFAULT_ADB_PORT;
if (server_port_str && strlen(server_port_str) > 0) {
- server_port = (int) strtol(server_port_str, NULL, 0);
+ server_port = strtol(server_port_str, nullptr, 0);
if (server_port <= 0 || server_port > 65535) {
fprintf(stderr,
- "adb: Env var ANDROID_ADB_SERVER_PORT must be a positive number less than 65535. Got \"%s\"\n",
+ "adb: Env var ANDROID_ADB_SERVER_PORT must be a positive number less than 65536. Got \"%s\"\n",
server_port_str);
return usage();
}
@@ -976,8 +983,25 @@
} else if (!strcmp(argv[0], "fork-server")) {
/* this is a special flag used only when the ADB client launches the ADB Server */
is_daemon = 1;
+ } else if (!strcmp(argv[0], "--reply-fd")) {
+ if (argc < 2) return usage();
+ const char* reply_fd_str = argv[1];
+ argc--;
+ argv++;
+ ack_reply_fd = strtol(reply_fd_str, nullptr, 10);
+#ifdef _WIN32
+ const HANDLE ack_reply_handle = cast_int_to_handle(ack_reply_fd);
+ if ((GetStdHandle(STD_INPUT_HANDLE) == ack_reply_handle) ||
+ (GetStdHandle(STD_OUTPUT_HANDLE) == ack_reply_handle) ||
+ (GetStdHandle(STD_ERROR_HANDLE) == ack_reply_handle)) {
+#else
+ if (ack_reply_fd <= 2) { // Disallow stdin, stdout, and stderr.
+#endif
+ fprintf(stderr, "adb: invalid reply fd \"%s\"\n", reply_fd_str);
+ return usage();
+ }
} else if (!strncmp(argv[0], "-p", 2)) {
- const char *product = NULL;
+ const char* product = nullptr;
if (argv[0][2] == '\0') {
if (argc < 2) return usage();
product = argv[1];
@@ -1053,7 +1077,11 @@
if (is_server) {
if (no_daemon || is_daemon) {
- r = adb_main(is_daemon, server_port);
+ if (is_daemon && (ack_reply_fd == -1)) {
+ fprintf(stderr, "reply fd for adb server to client communication not specified.\n");
+ return usage();
+ }
+ r = adb_main(is_daemon, server_port, ack_reply_fd);
} else {
r = launch_server(server_port);
}
@@ -1205,11 +1233,22 @@
else if (!strcmp(argv[0], "kill-server")) {
std::string error;
int fd = _adb_connect("host:kill", &error);
- if (fd == -1) {
+ if (fd == -2) {
+ // Failed to make network connection to server. Don't output the
+ // network error since that is expected.
fprintf(stderr,"* server not running *\n");
+ // Successful exit code because the server is already "killed".
+ return 0;
+ } else if (fd == -1) {
+ // Some other error.
+ fprintf(stderr, "error: %s\n", error.c_str());
return 1;
+ } else {
+ // Successfully connected, kill command sent, okay status came back.
+ // Server should exit() in a moment, if not already.
+ adb_close(fd);
+ return 0;
}
- return 0;
}
else if (!strcmp(argv[0], "sideload")) {
if (argc != 2) return usage();
@@ -1219,10 +1258,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") ||
@@ -1391,7 +1432,11 @@
}
else if (!strcmp(argv[0], "start-server")) {
std::string error;
- return adb_connect("host:start-server", &error);
+ const int result = adb_connect("host:start-server", &error);
+ if (result < 0) {
+ fprintf(stderr, "error: %s\n", error.c_str());
+ }
+ return result;
}
else if (!strcmp(argv[0], "backup")) {
return backup(argc, argv);
@@ -1412,9 +1457,12 @@
return 0;
}
else if (!strcmp(argv[0], "version")) {
- version(stdout);
+ fprintf(stdout, "%s", adb_version().c_str());
return 0;
}
+ else if (!strcmp(argv[0], "features")) {
+ return adb_query_command("host:features");
+ }
usage();
return 1;
@@ -1447,22 +1495,11 @@
return pm_command(transport, serial, argc, argv);
}
-static int delete_file(TransportType transport, const char* serial, char* filename) {
+static int delete_file(TransportType transport, const char* serial, const std::string& filename) {
std::string cmd = "shell:rm -f " + escape_arg(filename);
return send_shell_command(transport, serial, cmd);
}
-static const char* get_basename(const char* filename)
-{
- const char* basename = adb_dirstop(filename);
- if (basename) {
- basename++;
- return basename;
- } else {
- return filename;
- }
-}
-
static int install_app(TransportType transport, const char* serial, int argc, const char** argv) {
static const char *const DATA_DEST = "/data/local/tmp/%s";
static const char *const SD_DEST = "/sdcard/tmp/%s";
@@ -1481,7 +1518,7 @@
int last_apk = -1;
for (i = argc - 1; i >= 0; i--) {
const char* file = argv[i];
- char* dot = strrchr(file, '.');
+ const char* dot = strrchr(file, '.');
if (dot && !strcasecmp(dot, ".apk")) {
if (stat(file, &sb) == -1 || !S_ISREG(sb.st_mode)) {
fprintf(stderr, "Invalid APK file: %s\n", file);
@@ -1499,13 +1536,12 @@
}
const char* apk_file = argv[last_apk];
- char apk_dest[PATH_MAX];
- snprintf(apk_dest, sizeof apk_dest, where, get_basename(apk_file));
- int err = do_sync_push(apk_file, apk_dest, 0 /* no show progress */);
+ std::string apk_dest = android::base::StringPrintf(where, adb_basename(apk_file).c_str());
+ int err = do_sync_push(apk_file, apk_dest.c_str(), 0 /* no show progress */);
if (err) {
goto cleanup_apk;
} else {
- argv[last_apk] = apk_dest; /* destination name, not source location */
+ argv[last_apk] = apk_dest.c_str(); /* destination name, not source location */
}
err = pm_command(transport, serial, argc, argv);
@@ -1527,7 +1563,7 @@
int first_apk = -1;
for (i = argc - 1; i >= 0; i--) {
const char* file = argv[i];
- char* dot = strrchr(file, '.');
+ const char* dot = strrchr(file, '.');
if (dot && !strcasecmp(dot, ".apk")) {
if (stat(file, &sb) == -1 || !S_ISREG(sb.st_mode)) {
fprintf(stderr, "Invalid APK file: %s\n", file);
@@ -1589,7 +1625,7 @@
std::string cmd = android::base::StringPrintf(
"exec:pm install-write -S %" PRIu64 " %d %d_%s -",
- static_cast<uint64_t>(sb.st_size), session_id, i, get_basename(file));
+ static_cast<uint64_t>(sb.st_size), session_id, i, adb_basename(file).c_str());
int localFd = adb_open(file, O_RDONLY);
if (localFd < 0) {
diff --git a/adb/console.cpp b/adb/console.cpp
index 0707960..ba5a72b 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"
@@ -70,9 +71,11 @@
return -1;
}
- int fd = socket_loopback_client(port, SOCK_STREAM);
+ std::string error;
+ int fd = network_loopback_client(port, SOCK_STREAM, &error);
if (fd == -1) {
- fprintf(stderr, "error: could not connect to TCP port %d\n", port);
+ fprintf(stderr, "error: could not connect to TCP port %d: %s\n", port,
+ error.c_str());
return -1;
}
return fd;
diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp
index c0612cd..dc89639 100644
--- a/adb/daemon/main.cpp
+++ b/adb/daemon/main.cpp
@@ -68,13 +68,6 @@
#if defined(ALLOW_ADBD_ROOT)
char value[PROPERTY_VALUE_MAX];
- // The emulator is never secure, so don't drop privileges there.
- // TODO: this seems like a bug --- shouldn't the emulator behave like a device?
- property_get("ro.kernel.qemu", value, "");
- if (strcmp(value, "1") == 0) {
- return false;
- }
-
// The properties that affect `adb root` and `adb unroot` are ro.secure and
// ro.debuggable. In this context the names don't make the expected behavior
// particularly obvious.
@@ -125,11 +118,12 @@
// descriptor will always be open.
adbd_cloexec_auth_socket();
- auth_enabled = property_get_bool("ro.adb.secure", 0) != 0;
- if (auth_enabled) {
- adbd_auth_init();
+ if (ALLOW_ADBD_NO_AUTH && property_get_bool("ro.adb.secure", 0) == 0) {
+ auth_required = false;
}
+ adbd_auth_init();
+
// Our external storage path may be different than apps, since
// we aren't able to bind mount after dropping root.
const char* adb_external_storage = getenv("ADB_EXTERNAL_STORAGE");
@@ -171,15 +165,18 @@
D("Local port disabled\n");
} else {
- if ((root_seclabel != nullptr) && (is_selinux_enabled() > 0)) {
+ if (root_seclabel != nullptr) {
if (setcon(root_seclabel) < 0) {
LOG(FATAL) << "Could not set selinux context";
}
}
+ std::string error;
std::string local_name =
android::base::StringPrintf("tcp:%d", server_port);
- if (install_listener(local_name, "*smartsocket*", nullptr, 0)) {
- LOG(FATAL) << "Could not install *smartsocket* listener";
+ if (install_listener(local_name, "*smartsocket*", nullptr, 0,
+ &error)) {
+ LOG(FATAL) << "Could not install *smartsocket* listener: "
+ << error;
}
}
@@ -226,7 +223,7 @@
return;
}
dup2(fd, STDIN_FILENO);
- adb_close(fd);
+ unix_close(fd);
}
int main(int argc, char** argv) {
diff --git a/adb/device.py b/adb/device.py
new file mode 100644
index 0000000..5b33ff2
--- /dev/null
+++ b/adb/device.py
@@ -0,0 +1,263 @@
+#
+# 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 logging
+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):
+ # Delimiter string to indicate the start of the exit code.
+ _RETURN_CODE_DELIMITER = 'x'
+
+ # Follow any shell command with this string to get the exit
+ # status of a program since this isn't propagated by adb.
+ #
+ # The delimiter is needed because `printf 1; echo $?` would print
+ # "10", and we wouldn't be able to distinguish the exit code.
+ _RETURN_CODE_PROBE_STRING = 'echo "{0}$?"'.format(_RETURN_CODE_DELIMITER)
+
+ # Maximum search distance from the output end to find the delimiter.
+ # adb on Windows returns \r\n even if adbd returns \n.
+ _RETURN_CODE_SEARCH_LENGTH = len('{0}255\r\n'.format(_RETURN_CODE_DELIMITER))
+
+ 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
+
+ @property
+ def linesep(self):
+ if self._linesep is None:
+ self._linesep = subprocess.check_output(self.adb_cmd +
+ ['shell', 'echo'])
+ return self._linesep
+
+ def _make_shell_cmd(self, user_cmd):
+ return (self.adb_cmd + ['shell'] + user_cmd +
+ ['; ' + self._RETURN_CODE_PROBE_STRING])
+
+ def _parse_shell_output(self, out):
+ """Finds the exit code string from shell output.
+
+ Args:
+ out: Shell output string.
+
+ Returns:
+ An (exit_code, output_string) tuple. The output string is
+ cleaned of any additional stuff we appended to find the
+ exit code.
+
+ Raises:
+ RuntimeError: Could not find the exit code in |out|.
+ """
+ search_text = out
+ if len(search_text) > self._RETURN_CODE_SEARCH_LENGTH:
+ # We don't want to search over massive amounts of data when we know
+ # the part we want is right at the end.
+ search_text = search_text[-self._RETURN_CODE_SEARCH_LENGTH:]
+ partition = search_text.rpartition(self._RETURN_CODE_DELIMITER)
+ if partition[1] == '':
+ raise RuntimeError('Could not find exit status in shell output.')
+ result = int(partition[2])
+ # partition[0] won't contain the full text if search_text was truncated,
+ # pull from the original string instead.
+ out = out[:-len(partition[1]) - len(partition[2])]
+ return result, out
+
+ def _simple_call(self, cmd):
+ logging.info(' '.join(self.adb_cmd + cmd))
+ return subprocess.check_output(
+ self.adb_cmd + cmd, stderr=subprocess.STDOUT)
+
+ def shell(self, cmd):
+ logging.info(' '.join(self.adb_cmd + ['shell'] + 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)
+ logging.info(' '.join(cmd))
+ p = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ out, _ = p.communicate()
+ return self._parse_shell_output(out)
+
+ def install(self, filename, replace=False):
+ cmd = ['install']
+ if replace:
+ cmd.append('-r')
+ cmd.append(filename)
+ return self._simple_call(cmd)
+
+ 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 reboot(self):
+ return self._simple_call(['reboot'])
+
+ 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]).splitlines()
+ 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..a8abade 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,
@@ -233,7 +234,7 @@
#else /* USE_SELECT */
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
#include <winsock2.h>
#else
#include <sys/select.h>
@@ -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));
@@ -597,7 +617,7 @@
fde->func = func;
fde->arg = arg;
-#ifndef HAVE_WINSOCK
+#if !defined(_WIN32)
fcntl(fd, F_SETFL, O_NONBLOCK);
#endif
fdevent_register(fde);
@@ -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/file_sync_client.cpp b/adb/file_sync_client.cpp
index 1dc711ae..da80013 100644
--- a/adb/file_sync_client.cpp
+++ b/adb/file_sync_client.cpp
@@ -32,8 +32,11 @@
#include "adb.h"
#include "adb_client.h"
#include "adb_io.h"
+#include "adb_utils.h"
#include "file_sync_service.h"
+#include <base/stringprintf.h>
+
static unsigned long long total_bytes;
static long long start_time;
@@ -91,36 +94,30 @@
typedef void (*sync_ls_cb)(unsigned mode, unsigned size, unsigned time, const char *name, void *cookie);
static int sync_ls(int fd, const char* path, sync_ls_cb func, void* cookie) {
+ int len = strlen(path);
+ if (len > 1024) goto fail;
+
syncmsg msg;
- char buf[257];
- int len;
-
- len = strlen(path);
- if(len > 1024) goto fail;
-
msg.req.id = ID_LIST;
msg.req.namelen = htoll(len);
- if(!WriteFdExactly(fd, &msg.req, sizeof(msg.req)) ||
- !WriteFdExactly(fd, path, len)) {
+ if (!WriteFdExactly(fd, &msg.req, sizeof(msg.req)) || !WriteFdExactly(fd, path, len)) {
goto fail;
}
- for(;;) {
- if(!ReadFdExactly(fd, &msg.dent, sizeof(msg.dent))) break;
- if(msg.dent.id == ID_DONE) return 0;
- if(msg.dent.id != ID_DENT) break;
+ for (;;) {
+ if (!ReadFdExactly(fd, &msg.dent, sizeof(msg.dent))) break;
+ if (msg.dent.id == ID_DONE) return 0;
+ if (msg.dent.id != ID_DENT) break;
len = ltohl(msg.dent.namelen);
- if(len > 256) break;
+ if (len > 256) break;
- if(!ReadFdExactly(fd, buf, len)) break;
+ char buf[257];
+ if (!ReadFdExactly(fd, buf, len)) break;
buf[len] = 0;
- func(ltohl(msg.dent.mode),
- ltohl(msg.dent.size),
- ltohl(msg.dent.time),
- buf, cookie);
+ func(ltohl(msg.dent.mode), ltohl(msg.dent.size), ltohl(msg.dent.time), buf, cookie);
}
fail:
@@ -219,7 +216,7 @@
return 0;
}
-static int write_data_file(int fd, const char *path, syncsendbuf *sbuf, int show_progress)
+static int write_data_file(int fd, const char *path, syncsendbuf *sbuf, bool show_progress)
{
int lfd, err = 0;
unsigned long long size = 0;
@@ -273,7 +270,7 @@
}
static int write_data_buffer(int fd, char* file_buffer, int size, syncsendbuf *sbuf,
- int show_progress)
+ bool show_progress)
{
int err = 0;
int total = 0;
@@ -307,10 +304,8 @@
#else
static int write_data_link(int fd, const char *path, syncsendbuf *sbuf)
{
- int len, ret;
-
- len = readlink(path, sbuf->data, SYNC_DATA_MAX-1);
- if(len < 0) {
+ int len = readlink(path, sbuf->data, SYNC_DATA_MAX-1);
+ if (len < 0) {
fprintf(stderr, "error reading link '%s': %s\n", path, strerror(errno));
return -1;
}
@@ -319,9 +314,9 @@
sbuf->size = htoll(len + 1);
sbuf->id = ID_DATA;
- ret = !WriteFdExactly(fd, sbuf, sizeof(unsigned) * 2 + len + 1);
- if(ret)
+ if (!WriteFdExactly(fd, sbuf, sizeof(unsigned) * 2 + len + 1)) {
return -1;
+ }
total_bytes += len + 1;
@@ -330,7 +325,7 @@
#endif
static int sync_send(int fd, const char *lpath, const char *rpath,
- unsigned mtime, mode_t mode, int show_progress)
+ unsigned mtime, mode_t mode, bool show_progress)
{
syncmsg msg;
int len, r;
@@ -395,26 +390,7 @@
return -1;
}
-static int mkdirs(const char *name)
-{
- int ret;
- char *x = (char *)name + 1;
-
- for(;;) {
- x = adb_dirstart(x);
- if(x == 0) return 0;
- *x = 0;
- ret = adb_mkdir(name, 0775);
- *x = OS_PATH_SEPARATOR;
- if((ret < 0) && (errno != EEXIST)) {
- return ret;
- }
- x++;
- }
- return 0;
-}
-
-static int sync_recv(int fd, const char* rpath, const char* lpath, int show_progress) {
+static int sync_recv(int fd, const char* rpath, const char* lpath, bool show_progress) {
syncmsg msg;
int len;
int lfd = -1;
@@ -541,12 +517,12 @@
return 1;
}
- if(sync_ls(fd, path, do_sync_ls_cb, 0)) {
+ if (sync_ls(fd, path, do_sync_ls_cb, 0)) {
return 1;
- } else {
- sync_quit(fd);
- return 0;
}
+
+ sync_quit(fd);
+ return 0;
}
struct copyinfo
@@ -730,11 +706,7 @@
}
-int do_sync_push(const char *lpath, const char *rpath, int show_progress)
-{
- struct stat st;
- unsigned mode;
-
+int do_sync_push(const char* lpath, const char* rpath, bool show_progress) {
std::string error;
int fd = adb_connect("sync:", &error);
if (fd < 0) {
@@ -742,51 +714,38 @@
return 1;
}
- if(stat(lpath, &st)) {
+ struct stat st;
+ if (stat(lpath, &st)) {
fprintf(stderr,"cannot stat '%s': %s\n", lpath, strerror(errno));
sync_quit(fd);
return 1;
}
- if(S_ISDIR(st.st_mode)) {
+ if (S_ISDIR(st.st_mode)) {
BEGIN();
- if(copy_local_dir_remote(fd, lpath, rpath, 0, 0)) {
+ if (copy_local_dir_remote(fd, lpath, rpath, 0, 0)) {
return 1;
- } else {
- END();
- sync_quit(fd);
}
} else {
- if(sync_readmode(fd, rpath, &mode)) {
+ unsigned mode;
+ if (sync_readmode(fd, rpath, &mode)) {
return 1;
}
- if((mode != 0) && S_ISDIR(mode)) {
- /* if we're copying a local file to a remote directory,
- ** we *really* want to copy to remotedir + "/" + localfilename
- */
- const char *name = adb_dirstop(lpath);
- if(name == 0) {
- name = lpath;
- } else {
- name++;
- }
- int tmplen = strlen(name) + strlen(rpath) + 2;
- char *tmp = reinterpret_cast<char*>(
- malloc(strlen(name) + strlen(rpath) + 2));
- if(tmp == 0) return 1;
- snprintf(tmp, tmplen, "%s/%s", rpath, name);
- rpath = tmp;
+ std::string path_holder;
+ if ((mode != 0) && S_ISDIR(mode)) {
+ // If we're copying a local file to a remote directory,
+ // we really want to copy to remote_dir + "/" + local_filename.
+ path_holder = android::base::StringPrintf("%s/%s", rpath, adb_basename(lpath).c_str());
+ rpath = path_holder.c_str();
}
BEGIN();
- if(sync_send(fd, lpath, rpath, st.st_mtime, st.st_mode, show_progress)) {
+ if (sync_send(fd, lpath, rpath, st.st_mtime, st.st_mode, show_progress)) {
return 1;
- } else {
- END();
- sync_quit(fd);
- return 0;
}
}
+ END();
+ sync_quit(fd);
return 0;
}
@@ -952,11 +911,7 @@
return ret;
}
-int do_sync_pull(const char *rpath, const char *lpath, int show_progress, int copy_attrs)
-{
- unsigned mode, time;
- struct stat st;
-
+int do_sync_pull(const char* rpath, const char* lpath, bool show_progress, int copy_attrs) {
std::string error;
int fd = adb_connect("sync:", &error);
if (fd < 0) {
@@ -964,56 +919,46 @@
return 1;
}
- if(sync_readtime(fd, rpath, &time, &mode)) {
+ unsigned mode, time;
+ if (sync_readtime(fd, rpath, &time, &mode)) {
return 1;
}
- if(mode == 0) {
+ if (mode == 0) {
fprintf(stderr,"remote object '%s' does not exist\n", rpath);
return 1;
}
- if(S_ISREG(mode) || S_ISLNK(mode) || S_ISCHR(mode) || S_ISBLK(mode)) {
- if(stat(lpath, &st) == 0) {
- if(S_ISDIR(st.st_mode)) {
- /* if we're copying a remote file to a local directory,
- ** we *really* want to copy to localdir + "/" + remotefilename
- */
- const char *name = adb_dirstop(rpath);
- if(name == 0) {
- name = rpath;
- } else {
- name++;
- }
- int tmplen = strlen(name) + strlen(lpath) + 2;
- char *tmp = reinterpret_cast<char*>(malloc(tmplen));
- if(tmp == 0) return 1;
- snprintf(tmp, tmplen, "%s/%s", lpath, name);
- lpath = tmp;
+ if (S_ISREG(mode) || S_ISLNK(mode) || S_ISCHR(mode) || S_ISBLK(mode)) {
+ std::string path_holder;
+ struct stat st;
+ if (stat(lpath, &st) == 0) {
+ if (S_ISDIR(st.st_mode)) {
+ // If we're copying a remote file to a local directory,
+ // we really want to copy to local_dir + "/" + basename(remote).
+ path_holder = android::base::StringPrintf("%s/%s", lpath, adb_basename(rpath).c_str());
+ lpath = path_holder.c_str();
}
}
BEGIN();
if (sync_recv(fd, rpath, lpath, show_progress)) {
return 1;
} else {
- if (copy_attrs && set_time_and_mode(lpath, time, mode))
+ if (copy_attrs && set_time_and_mode(lpath, time, mode)) {
return 1;
- END();
- sync_quit(fd);
- return 0;
+ }
}
} else if(S_ISDIR(mode)) {
BEGIN();
if (copy_remote_dir_local(fd, rpath, lpath, copy_attrs)) {
return 1;
- } else {
- END();
- sync_quit(fd);
- return 0;
}
} else {
fprintf(stderr,"remote object '%s' not a file or directory\n", rpath);
return 1;
}
+ END();
+ sync_quit(fd);
+ return 0;
}
int do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only)
@@ -1030,9 +975,8 @@
BEGIN();
if (copy_local_dir_remote(fd, lpath.c_str(), rpath.c_str(), 1, list_only)) {
return 1;
- } else {
- END();
- sync_quit(fd);
- return 0;
}
+ END();
+ sync_quit(fd);
+ return 0;
}
diff --git a/adb/file_sync_service.cpp b/adb/file_sync_service.cpp
index e8e9a0f..ea019f4 100644
--- a/adb/file_sync_service.cpp
+++ b/adb/file_sync_service.cpp
@@ -34,47 +34,46 @@
#include "adb_io.h"
#include "private/android_filesystem_config.h"
-static bool should_use_fs_config(const char* path) {
+#include <base/strings.h>
+
+static bool should_use_fs_config(const std::string& path) {
// TODO: use fs_config to configure permissions on /data.
- return strncmp("/system/", path, strlen("/system/")) == 0 ||
- strncmp("/vendor/", path, strlen("/vendor/")) == 0 ||
- strncmp("/oem/", path, strlen("/oem/")) == 0;
+ return android::base::StartsWith(path, "/system/") ||
+ android::base::StartsWith(path, "/vendor/") ||
+ android::base::StartsWith(path, "/oem/");
}
-static int mkdirs(char *name)
-{
- int ret;
- char *x = name + 1;
+static bool secure_mkdirs(const std::string& path) {
uid_t uid = -1;
gid_t gid = -1;
unsigned int mode = 0775;
uint64_t cap = 0;
- if(name[0] != '/') return -1;
+ if (path[0] != '/') return false;
- for(;;) {
- x = adb_dirstart(x);
- if(x == 0) return 0;
- *x = 0;
- if (should_use_fs_config(name)) {
- fs_config(name, 1, &uid, &gid, &mode, &cap);
+ std::vector<std::string> path_components = android::base::Split(path, "/");
+ path_components.pop_back(); // For "/system/bin/sh", only create "/system/bin".
+
+ std::string partial_path;
+ for (auto& path_component : path_components) {
+ if (partial_path.back() != OS_PATH_SEPARATOR) partial_path += OS_PATH_SEPARATOR;
+ partial_path += path_component;
+
+ if (should_use_fs_config(partial_path)) {
+ fs_config(partial_path.c_str(), 1, &uid, &gid, &mode, &cap);
}
- ret = adb_mkdir(name, mode);
- if((ret < 0) && (errno != EEXIST)) {
- D("mkdir(\"%s\") -> %s\n", name, strerror(errno));
- *x = '/';
- return ret;
- } else if(ret == 0) {
- ret = chown(name, uid, gid);
- if (ret < 0) {
- *x = '/';
- return ret;
+ if (adb_mkdir(partial_path.c_str(), mode) == -1) {
+ if (errno != EEXIST) {
+ return false;
}
- selinux_android_restorecon(name, 0);
+ } else {
+ if (chown(partial_path.c_str(), uid, gid) == -1) {
+ return false;
+ }
+ selinux_android_restorecon(partial_path.c_str(), 0);
}
- *x++ = '/';
}
- return 0;
+ return true;
}
static int do_stat(int s, const char *path)
@@ -99,26 +98,24 @@
static int do_list(int s, const char *path)
{
- DIR *d;
struct dirent *de;
struct stat st;
- syncmsg msg;
- int len;
char tmp[1024 + 256 + 1];
char *fname;
- len = strlen(path);
+ size_t len = strlen(path);
memcpy(tmp, path, len);
tmp[len] = '/';
fname = tmp + len + 1;
+ syncmsg msg;
msg.dent.id = ID_DENT;
- d = opendir(path);
- if(d == 0) goto done;
+ std::unique_ptr<DIR, int(*)(DIR*)> d(opendir(path), closedir);
+ if (!d) goto done;
- while((de = readdir(d))) {
+ while ((de = readdir(d.get()))) {
int len = strlen(de->d_name);
/* not supposed to be possible, but
@@ -134,14 +131,11 @@
if(!WriteFdExactly(s, &msg.dent, sizeof(msg.dent)) ||
!WriteFdExactly(s, de->d_name, len)) {
- closedir(d);
return -1;
}
}
}
- closedir(d);
-
done:
msg.dent.id = ID_DONE;
msg.dent.mode = 0;
@@ -182,7 +176,7 @@
fd = adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode);
if(fd < 0 && errno == ENOENT) {
- if(mkdirs(path) != 0) {
+ if (!secure_mkdirs(path)) {
if(fail_errno(s))
return -1;
fd = -1;
@@ -294,7 +288,7 @@
ret = symlink(buffer, path);
if(ret && errno == ENOENT) {
- if(mkdirs(path) != 0) {
+ if (!secure_mkdirs(path)) {
fail_errno(s);
return -1;
}
diff --git a/adb/file_sync_service.h b/adb/file_sync_service.h
index 344eb98..1d3e3bd 100644
--- a/adb/file_sync_service.h
+++ b/adb/file_sync_service.h
@@ -68,9 +68,9 @@
void file_sync_service(int fd, void *cookie);
int do_sync_ls(const char *path);
-int do_sync_push(const char *lpath, const char *rpath, int show_progress);
+int do_sync_push(const char *lpath, const char *rpath, bool show_progress);
int do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only);
-int do_sync_pull(const char *rpath, const char *lpath, int show_progress, int pullTime);
+int do_sync_pull(const char *rpath, const char *lpath, bool show_progress, int pullTime);
#define SYNC_DATA_MAX (64*1024)
diff --git a/adb/get_my_path_windows.cpp b/adb/get_my_path_windows.cpp
deleted file mode 100644
index 9d23e1c..0000000
--- a/adb/get_my_path_windows.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2007 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 <assert.h>
-#include <limits.h>
-#include <windows.h>
-
-#include "adb.h"
-
-void get_my_path(char *exe, size_t maxLen)
-{
- char *r;
-
- /* XXX: should be GetModuleFileNameA */
- if (GetModuleFileName(NULL, exe, maxLen) > 0) {
- r = strrchr(exe, '\\');
- if (r != NULL)
- *r = '\0';
- } else {
- exe[0] = '\0';
- }
-}
-
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/mutex_list.h b/adb/mutex_list.h
index ff72751..9003361 100644
--- a/adb/mutex_list.h
+++ b/adb/mutex_list.h
@@ -6,6 +6,7 @@
#ifndef ADB_MUTEX
#error ADB_MUTEX not defined when including this file
#endif
+ADB_MUTEX(dirname_lock)
ADB_MUTEX(socket_list_lock)
ADB_MUTEX(transport_lock)
#if ADB_HOST
diff --git a/adb/protocol.txt b/adb/protocol.txt
index c9d3c24..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.
@@ -133,7 +136,7 @@
---- WRITE(0, remote-id, "data") ----------------------------------------
+--- WRITE(local-id, remote-id, "data") ---------------------------------
The WRITE message sends data to the recipient's stream identified by
remote-id. The payload MUST be <= maxdata in length.
diff --git a/adb/remount_service.cpp b/adb/remount_service.cpp
index 7a3b89a..2893263 100644
--- a/adb/remount_service.cpp
+++ b/adb/remount_service.cpp
@@ -33,9 +33,12 @@
#include "adb_io.h"
#include "adb_utils.h"
#include "cutils/properties.h"
+#include "fs_mgr.h"
+
+const std::string kFstab_Prefix = "/fstab.";
// Returns the device used to mount a directory in /proc/mounts.
-static std::string find_mount(const char* dir) {
+static std::string find_proc_mount(const char* dir) {
std::unique_ptr<FILE, int(*)(FILE*)> fp(setmntent("/proc/mounts", "r"), endmntent);
if (!fp) {
return "";
@@ -50,6 +53,29 @@
return "";
}
+// Returns the device used to mount a directory in the fstab.
+static std::string find_fstab_mount(const char* dir) {
+ char propbuf[PROPERTY_VALUE_MAX];
+
+ property_get("ro.hardware", propbuf, "");
+ std::string fstab_filename = kFstab_Prefix + propbuf;
+ struct fstab* fstab = fs_mgr_read_fstab(fstab_filename.c_str());
+ struct fstab_rec* rec = fs_mgr_get_entry_for_mount_point(fstab, dir);
+ std::string dev = rec ? std::string(rec->blk_device) : "";
+ fs_mgr_free_fstab(fstab);
+ return dev;
+}
+
+// The proc entry for / is full of lies, so check fstab instead.
+// /proc/mounts lists rootfs and /dev/root, neither of which is what we want.
+static std::string find_mount(const char* dir) {
+ if (strcmp(dir, "/") == 0) {
+ return find_fstab_mount(dir);
+ } else {
+ return find_proc_mount(dir);
+ }
+}
+
bool make_block_device_writable(const std::string& dev) {
int fd = unix_open(dev.c_str(), O_RDONLY | O_CLOEXEC);
if (fd == -1) {
@@ -58,7 +84,7 @@
int OFF = 0;
bool result = (ioctl(fd, BLKROSET, &OFF) != -1);
- adb_close(fd);
+ unix_close(fd);
return result;
}
@@ -112,7 +138,13 @@
}
bool success = true;
- success &= remount_partition(fd, "/system");
+ property_get("ro.build.system_root_image", prop_buf, "");
+ bool system_root = !strcmp(prop_buf, "true");
+ if (system_root) {
+ success &= remount_partition(fd, "/");
+ } else {
+ success &= remount_partition(fd, "/system");
+ }
success &= remount_partition(fd, "/vendor");
success &= remount_partition(fd, "/oem");
diff --git a/adb/services.cpp b/adb/services.cpp
index a9edbe5..255be09 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"
@@ -57,6 +59,10 @@
void *cookie;
};
+enum class SubprocessType {
+ kPty,
+ kRaw,
+};
void *service_bootstrap_func(void *x)
{
@@ -205,7 +211,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) {
@@ -252,7 +258,7 @@
*pid = forkpty(&ptm, pts_name, nullptr, nullptr);
if (*pid == -1) {
printf("- fork failed: %s -\n", strerror(errno));
- adb_close(ptm);
+ unix_close(ptm);
return -1;
}
@@ -263,7 +269,7 @@
if (pts == -1) {
fprintf(stderr, "child failed to open pseudo-term slave %s: %s\n",
pts_name, strerror(errno));
- adb_close(ptm);
+ unix_close(ptm);
exit(-1);
}
@@ -272,16 +278,16 @@
termios tattr;
if (tcgetattr(pts, &tattr) == -1) {
fprintf(stderr, "tcgetattr failed: %s\n", strerror(errno));
- adb_close(pts);
- adb_close(ptm);
+ unix_close(pts);
+ unix_close(ptm);
exit(-1);
}
cfmakeraw(&tattr);
if (tcsetattr(pts, TCSADRAIN, &tattr) == -1) {
fprintf(stderr, "tcsetattr failed: %s\n", strerror(errno));
- adb_close(pts);
- adb_close(ptm);
+ unix_close(pts);
+ unix_close(ptm);
exit(-1);
}
}
@@ -290,8 +296,8 @@
dup2(pts, STDOUT_FILENO);
dup2(pts, STDERR_FILENO);
- adb_close(pts);
- adb_close(ptm);
+ unix_close(pts);
+ unix_close(ptm);
execl(cmd, cmd, arg0, arg1, nullptr);
fprintf(stderr, "- exec '%s' failed: %s (%d) -\n",
@@ -317,7 +323,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) {
@@ -387,17 +393,27 @@
}
}
-static int create_subproc_thread(const char *name, bool pty = false) {
+// Starts a subprocess and spawns a thread to wait for the subprocess to finish
+// and trigger the necessary cleanup.
+//
+// |name| is the command to execute in the subprocess; empty string will start
+// an interactive session.
+// |type| selects between a PTY or raw subprocess.
+//
+// Returns an open file descriptor tied to the subprocess stdin/stdout/stderr.
+static int create_subproc_thread(const char *name, SubprocessType type) {
const char *arg0, *arg1;
- if (name == 0 || *name == 0) {
- arg0 = "-"; arg1 = 0;
+ if (*name == '\0') {
+ arg0 = "-";
+ arg1 = nullptr;
} else {
- arg0 = "-c"; arg1 = name;
+ arg0 = "-c";
+ arg1 = name;
}
pid_t pid = -1;
int ret_fd;
- if (pty) {
+ if (type == SubprocessType::kPty) {
ret_fd = create_subproc_pty(SHELL_COMMAND, arg0, arg1, &pid);
} else {
ret_fd = create_subproc_raw(SHELL_COMMAND, arg0, arg1, &pid);
@@ -430,17 +446,19 @@
int port = atoi(name + 4);
name = strchr(name + 4, ':');
if(name == 0) {
- ret = socket_loopback_client(port, SOCK_STREAM);
+ std::string error;
+ ret = network_loopback_client(port, SOCK_STREAM, &error);
if (ret >= 0)
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
}
-#ifndef HAVE_WINSOCK /* winsock doesn't implement unix domain sockets */
+#if !defined(_WIN32) /* winsock doesn't implement unix domain sockets */
} else if(!strncmp(name, "local:", 6)) {
ret = socket_local_client(name + 6,
ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
@@ -461,10 +479,17 @@
ret = create_service_thread(framebuffer_service, 0);
} else if (!strncmp(name, "jdwp:", 5)) {
ret = create_jdwp_connection_fd(atoi(name+5));
- } else if(!HOST && !strncmp(name, "shell:", 6)) {
- ret = create_subproc_thread(name + 6, true);
- } else if(!HOST && !strncmp(name, "exec:", 5)) {
- ret = create_subproc_thread(name + 5);
+ } else if(!strncmp(name, "shell:", 6)) {
+ const char* args = name + 6;
+ if (*args) {
+ // Non-interactive session uses a raw subprocess.
+ ret = create_subproc_thread(args, SubprocessType::kRaw);
+ } else {
+ // Interactive session uses a PTY subprocess.
+ ret = create_subproc_thread(args, SubprocessType::kPty);
+ }
+ } else if(!strncmp(name, "exec:", 5)) {
+ ret = create_subproc_thread(name + 5, SubprocessType::kRaw);
} else if(!strncmp(name, "sync:", 5)) {
ret = create_service_thread(file_sync_service, NULL);
} else if(!strncmp(name, "remount:", 8)) {
@@ -478,14 +503,17 @@
} else if(!strncmp(name, "unroot:", 7)) {
ret = create_service_thread(restart_unroot_service, NULL);
} else if(!strncmp(name, "backup:", 7)) {
- ret = create_subproc_thread(android::base::StringPrintf("/system/bin/bu backup %s",
- (name + 7)).c_str());
+ ret = create_subproc_thread(
+ android::base::StringPrintf("/system/bin/bu backup %s",
+ (name + 7)).c_str(),
+ SubprocessType::kRaw);
} else if(!strncmp(name, "restore:", 8)) {
- ret = create_subproc_thread("/system/bin/bu restore");
+ ret = create_subproc_thread("/system/bin/bu restore",
+ SubprocessType::kRaw);
} 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)) {
@@ -528,7 +556,7 @@
std::string error_msg = "unknown error";
atransport* t = acquire_one_transport(sinfo->state, sinfo->transport_type, sinfo->serial,
&error_msg);
- if (t != 0) {
+ if (t != nullptr) {
SendOkay(fd);
} else {
SendFail(fd, error_msg);
@@ -541,35 +569,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 +640,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 0aa1570..6f3c443 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -24,6 +24,10 @@
# undef _WIN32
#endif
+#include <errno.h>
+
+#include <string>
+
/*
* 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
@@ -39,21 +43,39 @@
_rc; })
#endif
+// Some printf-like functions are implemented in terms of
+// android::base::StringAppendV, so they should use the same attribute for
+// compile-time format string checking. On Windows, if the mingw version of
+// vsnprintf is used in StringAppendV, use `gnu_printf' which allows z in %zd
+// and PRIu64 (and related) to be recognized by the compile-time checking.
+#define ADB_FORMAT_ARCHETYPE __printf__
+#ifdef __USE_MINGW_ANSI_STDIO
+#if __USE_MINGW_ANSI_STDIO
+#undef ADB_FORMAT_ARCHETYPE
+#define ADB_FORMAT_ARCHETYPE gnu_printf
+#endif
+#endif
+
#ifdef _WIN32
#include <ctype.h>
#include <direct.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>
#include <sys/stat.h>
+#include <utime.h>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
+#include <string> // Prototypes for narrow() and widen() use std::(w)string.
+
#include "fdevent.h"
+#define OS_PATH_SEPARATORS "\\/"
#define OS_PATH_SEPARATOR '\\'
#define OS_PATH_SEPARATOR_STR "\\"
#define ENV_PATH_SEPARATOR_STR ";"
@@ -102,29 +124,15 @@
#define S_ISLNK(m) 0 /* no symlinks on Win32 */
-static __inline__ int adb_unlink(const char* path)
-{
- int rc = unlink(path);
-
- if (rc == -1 && errno == EACCES) {
- /* unlink returns EACCES when the file is read-only, so we first */
- /* try to make it writable, then unlink again... */
- rc = chmod(path, _S_IREAD|_S_IWRITE );
- if (rc == 0)
- rc = unlink(path);
- }
- return rc;
-}
+extern int adb_unlink(const char* path);
#undef unlink
#define unlink ___xxx_unlink
-static __inline__ int adb_mkdir(const char* path, int mode)
-{
- return _mkdir(path);
-}
+extern int adb_mkdir(const std::string& path, int mode);
#undef mkdir
#define mkdir ___xxx_mkdir
+// See the comments for the !defined(_WIN32) versions of adb_*().
extern int adb_open(const char* path, int options);
extern int adb_creat(const char* path, int mode);
extern int adb_read(int fd, void* buf, int len);
@@ -133,6 +141,7 @@
extern int adb_shutdown(int fd);
extern int adb_close(int fd);
+// See the comments for the !defined(_WIN32) version of unix_close().
static __inline__ int unix_close(int fd)
{
return close(fd);
@@ -140,11 +149,13 @@
#undef close
#define close ____xxx_close
+// See the comments for the !defined(_WIN32) version of unix_read().
extern int unix_read(int fd, void* buf, size_t len);
#undef read
#define read ___xxx_read
+// See the comments for the !defined(_WIN32) version of unix_write().
static __inline__ int unix_write(int fd, const void* buf, size_t len)
{
return write(fd, buf, len);
@@ -152,41 +163,20 @@
#undef write
#define write ___xxx_write
+// See the comments for the !defined(_WIN32) version of adb_open_mode().
static __inline__ int adb_open_mode(const char* path, int options, int mode)
{
return adb_open(path, options);
}
-static __inline__ int unix_open(const char* path, int options,...)
-{
- if ((options & O_CREAT) == 0)
- {
- return open(path, options);
- }
- else
- {
- int mode;
- va_list args;
- va_start( args, options );
- mode = va_arg( args, int );
- va_end( args );
- return open(path, options, mode);
- }
-}
+// See the comments for the !defined(_WIN32) version of unix_open().
+extern int unix_open(const char* path, int options, ...);
#define open ___xxx_unix_open
/* 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
@@ -210,6 +200,12 @@
Sleep( mseconds );
}
+int network_loopback_client(int port, int type, std::string* error);
+int network_loopback_server(int port, int type, std::string* error);
+int network_inaddr_any_server(int port, int type, std::string* error);
+int network_connect(const std::string& host, int port, int type, int timeout,
+ std::string* error);
+
extern int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen);
#undef accept
@@ -234,42 +230,137 @@
extern int adb_socketpair( int sv[2] );
-static __inline__ char* adb_dirstart( const char* path )
-{
- char* p = strchr(path, '/');
- char* p2 = strchr(path, '\\');
-
- if ( !p )
- p = p2;
- else if ( p2 && p2 > p )
- p = p2;
-
- return p;
-}
-
-static __inline__ char* adb_dirstop( const char* path )
-{
- char* p = strrchr(path, '/');
- char* p2 = strrchr(path, '\\');
-
- if ( !p )
- p = p2;
- else if ( p2 && p2 > p )
- p = p2;
-
- return p;
-}
-
-static __inline__ int adb_is_absolute_host_path( const char* path )
-{
+static __inline__ int adb_is_absolute_host_path(const char* path) {
return isalpha(path[0]) && path[1] == ':' && path[2] == '\\';
}
+// Like strerror(), but for Win32 error codes.
+std::string SystemErrorCodeToString(DWORD error_code);
+
+// We later define a macro mapping 'stat' to 'adb_stat'. This causes:
+// struct stat s;
+// stat(filename, &s);
+// To turn into the following:
+// struct adb_stat s;
+// adb_stat(filename, &s);
+// To get this to work, we need to make 'struct adb_stat' the same as
+// 'struct stat'. Note that this definition of 'struct adb_stat' uses the
+// *current* macro definition of stat, so it may actually be inheriting from
+// struct _stat32i64 (or some other remapping).
+struct adb_stat : public stat {};
+
+static_assert(sizeof(struct adb_stat) == sizeof(struct stat),
+ "structures should be the same");
+
+extern int adb_stat(const char* f, struct adb_stat* s);
+
+// stat is already a macro, undefine it so we can redefine it.
+#undef stat
+#define stat adb_stat
+
+// UTF-8 versions of POSIX APIs.
+extern DIR* adb_opendir(const char* dirname);
+extern struct dirent* adb_readdir(DIR* dir);
+extern int adb_closedir(DIR* dir);
+
+extern int adb_utime(const char *, struct utimbuf *);
+extern int adb_chmod(const char *, int);
+
+extern int adb_vfprintf(FILE *stream, const char *format, va_list ap)
+ __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 0)));
+extern int adb_fprintf(FILE *stream, const char *format, ...)
+ __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3)));
+extern int adb_printf(const char *format, ...)
+ __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 1, 2)));
+
+extern int adb_fputs(const char* buf, FILE* stream);
+extern int adb_fputc(int ch, FILE* stream);
+extern size_t adb_fwrite(const void* ptr, size_t size, size_t nmemb,
+ FILE* stream);
+
+extern FILE* adb_fopen(const char* f, const char* m);
+
+extern char* adb_getenv(const char* name);
+
+extern char* adb_getcwd(char* buf, int size);
+
+// Remap calls to POSIX APIs to our UTF-8 versions.
+#define opendir adb_opendir
+#define readdir adb_readdir
+#define closedir adb_closedir
+#define rewinddir rewinddir_utf8_not_yet_implemented
+#define telldir telldir_utf8_not_yet_implemented
+#define seekdir seekdir_utf8_not_yet_implemented
+
+#define utime adb_utime
+#define chmod adb_chmod
+
+#define vfprintf adb_vfprintf
+#define fprintf adb_fprintf
+#define printf adb_printf
+#define fputs adb_fputs
+#define fputc adb_fputc
+#define fwrite adb_fwrite
+
+#define fopen adb_fopen
+
+#define getenv adb_getenv
+#define putenv putenv_utf8_not_yet_implemented
+#define setenv setenv_utf8_not_yet_implemented
+#define unsetenv unsetenv_utf8_not_yet_implemented
+
+#define getcwd adb_getcwd
+
+// Convert from UTF-8 to UTF-16, typically used to convert char strings into
+// wchar_t strings that can be passed to wchar_t-based OS and C Runtime APIs
+// on Windows.
+extern std::wstring widen(const std::string& utf8);
+extern std::wstring widen(const char* utf8);
+
+// Convert from UTF-16 to UTF-8, typically used to convert strings from OS and
+// C Runtime APIs that return wchar_t, to a format for our char-based data
+// structures.
+extern std::string narrow(const std::wstring& utf16);
+extern std::string narrow(const wchar_t* utf16);
+
+// Helper class to convert UTF-16 argv from wmain() to UTF-8 args that can be
+// passed to main().
+class NarrowArgs {
+public:
+ NarrowArgs(int argc, wchar_t** argv);
+ ~NarrowArgs();
+
+ inline char** data() {
+ return narrow_args;
+ }
+
+private:
+ char** narrow_args;
+};
+
+// Windows HANDLE values only use 32-bits of the type, even on 64-bit machines,
+// so they can fit in an int. To convert back, we just need to sign-extend.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203%28v=vs.85%29.aspx
+// Note that this does not make a HANDLE value work with APIs like open(), nor
+// does this make a value from open() passable to APIs taking a HANDLE. This
+// just lets you take a HANDLE, pass it around as an int, and then use it again
+// as a HANDLE.
+inline int cast_handle_to_int(const HANDLE h) {
+ // truncate
+ return static_cast<int>(reinterpret_cast<INT_PTR>(h));
+}
+
+inline HANDLE cast_int_to_handle(const int fd) {
+ // sign-extend
+ return reinterpret_cast<HANDLE>(static_cast<INT_PTR>(fd));
+}
+
#else /* !_WIN32 a.k.a. Unix */
#include "fdevent.h"
-#include <cutils/sockets.h>
#include <cutils/misc.h>
+#include <cutils/sockets.h>
+#include <cutils/threads.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
@@ -279,11 +370,15 @@
#include <unistd.h>
#include <fcntl.h>
#include <stdarg.h>
+#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <unistd.h>
+#include <string>
+
+#define OS_PATH_SEPARATORS "/"
#define OS_PATH_SEPARATOR '/'
#define OS_PATH_SEPARATOR_STR "/"
#define ENV_PATH_SEPARATOR_STR ":"
@@ -314,6 +409,15 @@
fcntl( fd, F_SETFD, FD_CLOEXEC );
}
+// Open a file and return a file descriptor that may be used with unix_read(),
+// unix_write(), unix_close(), but not adb_read(), adb_write(), adb_close().
+//
+// On Unix, this is based on open(), so the file descriptor is a real OS file
+// descriptor, but the Windows implementation (in sysdeps_win32.cpp) returns a
+// file descriptor that can only be used with C Runtime APIs (which are wrapped
+// by unix_read(), unix_write(), unix_close()). Also, the C Runtime has
+// configurable CR/LF translation which defaults to text mode, but is settable
+// with _setmode().
static __inline__ int unix_open(const char* path, int options,...)
{
if ((options & O_CREAT) == 0)
@@ -331,12 +435,21 @@
}
}
+// Similar to the two-argument adb_open(), but takes a mode parameter for file
+// creation. See adb_open() for more info.
static __inline__ int adb_open_mode( const char* pathname, int options, int mode )
{
return TEMP_FAILURE_RETRY( open( pathname, options, mode ) );
}
+// Open a file and return a file descriptor that may be used with adb_read(),
+// adb_write(), adb_close(), but not unix_read(), unix_write(), unix_close().
+//
+// On Unix, this is based on open(), but the Windows implementation (in
+// sysdeps_win32.cpp) uses Windows native file I/O and bypasses the C Runtime
+// and its CR/LF translation. The returned file descriptor should be used with
+// adb_read(), adb_write(), adb_close(), etc.
static __inline__ int adb_open( const char* pathname, int options )
{
int fd = TEMP_FAILURE_RETRY( open( pathname, options ) );
@@ -355,6 +468,9 @@
#undef shutdown
#define shutdown ____xxx_shutdown
+// Closes a file descriptor that came from adb_open() or adb_open_mode(), but
+// not designed to take a file descriptor from unix_open(). See the comments
+// for adb_open() for more info.
static __inline__ int adb_close(int fd)
{
return close(fd);
@@ -405,6 +521,48 @@
#undef creat
#define creat ___xxx_creat
+// Helper for network_* functions.
+inline int _fd_set_error_str(int fd, std::string* error) {
+ if (fd == -1) {
+ *error = strerror(errno);
+ }
+ return fd;
+}
+
+inline int network_loopback_client(int port, int type, std::string* error) {
+ return _fd_set_error_str(socket_loopback_client(port, type), error);
+}
+
+inline int network_loopback_server(int port, int type, std::string* error) {
+ return _fd_set_error_str(socket_loopback_server(port, type), error);
+}
+
+inline int network_inaddr_any_server(int port, int type, std::string* error) {
+ return _fd_set_error_str(socket_inaddr_any_server(port, type), error);
+}
+
+inline int network_local_server(const char *name, int namespace_id, int type,
+ std::string* error) {
+ return _fd_set_error_str(socket_local_server(name, namespace_id, type),
+ error);
+}
+
+inline 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) {
+ *error = gai_strerror(getaddrinfo_error);
+ } else {
+ *error = strerror(errno);
+ }
+ return -1;
+}
+
static __inline__ int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen)
{
int fd;
@@ -419,6 +577,14 @@
#undef accept
#define accept ___xxx_accept
+// Operate on a file descriptor returned from unix_open() or a well-known file
+// descriptor such as STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO.
+//
+// On Unix, unix_read(), unix_write(), unix_close() map to adb_read(),
+// adb_write(), adb_close() (which all map to Unix system calls), but the
+// Windows implementations (in the ifdef above and in sysdeps_win32.cpp) call
+// into the C Runtime and its configurable CR/LF translation (which is settable
+// via _setmode()).
#define unix_read adb_read
#define unix_write adb_write
#define unix_close adb_close
@@ -481,10 +647,11 @@
usleep( mseconds*1000 );
}
-static __inline__ int adb_mkdir(const char* path, int mode)
+static __inline__ int adb_mkdir(const std::string& path, int mode)
{
- return mkdir(path, mode);
+ return mkdir(path.c_str(), mode);
}
+
#undef mkdir
#define mkdir ___xxx_mkdir
@@ -492,24 +659,13 @@
{
}
-static __inline__ char* adb_dirstart(const char* path)
-{
- return strchr(path, '/');
-}
-
-static __inline__ char* adb_dirstop(const char* path)
-{
- return strrchr(path, '/');
-}
-
-static __inline__ int adb_is_absolute_host_path( const char* path )
-{
+static __inline__ int adb_is_absolute_host_path(const char* path) {
return path[0] == '/';
}
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 a21272f..f534d61 100644
--- a/adb/sysdeps_win32.cpp
+++ b/adb/sysdeps_win32.cpp
@@ -25,6 +25,16 @@
#include <stdio.h>
#include <stdlib.h>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include <cutils/sockets.h>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+#include <base/strings.h>
+
#include "adb.h"
extern void fatal(const char *fmt, ...);
@@ -78,6 +88,30 @@
#define assert(cond) do { if (!(cond)) fatal( "assertion failed '%s' on %s:%ld\n", #cond, __FILE__, __LINE__ ); } while (0)
+std::string SystemErrorCodeToString(const DWORD error_code) {
+ const int kErrorMessageBufferSize = 256;
+ WCHAR msgbuf[kErrorMessageBufferSize];
+ DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
+ DWORD len = FormatMessageW(flags, nullptr, error_code, 0, msgbuf,
+ arraysize(msgbuf), nullptr);
+ if (len == 0) {
+ return android::base::StringPrintf(
+ "Error (%lu) while retrieving error. (%lu)", GetLastError(),
+ error_code);
+ }
+
+ // Convert UTF-16 to UTF-8.
+ std::string msg(narrow(msgbuf));
+ // Messages returned by the system end with line breaks.
+ msg = android::base::Trim(msg);
+ // There are many Windows error messages compared to POSIX, so include the
+ // numeric error code for easier, quicker, accurate identification. Use
+ // decimal instead of hex because there are decimal ranges like 10000-11999
+ // for Winsock.
+ android::base::StringAppendF(&msg, " (%lu)", error_code);
+ return msg;
+}
+
/**************************************************************************/
/**************************************************************************/
/***** *****/
@@ -92,13 +126,13 @@
char *data;
DWORD file_size;
- file = CreateFile( fn,
- GENERIC_READ,
- FILE_SHARE_READ,
- NULL,
- OPEN_EXISTING,
- 0,
- NULL );
+ file = CreateFileW( widen(fn).c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL );
if (file == INVALID_HANDLE_VALUE)
return NULL;
@@ -169,17 +203,18 @@
static adb_mutex_t _win32_lock;
static FHRec _win32_fhs[ WIN32_MAX_FHS ];
-static int _win32_fh_count;
+static int _win32_fh_next; // where to start search for free FHRec
static FH
-_fh_from_int( int fd )
+_fh_from_int( int fd, const char* func )
{
FH f;
fd -= WIN32_FH_BASE;
- if (fd < 0 || fd >= _win32_fh_count) {
- D( "_fh_from_int: invalid fd %d\n", fd + WIN32_FH_BASE );
+ if (fd < 0 || fd >= WIN32_MAX_FHS) {
+ D( "_fh_from_int: invalid fd %d passed to %s\n", fd + WIN32_FH_BASE,
+ func );
errno = EBADF;
return NULL;
}
@@ -187,7 +222,8 @@
f = &_win32_fhs[fd];
if (f->used == 0) {
- D( "_fh_from_int: invalid fd %d\n", fd + WIN32_FH_BASE );
+ D( "_fh_from_int: invalid fd %d passed to %s\n", fd + WIN32_FH_BASE,
+ func );
errno = EBADF;
return NULL;
}
@@ -208,28 +244,32 @@
static FH
_fh_alloc( FHClass clazz )
{
- int nn;
FH f = NULL;
adb_mutex_lock( &_win32_lock );
- if (_win32_fh_count < WIN32_MAX_FHS) {
- f = &_win32_fhs[ _win32_fh_count++ ];
- goto Exit;
- }
-
- for (nn = 0; nn < WIN32_MAX_FHS; nn++) {
- if ( _win32_fhs[nn].clazz == NULL) {
- f = &_win32_fhs[nn];
+ // Search entire array, starting from _win32_fh_next.
+ for (int nn = 0; nn < WIN32_MAX_FHS; nn++) {
+ // Keep incrementing _win32_fh_next to avoid giving out an index that
+ // was recently closed, to try to avoid use-after-free.
+ const int index = _win32_fh_next++;
+ // Handle wrap-around of _win32_fh_next.
+ if (_win32_fh_next == WIN32_MAX_FHS) {
+ _win32_fh_next = 0;
+ }
+ if (_win32_fhs[index].clazz == NULL) {
+ f = &_win32_fhs[index];
goto Exit;
}
}
D( "_fh_alloc: no more free file descriptors\n" );
+ errno = EMFILE; // Too many open files
Exit:
if (f) {
- f->clazz = clazz;
- f->used = 1;
- f->eof = 0;
+ f->clazz = clazz;
+ f->used = 1;
+ f->eof = 0;
+ f->name[0] = '\0';
clazz->_fh_init(f);
}
adb_mutex_unlock( &_win32_lock );
@@ -240,15 +280,37 @@
static int
_fh_close( FH f )
{
- if ( f->used ) {
+ // Use lock so that closing only happens once and so that _fh_alloc can't
+ // allocate a FH that we're in the middle of closing.
+ adb_mutex_lock(&_win32_lock);
+ if (f->used) {
f->clazz->_fh_close( f );
- f->used = 0;
- f->eof = 0;
- f->clazz = NULL;
+ f->name[0] = '\0';
+ f->eof = 0;
+ f->used = 0;
+ f->clazz = NULL;
}
+ adb_mutex_unlock(&_win32_lock);
return 0;
}
+// Deleter for unique_fh.
+class fh_deleter {
+ public:
+ void operator()(struct FHRec_* fh) {
+ // We're called from a destructor and destructors should not overwrite
+ // errno because callers may do:
+ // errno = EBLAH;
+ // return -1; // calls destructor, which should not overwrite errno
+ const int saved_errno = errno;
+ _fh_close(fh);
+ errno = saved_errno;
+ }
+};
+
+// Like std::unique_ptr, but calls _fh_close() instead of operator delete().
+typedef std::unique_ptr<struct FHRec_, fh_deleter> unique_fh;
+
/**************************************************************************/
/**************************************************************************/
/***** *****/
@@ -351,17 +413,17 @@
f = _fh_alloc( &_fh_file_class );
if ( !f ) {
- errno = ENOMEM;
return -1;
}
- f->fh_handle = CreateFile( path, desiredAccess, shareMode, NULL, OPEN_EXISTING,
- 0, NULL );
+ f->fh_handle = CreateFileW( widen(path).c_str(), desiredAccess, shareMode,
+ NULL, OPEN_EXISTING, 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;
@@ -373,7 +435,8 @@
return -1;
default:
- D( "unknown error\n" );
+ D( "unknown error: %s\n",
+ SystemErrorCodeToString( err ).c_str() );
errno = ENOENT;
return -1;
}
@@ -391,18 +454,19 @@
f = _fh_alloc( &_fh_file_class );
if ( !f ) {
- errno = ENOMEM;
return -1;
}
- f->fh_handle = CreateFile( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
- NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
- NULL );
+ f->fh_handle = CreateFileW( widen(path).c_str(), GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
+ 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;
@@ -414,7 +478,8 @@
return -1;
default:
- D( "unknown error\n" );
+ D( "unknown error: %s\n",
+ SystemErrorCodeToString( err ).c_str() );
errno = ENOENT;
return -1;
}
@@ -427,7 +492,7 @@
int adb_read(int fd, void* buf, int len)
{
- FH f = _fh_from_int(fd);
+ FH f = _fh_from_int(fd, __func__);
if (f == NULL) {
return -1;
@@ -439,7 +504,7 @@
int adb_write(int fd, const void* buf, int len)
{
- FH f = _fh_from_int(fd);
+ FH f = _fh_from_int(fd, __func__);
if (f == NULL) {
return -1;
@@ -451,7 +516,7 @@
int adb_lseek(int fd, int pos, int where)
{
- FH f = _fh_from_int(fd);
+ FH f = _fh_from_int(fd, __func__);
if (!f) {
return -1;
@@ -461,24 +526,9 @@
}
-int adb_shutdown(int fd)
-{
- FH f = _fh_from_int(fd);
-
- if (!f || f->clazz != &_fh_socket_class) {
- D("adb_shutdown: invalid fd %d\n", fd);
- return -1;
- }
-
- D( "adb_shutdown: %s\n", f->name);
- shutdown( f->fh_socket, SD_BOTH );
- return 0;
-}
-
-
int adb_close(int fd)
{
- FH f = _fh_from_int(fd);
+ FH f = _fh_from_int(fd, __func__);
if (!f) {
return -1;
@@ -499,29 +549,66 @@
#undef setsockopt
-static void _socket_set_errno( void ) {
- switch (WSAGetLastError()) {
+static void _socket_set_errno( const DWORD err ) {
+ // The Windows C Runtime (MSVCRT.DLL) strerror() does not support a lot of
+ // POSIX and socket error codes, so this can only meaningfully map so much.
+ switch ( err ) {
case 0: errno = 0; break;
+ // Mapping WSAEWOULDBLOCK to EAGAIN is absolutely critical because
+ // non-blocking sockets can cause an error code of WSAEWOULDBLOCK and
+ // callers check specifically for EAGAIN.
case WSAEWOULDBLOCK: errno = EAGAIN; break;
case WSAEINTR: errno = EINTR; break;
+ case WSAEFAULT: errno = EFAULT; break;
+ case WSAEINVAL: errno = EINVAL; break;
+ case WSAEMFILE: errno = EMFILE; break;
default:
- D( "_socket_set_errno: unhandled value %d\n", WSAGetLastError() );
errno = EINVAL;
+ D( "_socket_set_errno: mapping Windows error code %lu to errno %d\n",
+ err, errno );
}
}
static void _fh_socket_init( FH f ) {
f->fh_socket = INVALID_SOCKET;
f->event = WSACreateEvent();
+ if (f->event == WSA_INVALID_EVENT) {
+ D("WSACreateEvent failed: %s\n",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+
+ // _event_socket_start assumes that this field is INVALID_HANDLE_VALUE
+ // on failure, instead of NULL which is what Windows really returns on
+ // error. It might be better to change all the other code to look for
+ // NULL, but that is a much riskier change.
+ f->event = INVALID_HANDLE_VALUE;
+ }
f->mask = 0;
}
static int _fh_socket_close( FH f ) {
- /* gently tell any peer that we're closing the socket */
- shutdown( f->fh_socket, SD_BOTH );
- closesocket( f->fh_socket );
- f->fh_socket = INVALID_SOCKET;
- CloseHandle( f->event );
+ if (f->fh_socket != INVALID_SOCKET) {
+ /* gently tell any peer that we're closing the socket */
+ if (shutdown(f->fh_socket, SD_BOTH) == SOCKET_ERROR) {
+ // If the socket is not connected, this returns an error. We want to
+ // minimize logging spam, so don't log these errors for now.
+#if 0
+ D("socket shutdown failed: %s\n",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+#endif
+ }
+ if (closesocket(f->fh_socket) == SOCKET_ERROR) {
+ D("closesocket failed: %s\n",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+ }
+ f->fh_socket = INVALID_SOCKET;
+ }
+ if (f->event != NULL) {
+ if (!CloseHandle(f->event)) {
+ D("CloseHandle failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ }
+ f->event = NULL;
+ }
f->mask = 0;
return 0;
}
@@ -534,7 +621,14 @@
static int _fh_socket_read(FH f, void* buf, int len) {
int result = recv(f->fh_socket, reinterpret_cast<char*>(buf), len, 0);
if (result == SOCKET_ERROR) {
- _socket_set_errno();
+ const DWORD err = WSAGetLastError();
+ // WSAEWOULDBLOCK is normal with a non-blocking socket, so don't trace
+ // that to reduce spam and confusion.
+ if (err != WSAEWOULDBLOCK) {
+ D("recv fd %d failed: %s\n", _fh_to_int(f),
+ SystemErrorCodeToString(err).c_str());
+ }
+ _socket_set_errno(err);
result = -1;
}
return result;
@@ -543,7 +637,10 @@
static int _fh_socket_write(FH f, const void* buf, int len) {
int result = send(f->fh_socket, reinterpret_cast<const char*>(buf), len, 0);
if (result == SOCKET_ERROR) {
- _socket_set_errno();
+ const DWORD err = WSAGetLastError();
+ D("send fd %d failed: %s\n", _fh_to_int(f),
+ SystemErrorCodeToString(err).c_str());
+ _socket_set_errno(err);
result = -1;
}
return result;
@@ -562,33 +659,44 @@
static int _winsock_init;
static void
-_cleanup_winsock( void )
-{
- WSACleanup();
-}
-
-static void
_init_winsock( void )
{
+ // TODO: Multiple threads calling this may potentially cause multiple calls
+ // to WSAStartup() which offers no real benefit.
if (!_winsock_init) {
WSADATA wsaData;
int rc = WSAStartup( MAKEWORD(2,2), &wsaData);
if (rc != 0) {
- fatal( "adb: could not initialize Winsock\n" );
+ fatal( "adb: could not initialize Winsock: %s",
+ SystemErrorCodeToString( rc ).c_str());
}
- atexit( _cleanup_winsock );
_winsock_init = 1;
+
+ // Note that we do not call atexit() to register WSACleanup to be called
+ // at normal process termination because:
+ // 1) When exit() is called, there are still threads actively using
+ // Winsock because we don't cleanly shutdown all threads, so it
+ // doesn't make sense to call WSACleanup() and may cause problems
+ // with those threads.
+ // 2) A deadlock can occur when exit() holds a C Runtime lock, then it
+ // calls WSACleanup() which tries to unload a DLL, which tries to
+ // grab the LoaderLock. This conflicts with the device_poll_thread
+ // which holds the LoaderLock because AdbWinApi.dll calls
+ // setupapi.dll which tries to load wintrust.dll which tries to load
+ // crypt32.dll which calls atexit() which tries to acquire the C
+ // Runtime lock that the other thread holds.
}
}
-int socket_loopback_client(int port, int type)
-{
- FH f = _fh_alloc( &_fh_socket_class );
+int network_loopback_client(int port, int type, std::string* error) {
struct sockaddr_in addr;
SOCKET s;
- if (!f)
+ unique_fh f(_fh_alloc(&_fh_socket_class));
+ if (!f) {
+ *error = strerror(errno);
return -1;
+ }
if (!_winsock_init)
_init_winsock();
@@ -600,32 +708,45 @@
s = socket(AF_INET, type, 0);
if(s == INVALID_SOCKET) {
- D("socket_loopback_client: could not create socket\n" );
- _fh_close(f);
+ *error = android::base::StringPrintf("cannot create socket: %s",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+ D("%s\n", error->c_str());
+ return -1;
+ }
+ f->fh_socket = s;
+
+ if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR) {
+ // Save err just in case inet_ntoa() or ntohs() changes the last error.
+ const DWORD err = WSAGetLastError();
+ *error = android::base::StringPrintf("cannot connect to %s:%u: %s",
+ inet_ntoa(addr.sin_addr), ntohs(addr.sin_port),
+ SystemErrorCodeToString(err).c_str());
+ D("could not connect to %s:%d: %s\n",
+ type != SOCK_STREAM ? "udp" : "tcp", port, error->c_str());
return -1;
}
- f->fh_socket = s;
- if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
- D("socket_loopback_client: could not connect to %s:%d\n", type != SOCK_STREAM ? "udp" : "tcp", port );
- _fh_close(f);
- return -1;
- }
- snprintf( f->name, sizeof(f->name), "%d(lo-client:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port );
- D( "socket_loopback_client: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) );
- return _fh_to_int(f);
+ const int fd = _fh_to_int(f.get());
+ snprintf( f->name, sizeof(f->name), "%d(lo-client:%s%d)", fd,
+ type != SOCK_STREAM ? "udp:" : "", port );
+ D( "port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp",
+ fd );
+ f.release();
+ return fd;
}
#define LISTEN_BACKLOG 4
-int socket_loopback_server(int port, int type)
-{
- FH f = _fh_alloc( &_fh_socket_class );
+// interface_address is INADDR_LOOPBACK or INADDR_ANY.
+static int _network_server(int port, int type, u_long interface_address,
+ std::string* error) {
struct sockaddr_in addr;
SOCKET s;
int n;
+ unique_fh f(_fh_alloc(&_fh_socket_class));
if (!f) {
+ *error = strerror(errno);
return -1;
}
@@ -635,171 +756,218 @@
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_addr.s_addr = htonl(interface_address);
+ // TODO: Consider using dual-stack socket that can simultaneously listen on
+ // IPv4 and IPv6.
s = socket(AF_INET, type, 0);
- if(s == INVALID_SOCKET) return -1;
+ if (s == INVALID_SOCKET) {
+ *error = android::base::StringPrintf("cannot create socket: %s",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+ D("%s\n", error->c_str());
+ return -1;
+ }
f->fh_socket = s;
+ // Note: SO_REUSEADDR on Windows allows multiple processes to bind to the
+ // same port, so instead use SO_EXCLUSIVEADDRUSE.
n = 1;
- setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&n, sizeof(n));
+ if (setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&n,
+ sizeof(n)) == SOCKET_ERROR) {
+ *error = android::base::StringPrintf(
+ "cannot set socket option SO_EXCLUSIVEADDRUSE: %s",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+ D("%s\n", error->c_str());
+ return -1;
+ }
- if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
- _fh_close(f);
+ if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR) {
+ // Save err just in case inet_ntoa() or ntohs() changes the last error.
+ const DWORD err = WSAGetLastError();
+ *error = android::base::StringPrintf("cannot bind to %s:%u: %s",
+ inet_ntoa(addr.sin_addr), ntohs(addr.sin_port),
+ SystemErrorCodeToString(err).c_str());
+ D("could not bind to %s:%d: %s\n",
+ type != SOCK_STREAM ? "udp" : "tcp", port, error->c_str());
return -1;
}
if (type == SOCK_STREAM) {
- int ret;
-
- ret = listen(s, LISTEN_BACKLOG);
- if (ret < 0) {
- _fh_close(f);
+ if (listen(s, LISTEN_BACKLOG) == SOCKET_ERROR) {
+ *error = android::base::StringPrintf("cannot listen on socket: %s",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+ D("could not listen on %s:%d: %s\n",
+ type != SOCK_STREAM ? "udp" : "tcp", port, error->c_str());
return -1;
}
}
- snprintf( f->name, sizeof(f->name), "%d(lo-server:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port );
- D( "socket_loopback_server: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) );
- return _fh_to_int(f);
+ const int fd = _fh_to_int(f.get());
+ snprintf( f->name, sizeof(f->name), "%d(%s-server:%s%d)", fd,
+ interface_address == INADDR_LOOPBACK ? "lo" : "any",
+ type != SOCK_STREAM ? "udp:" : "", port );
+ D( "port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp",
+ fd );
+ f.release();
+ return fd;
}
+int network_loopback_server(int port, int type, std::string* error) {
+ return _network_server(port, type, INADDR_LOOPBACK, error);
+}
-int socket_network_client(const char *host, int port, int type)
-{
- FH f = _fh_alloc( &_fh_socket_class );
- struct hostent *hp;
- struct sockaddr_in addr;
- SOCKET s;
+int network_inaddr_any_server(int port, int type, std::string* error) {
+ return _network_server(port, type, INADDR_ANY, error);
+}
- if (!f)
- return -1;
-
- if (!_winsock_init)
- _init_winsock();
-
- hp = gethostbyname(host);
- if(hp == 0) {
- _fh_close(f);
+int network_connect(const std::string& host, int port, int type, int timeout, std::string* error) {
+ unique_fh f(_fh_alloc(&_fh_socket_class));
+ if (!f) {
+ *error = strerror(errno);
return -1;
}
- 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);
+ if (!_winsock_init) _init_winsock();
- s = socket(hp->h_addrtype, type, 0);
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = type;
+
+ char port_str[16];
+ snprintf(port_str, sizeof(port_str), "%d", port);
+
+ struct addrinfo* addrinfo_ptr = nullptr;
+
+#if (NTDDI_VERSION >= NTDDI_WINXPSP2) || (_WIN32_WINNT >= _WIN32_WINNT_WS03)
+ // TODO: When the Android SDK tools increases the Windows system
+ // requirements >= WinXP SP2, switch to GetAddrInfoW(widen(host).c_str()).
+#else
+ // Otherwise, keep using getaddrinfo(), or do runtime API detection
+ // with GetProcAddress("GetAddrInfoW").
+#endif
+ if (getaddrinfo(host.c_str(), port_str, &hints, &addrinfo_ptr) != 0) {
+ *error = android::base::StringPrintf(
+ "cannot resolve host '%s' and port %s: %s", host.c_str(),
+ port_str, SystemErrorCodeToString(WSAGetLastError()).c_str());
+ D("%s\n", error->c_str());
+ return -1;
+ }
+ std::unique_ptr<struct addrinfo, decltype(freeaddrinfo)*>
+ addrinfo(addrinfo_ptr, freeaddrinfo);
+ addrinfo_ptr = nullptr;
+
+ // TODO: Try all the addresses if there's more than one? This just uses
+ // the first. Or, could call WSAConnectByName() (Windows Vista and newer)
+ // which tries all addresses, takes a timeout and more.
+ SOCKET s = socket(addrinfo->ai_family, addrinfo->ai_socktype,
+ addrinfo->ai_protocol);
if(s == INVALID_SOCKET) {
- _fh_close(f);
+ *error = android::base::StringPrintf("cannot create socket: %s",
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+ D("%s\n", error->c_str());
return -1;
}
f->fh_socket = s;
- if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
- _fh_close(f);
+ // TODO: Implement timeouts for Windows. Seems like the default in theory
+ // (according to http://serverfault.com/a/671453) and in practice is 21 sec.
+ if(connect(s, addrinfo->ai_addr, addrinfo->ai_addrlen) == SOCKET_ERROR) {
+ // TODO: Use WSAAddressToString or inet_ntop on address.
+ *error = android::base::StringPrintf("cannot connect to %s:%s: %s",
+ host.c_str(), port_str,
+ SystemErrorCodeToString(WSAGetLastError()).c_str());
+ D("could not connect to %s:%s:%s: %s\n",
+ type != SOCK_STREAM ? "udp" : "tcp", host.c_str(), port_str,
+ error->c_str());
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) );
- 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 );
- struct sockaddr_in addr;
- SOCKET s;
- int n;
-
- if (!f)
- return -1;
-
- if (!_winsock_init)
- _init_winsock();
-
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- s = socket(AF_INET, type, 0);
- if(s == INVALID_SOCKET) {
- _fh_close(f);
- return -1;
- }
-
- f->fh_socket = s;
- n = 1;
- setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&n, sizeof(n));
-
- if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
- _fh_close(f);
- return -1;
- }
-
- if (type == SOCK_STREAM) {
- int ret;
-
- ret = listen(s, LISTEN_BACKLOG);
- if (ret < 0) {
- _fh_close(f);
- return -1;
- }
- }
- snprintf( f->name, sizeof(f->name), "%d(any-server:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port );
- D( "socket_inaddr_server: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) );
- return _fh_to_int(f);
+ const int fd = _fh_to_int(f.get());
+ snprintf( f->name, sizeof(f->name), "%d(net-client:%s%d)", fd,
+ type != SOCK_STREAM ? "udp:" : "", port );
+ D( "host '%s' port %d type %s => fd %d\n", host.c_str(), port,
+ type != SOCK_STREAM ? "udp" : "tcp", fd );
+ f.release();
+ return fd;
}
#undef accept
int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen)
{
- FH serverfh = _fh_from_int(serverfd);
- FH fh;
+ FH serverfh = _fh_from_int(serverfd, __func__);
if ( !serverfh || serverfh->clazz != &_fh_socket_class ) {
- D( "adb_socket_accept: invalid fd %d\n", serverfd );
+ D("adb_socket_accept: invalid fd %d\n", serverfd);
+ errno = EBADF;
return -1;
}
- fh = _fh_alloc( &_fh_socket_class );
+ unique_fh fh(_fh_alloc( &_fh_socket_class ));
if (!fh) {
- D( "adb_socket_accept: not enough memory to allocate accepted socket descriptor\n" );
+ PLOG(ERROR) << "adb_socket_accept: failed to allocate accepted socket "
+ "descriptor";
return -1;
}
fh->fh_socket = accept( serverfh->fh_socket, addr, addrlen );
if (fh->fh_socket == INVALID_SOCKET) {
- _fh_close( fh );
- D( "adb_socket_accept: accept on fd %d return error %ld\n", serverfd, GetLastError() );
+ const DWORD err = WSAGetLastError();
+ LOG(ERROR) << "adb_socket_accept: accept on fd " << serverfd <<
+ " failed: " + SystemErrorCodeToString(err);
+ _socket_set_errno( err );
return -1;
}
- snprintf( fh->name, sizeof(fh->name), "%d(accept:%s)", _fh_to_int(fh), serverfh->name );
- D( "adb_socket_accept on fd %d returns fd %d\n", serverfd, _fh_to_int(fh) );
- return _fh_to_int(fh);
+ const int fd = _fh_to_int(fh.get());
+ snprintf( fh->name, sizeof(fh->name), "%d(accept:%s)", fd, serverfh->name );
+ D( "adb_socket_accept on fd %d returns fd %d\n", serverfd, fd );
+ fh.release();
+ return fd;
}
int adb_setsockopt( int fd, int level, int optname, const void* optval, socklen_t optlen )
{
- FH fh = _fh_from_int(fd);
+ FH fh = _fh_from_int(fd, __func__);
if ( !fh || fh->clazz != &_fh_socket_class ) {
D("adb_setsockopt: invalid fd %d\n", fd);
+ errno = EBADF;
+ return -1;
+ }
+ int result = setsockopt( fh->fh_socket, level, optname,
+ reinterpret_cast<const char*>(optval), optlen );
+ if ( result == SOCKET_ERROR ) {
+ const DWORD err = WSAGetLastError();
+ D( "adb_setsockopt: setsockopt on fd %d level %d optname %d "
+ "failed: %s\n", fd, level, optname,
+ SystemErrorCodeToString(err).c_str() );
+ _socket_set_errno( err );
+ result = -1;
+ }
+ return result;
+}
+
+
+int adb_shutdown(int fd)
+{
+ FH f = _fh_from_int(fd, __func__);
+
+ if (!f || f->clazz != &_fh_socket_class) {
+ D("adb_shutdown: invalid fd %d\n", fd);
+ errno = EBADF;
return -1;
}
- return setsockopt( fh->fh_socket, level, optname, reinterpret_cast<const char*>(optval), optlen );
+ D( "adb_shutdown: %s\n", f->name);
+ if (shutdown(f->fh_socket, SD_BOTH) == SOCKET_ERROR) {
+ const DWORD err = WSAGetLastError();
+ D("socket shutdown fd %d failed: %s\n", fd,
+ SystemErrorCodeToString(err).c_str());
+ _socket_set_errno(err);
+ return -1;
+ }
+ return 0;
}
/**************************************************************************/
@@ -1202,16 +1370,19 @@
int adb_socketpair(int sv[2]) {
SocketPair pair;
- FH fa = _fh_alloc(&_fh_socketpair_class);
- FH fb = _fh_alloc(&_fh_socketpair_class);
-
- if (!fa || !fb)
- goto Fail;
+ unique_fh fa(_fh_alloc(&_fh_socketpair_class));
+ if (!fa) {
+ return -1;
+ }
+ unique_fh fb(_fh_alloc(&_fh_socketpair_class));
+ if (!fb) {
+ return -1;
+ }
pair = reinterpret_cast<SocketPair>(malloc(sizeof(*pair)));
if (pair == NULL) {
D("adb_socketpair: not enough memory to allocate pipes\n" );
- goto Fail;
+ return -1;
}
bip_buffer_init( &pair->a2b_bip );
@@ -1220,10 +1391,10 @@
fa->fh_pair = pair;
fb->fh_pair = pair;
pair->used = 2;
- pair->a_fd = fa;
+ pair->a_fd = fa.get();
- sv[0] = _fh_to_int(fa);
- sv[1] = _fh_to_int(fb);
+ sv[0] = _fh_to_int(fa.get());
+ sv[1] = _fh_to_int(fb.get());
pair->a2b_bip.fdin = sv[0];
pair->a2b_bip.fdout = sv[1];
@@ -1233,12 +1404,9 @@
snprintf( fa->name, sizeof(fa->name), "%d(pair:%d)", sv[0], sv[1] );
snprintf( fb->name, sizeof(fb->name), "%d(pair:%d)", sv[1], sv[0] );
D( "adb_socketpair: returns (%d, %d)\n", sv[0], sv[1] );
+ fa.release();
+ fb.release();
return 0;
-
-Fail:
- _fh_close(fb);
- _fh_close(fa);
- return -1;
}
/**************************************************************************/
@@ -1386,7 +1554,7 @@
static void
event_looper_hook( EventLooper looper, int fd, int events )
{
- FH f = _fh_from_int(fd);
+ FH f = _fh_from_int(fd, __func__);
EventHook *pnode;
EventHook node;
@@ -1418,7 +1586,7 @@
static void
event_looper_unhook( EventLooper looper, int fd, int events )
{
- FH fh = _fh_from_int(fd);
+ FH fh = _fh_from_int(fd, __func__);
EventHook *pnode = event_looper_find_p( looper, fh );
EventHook node = *pnode;
@@ -1544,7 +1712,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;
}
@@ -1552,7 +1720,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;
}
@@ -1585,7 +1753,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);
@@ -2086,6 +2254,7 @@
hook->check = _event_socket_check;
hook->peek = _event_socket_peek;
+ // TODO: check return value?
_event_socket_start( hook );
}
@@ -2193,7 +2362,7 @@
memset(input_record, 0, sizeof(*input_record));
if (!ReadConsoleInputA(console, input_record, 1, &read_count)) {
D("_get_interesting_input_record_uncached: ReadConsoleInputA() "
- "failure, error %ld\n", GetLastError());
+ "failed: %s\n", SystemErrorCodeToString(GetLastError()).c_str());
errno = EIO;
return false;
}
@@ -3007,8 +3176,8 @@
if (!SetConsoleMode(in, _old_console_mode & ~(ENABLE_PROCESSED_INPUT |
ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))) {
// This really should not fail.
- D("stdin_raw_init: SetConsoleMode() failure, error %ld\n",
- GetLastError());
+ D("stdin_raw_init: SetConsoleMode() failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
}
// Once this is set, it means that stdin has been configured for
@@ -3029,14 +3198,14 @@
if (!SetConsoleMode(in, _old_console_mode)) {
// This really should not fail.
- D("stdin_raw_restore: SetConsoleMode() failure, error %ld\n",
- GetLastError());
+ D("stdin_raw_restore: SetConsoleMode() failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
}
}
}
}
-// Called by 'adb shell' command to read from stdin.
+// Called by 'adb shell' and 'adb exec-in' to read from stdin.
int unix_read(int fd, void* buf, size_t len) {
if ((fd == STDIN_FILENO) && (_console_handle != NULL)) {
// If it is a request to read from stdin, and stdin_raw_init() has been
@@ -3046,8 +3215,633 @@
return _console_read(_console_handle, buf, len);
} else {
// Just call into C Runtime which can read from pipes/files and which
- // can do LF/CR translation.
+ // can do LF/CR translation (which is overridable with _setmode()).
+ // Undefine the macro that is set in sysdeps.h which bans calls to
+ // plain read() in favor of unix_read() or adb_read().
+#pragma push_macro("read")
#undef read
return read(fd, buf, len);
+#pragma pop_macro("read")
}
}
+
+/**************************************************************************/
+/**************************************************************************/
+/***** *****/
+/***** Unicode support *****/
+/***** *****/
+/**************************************************************************/
+/**************************************************************************/
+
+// This implements support for using files with Unicode filenames and for
+// outputting Unicode text to a Win32 console window. This is inspired from
+// http://utf8everywhere.org/.
+//
+// Background
+// ----------
+//
+// On POSIX systems, to deal with files with Unicode filenames, just pass UTF-8
+// filenames to APIs such as open(). This works because filenames are largely
+// opaque 'cookies' (perhaps excluding path separators).
+//
+// On Windows, the native file APIs such as CreateFileW() take 2-byte wchar_t
+// UTF-16 strings. There is an API, CreateFileA() that takes 1-byte char
+// strings, but the strings are in the ANSI codepage and not UTF-8. (The
+// CreateFile() API is really just a macro that adds the W/A based on whether
+// the UNICODE preprocessor symbol is defined).
+//
+// Options
+// -------
+//
+// Thus, to write a portable program, there are a few options:
+//
+// 1. Write the program with wchar_t filenames (wchar_t path[256];).
+// For Windows, just call CreateFileW(). For POSIX, write a wrapper openW()
+// that takes a wchar_t string, converts it to UTF-8 and then calls the real
+// open() API.
+//
+// 2. Write the program with a TCHAR typedef that is 2 bytes on Windows and
+// 1 byte on POSIX. Make T-* wrappers for various OS APIs and call those,
+// potentially touching a lot of code.
+//
+// 3. Write the program with a 1-byte char filenames (char path[256];) that are
+// UTF-8. For POSIX, just call open(). For Windows, write a wrapper that
+// takes a UTF-8 string, converts it to UTF-16 and then calls the real OS
+// or C Runtime API.
+//
+// The Choice
+// ----------
+//
+// The code below chooses option 3, the UTF-8 everywhere strategy. It
+// introduces narrow() which converts UTF-16 to UTF-8. This is used by the
+// NarrowArgs helper class that is used to convert wmain() args into UTF-8
+// args that are passed to main() at the beginning of program startup. We also
+// introduce widen() which converts from UTF-8 to UTF-16. This is used to
+// implement wrappers below that call UTF-16 OS and C Runtime APIs.
+//
+// Unicode console output
+// ----------------------
+//
+// The way to output Unicode to a Win32 console window is to call
+// WriteConsoleW() with UTF-16 text. (The user must also choose a proper font
+// such as Lucida Console or Consolas, and in the case of East Asian languages
+// (such as Chinese, Japanese, Korean), the user must go to the Control Panel
+// and change the "system locale" to Chinese, etc., which allows a Chinese, etc.
+// font to be used in console windows.)
+//
+// The problem is getting the C Runtime to make fprintf and related APIs call
+// WriteConsoleW() under the covers. The C Runtime API, _setmode() sounds
+// promising, but the various modes have issues:
+//
+// 1. _setmode(_O_TEXT) (the default) does not use WriteConsoleW() so UTF-8 and
+// UTF-16 do not display properly.
+// 2. _setmode(_O_BINARY) does not use WriteConsoleW() and the text comes out
+// totally wrong.
+// 3. _setmode(_O_U8TEXT) seems to cause the C Runtime _invalid_parameter
+// handler to be called (upon a later I/O call), aborting the process.
+// 4. _setmode(_O_U16TEXT) and _setmode(_O_WTEXT) cause non-wide printf/fprintf
+// to output nothing.
+//
+// So the only solution is to write our own adb_fprintf() that converts UTF-8
+// to UTF-16 and then calls WriteConsoleW().
+
+
+// Function prototype because attributes cannot be placed on func definitions.
+static void _widen_fatal(const char *fmt, ...)
+ __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 1, 2)));
+
+// A version of fatal() that does not call adb_(v)fprintf(), so it can be
+// called from those functions.
+static void _widen_fatal(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ // If (v)fprintf are macros that point to adb_(v)fprintf, when random adb
+ // code calls (v)fprintf, it may end up calling adb_(v)fprintf, which then
+ // calls _widen_fatal(). So then how does _widen_fatal() output a error?
+ // By directly calling real C Runtime APIs that don't properly output
+ // Unicode, but will be able to get a comprehendible message out. To do
+ // this, make sure we don't call (v)fprintf macros by undefining them.
+#pragma push_macro("fprintf")
+#pragma push_macro("vfprintf")
+#undef fprintf
+#undef vfprintf
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+#pragma pop_macro("vfprintf")
+#pragma pop_macro("fprintf")
+ va_end(ap);
+ exit(-1);
+}
+
+// TODO: Consider implementing widen() and narrow() out of std::wstring_convert
+// once libcxx is supported on Windows. Or, consider libutils/Unicode.cpp.
+
+// Convert from UTF-8 to UTF-16. A size of -1 specifies a NULL terminated
+// string. Any other size specifies the number of chars to convert, excluding
+// any NULL terminator (if you're passing an explicit size, you probably don't
+// have a NULL terminated string in the first place).
+std::wstring widen(const char* utf8, const int size) {
+ // Note: Do not call SystemErrorCodeToString() from widen() because
+ // SystemErrorCodeToString() calls narrow() which may call fatal() which
+ // calls adb_vfprintf() which calls widen(), potentially causing infinite
+ // recursion.
+ const int chars_to_convert = MultiByteToWideChar(CP_UTF8, 0, utf8, size,
+ NULL, 0);
+ if (chars_to_convert <= 0) {
+ // UTF-8 to UTF-16 should be lossless, so we don't expect this to fail.
+ _widen_fatal("MultiByteToWideChar failed counting: %d, "
+ "GetLastError: %lu", chars_to_convert, GetLastError());
+ }
+
+ std::wstring utf16;
+ size_t chars_to_allocate = chars_to_convert;
+ if (size == -1) {
+ // chars_to_convert includes a NULL terminator, so subtract space
+ // for that because resize() includes that itself.
+ --chars_to_allocate;
+ }
+ utf16.resize(chars_to_allocate);
+
+ // This uses &string[0] to get write-access to the entire string buffer
+ // which may be assuming that the chars are all contiguous, but it seems
+ // to work and saves us the hassle of using a temporary
+ // std::vector<wchar_t>.
+ const int result = MultiByteToWideChar(CP_UTF8, 0, utf8, size, &utf16[0],
+ chars_to_convert);
+ if (result != chars_to_convert) {
+ // UTF-8 to UTF-16 should be lossless, so we don't expect this to fail.
+ _widen_fatal("MultiByteToWideChar failed conversion: %d, "
+ "GetLastError: %lu", result, GetLastError());
+ }
+
+ // If a size was passed in (size != -1), then the string is NULL terminated
+ // by a NULL char that was written by std::string::resize(). If size == -1,
+ // then MultiByteToWideChar() read a NULL terminator from the original
+ // string and converted it to a NULL UTF-16 char in the output.
+
+ return utf16;
+}
+
+// Convert a NULL terminated string from UTF-8 to UTF-16.
+std::wstring widen(const char* utf8) {
+ // Pass -1 to let widen() determine the string length.
+ return widen(utf8, -1);
+}
+
+// Convert from UTF-8 to UTF-16.
+std::wstring widen(const std::string& utf8) {
+ return widen(utf8.c_str(), utf8.length());
+}
+
+// Convert from UTF-16 to UTF-8.
+std::string narrow(const std::wstring& utf16) {
+ return narrow(utf16.c_str());
+}
+
+// Convert from UTF-16 to UTF-8.
+std::string narrow(const wchar_t* utf16) {
+ // Note: Do not call SystemErrorCodeToString() from narrow() because
+ // SystemErrorCodeToString() calls narrow() and we don't want potential
+ // infinite recursion.
+ const int chars_required = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, NULL,
+ 0, NULL, NULL);
+ if (chars_required <= 0) {
+ // UTF-16 to UTF-8 should be lossless, so we don't expect this to fail.
+ fatal("WideCharToMultiByte failed counting: %d, GetLastError: %lu",
+ chars_required, GetLastError());
+ }
+
+ std::string utf8;
+ // Subtract space for the NULL terminator because resize() includes
+ // that itself. Note that this could potentially throw a std::bad_alloc
+ // exception.
+ utf8.resize(chars_required - 1);
+
+ // This uses &string[0] to get write-access to the entire string buffer
+ // which may be assuming that the chars are all contiguous, but it seems
+ // to work and saves us the hassle of using a temporary
+ // std::vector<char>.
+ const int result = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, &utf8[0],
+ chars_required, NULL, NULL);
+ if (result != chars_required) {
+ // UTF-16 to UTF-8 should be lossless, so we don't expect this to fail.
+ fatal("WideCharToMultiByte failed conversion: %d, GetLastError: %lu",
+ result, GetLastError());
+ }
+
+ return utf8;
+}
+
+// Constructor for helper class to convert wmain() UTF-16 args to UTF-8 to
+// be passed to main().
+NarrowArgs::NarrowArgs(const int argc, wchar_t** const argv) {
+ narrow_args = new char*[argc + 1];
+
+ for (int i = 0; i < argc; ++i) {
+ narrow_args[i] = strdup(narrow(argv[i]).c_str());
+ }
+ narrow_args[argc] = nullptr; // terminate
+}
+
+NarrowArgs::~NarrowArgs() {
+ if (narrow_args != nullptr) {
+ for (char** argp = narrow_args; *argp != nullptr; ++argp) {
+ free(*argp);
+ }
+ delete[] narrow_args;
+ narrow_args = nullptr;
+ }
+}
+
+int unix_open(const char* path, int options, ...) {
+ if ((options & O_CREAT) == 0) {
+ return _wopen(widen(path).c_str(), options);
+ } else {
+ int mode;
+ va_list args;
+ va_start(args, options);
+ mode = va_arg(args, int);
+ va_end(args);
+ return _wopen(widen(path).c_str(), options, mode);
+ }
+}
+
+// Version of stat() that takes a UTF-8 path.
+int adb_stat(const char* f, struct adb_stat* s) {
+#pragma push_macro("wstat")
+// This definition of wstat seems to be missing from <sys/stat.h>.
+#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64)
+#ifdef _USE_32BIT_TIME_T
+#define wstat _wstat32i64
+#else
+#define wstat _wstat64
+#endif
+#else
+// <sys/stat.h> has a function prototype for wstat() that should be available.
+#endif
+
+ return wstat(widen(f).c_str(), s);
+
+#pragma pop_macro("wstat")
+}
+
+// Version of opendir() that takes a UTF-8 path.
+DIR* adb_opendir(const char* name) {
+ // Just cast _WDIR* to DIR*. This doesn't work if the caller reads any of
+ // the fields, but right now all the callers treat the structure as
+ // opaque.
+ return reinterpret_cast<DIR*>(_wopendir(widen(name).c_str()));
+}
+
+// Version of readdir() that returns UTF-8 paths.
+struct dirent* adb_readdir(DIR* dir) {
+ _WDIR* const wdir = reinterpret_cast<_WDIR*>(dir);
+ struct _wdirent* const went = _wreaddir(wdir);
+ if (went == nullptr) {
+ return nullptr;
+ }
+ // Convert from UTF-16 to UTF-8.
+ const std::string name_utf8(narrow(went->d_name));
+
+ // Cast the _wdirent* to dirent* and overwrite the d_name field (which has
+ // space for UTF-16 wchar_t's) with UTF-8 char's.
+ struct dirent* ent = reinterpret_cast<struct dirent*>(went);
+
+ if (name_utf8.length() + 1 > sizeof(went->d_name)) {
+ // Name too big to fit in existing buffer.
+ errno = ENOMEM;
+ return nullptr;
+ }
+
+ // Note that sizeof(_wdirent::d_name) is bigger than sizeof(dirent::d_name)
+ // because _wdirent contains wchar_t instead of char. So even if name_utf8
+ // can fit in _wdirent::d_name, the resulting dirent::d_name field may be
+ // bigger than the caller expects because they expect a dirent structure
+ // which has a smaller d_name field. Ignore this since the caller should be
+ // resilient.
+
+ // Rewrite the UTF-16 d_name field to UTF-8.
+ strcpy(ent->d_name, name_utf8.c_str());
+
+ return ent;
+}
+
+// Version of closedir() to go with our version of adb_opendir().
+int adb_closedir(DIR* dir) {
+ return _wclosedir(reinterpret_cast<_WDIR*>(dir));
+}
+
+// Version of unlink() that takes a UTF-8 path.
+int adb_unlink(const char* path) {
+ const std::wstring wpath(widen(path));
+
+ int rc = _wunlink(wpath.c_str());
+
+ if (rc == -1 && errno == EACCES) {
+ /* unlink returns EACCES when the file is read-only, so we first */
+ /* try to make it writable, then unlink again... */
+ rc = _wchmod(wpath.c_str(), _S_IREAD | _S_IWRITE);
+ if (rc == 0)
+ rc = _wunlink(wpath.c_str());
+ }
+ return rc;
+}
+
+// Version of mkdir() that takes a UTF-8 path.
+int adb_mkdir(const std::string& path, int mode) {
+ return _wmkdir(widen(path.c_str()).c_str());
+}
+
+// Version of utime() that takes a UTF-8 path.
+int adb_utime(const char* path, struct utimbuf* u) {
+ static_assert(sizeof(struct utimbuf) == sizeof(struct _utimbuf),
+ "utimbuf and _utimbuf should be the same size because they both "
+ "contain the same types, namely time_t");
+ return _wutime(widen(path).c_str(), reinterpret_cast<struct _utimbuf*>(u));
+}
+
+// Version of chmod() that takes a UTF-8 path.
+int adb_chmod(const char* path, int mode) {
+ return _wchmod(widen(path).c_str(), mode);
+}
+
+// Internal function to get a Win32 console HANDLE from a C Runtime FILE*.
+static HANDLE _get_console_handle(FILE* const stream) {
+ // Get a C Runtime file descriptor number from the FILE* structure.
+ const int fd = fileno(stream);
+ if (fd < 0) {
+ return NULL;
+ }
+
+ // If it is not a "character device", it is probably a file and not a
+ // console. Do this check early because it is probably cheap. Still do more
+ // checks after this since there are devices that pass this test, but are
+ // not a console, such as NUL, the Windows /dev/null equivalent (I think).
+ if (!isatty(fd)) {
+ return NULL;
+ }
+
+ // Given a C Runtime file descriptor number, get the underlying OS
+ // file handle.
+ const intptr_t osfh = _get_osfhandle(fd);
+ if (osfh == -1) {
+ return NULL;
+ }
+
+ const HANDLE h = reinterpret_cast<const HANDLE>(osfh);
+
+ DWORD old_mode = 0;
+ if (!GetConsoleMode(h, &old_mode)) {
+ return NULL;
+ }
+
+ // If GetConsoleMode() was successful, assume this is a console.
+ return h;
+}
+
+// Internal helper function to write UTF-8 bytes to a console. Returns -1
+// on error.
+static int _console_write_utf8(const char* buf, size_t size, FILE* stream,
+ HANDLE console) {
+ // Convert from UTF-8 to UTF-16.
+ // This could throw std::bad_alloc.
+ const std::wstring output(widen(buf, size));
+
+ // Note that this does not do \n => \r\n translation because that
+ // doesn't seem necessary for the Windows console. For the Windows
+ // console \r moves to the beginning of the line and \n moves to a new
+ // line.
+
+ // Flush any stream buffering so that our output is afterwards which
+ // makes sense because our call is afterwards.
+ (void)fflush(stream);
+
+ // Write UTF-16 to the console.
+ DWORD written = 0;
+ if (!WriteConsoleW(console, output.c_str(), output.length(), &written,
+ NULL)) {
+ errno = EIO;
+ return -1;
+ }
+
+ // This is the number of UTF-16 chars written, which might be different
+ // than the number of UTF-8 chars passed in. It doesn't seem practical to
+ // get this count correct.
+ return written;
+}
+
+// Function prototype because attributes cannot be placed on func definitions.
+static int _console_vfprintf(const HANDLE console, FILE* stream,
+ const char *format, va_list ap)
+ __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 3, 0)));
+
+// Internal function to format a UTF-8 string and write it to a Win32 console.
+// Returns -1 on error.
+static int _console_vfprintf(const HANDLE console, FILE* stream,
+ const char *format, va_list ap) {
+ std::string output_utf8;
+
+ // Format the string.
+ // This could throw std::bad_alloc.
+ android::base::StringAppendV(&output_utf8, format, ap);
+
+ return _console_write_utf8(output_utf8.c_str(), output_utf8.length(),
+ stream, console);
+}
+
+// Version of vfprintf() that takes UTF-8 and can write Unicode to a
+// Windows console.
+int adb_vfprintf(FILE *stream, const char *format, va_list ap) {
+ const HANDLE console = _get_console_handle(stream);
+
+ // If there is an associated Win32 console, write to it specially,
+ // otherwise defer to the regular C Runtime, passing it UTF-8.
+ if (console != NULL) {
+ return _console_vfprintf(console, stream, format, ap);
+ } else {
+ // If vfprintf is a macro, undefine it, so we can call the real
+ // C Runtime API.
+#pragma push_macro("vfprintf")
+#undef vfprintf
+ return vfprintf(stream, format, ap);
+#pragma pop_macro("vfprintf")
+ }
+}
+
+// Version of fprintf() that takes UTF-8 and can write Unicode to a
+// Windows console.
+int adb_fprintf(FILE *stream, const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ const int result = adb_vfprintf(stream, format, ap);
+ va_end(ap);
+
+ return result;
+}
+
+// Version of printf() that takes UTF-8 and can write Unicode to a
+// Windows console.
+int adb_printf(const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ const int result = adb_vfprintf(stdout, format, ap);
+ va_end(ap);
+
+ return result;
+}
+
+// Version of fputs() that takes UTF-8 and can write Unicode to a
+// Windows console.
+int adb_fputs(const char* buf, FILE* stream) {
+ // adb_fprintf returns -1 on error, which is conveniently the same as EOF
+ // which fputs (and hence adb_fputs) should return on error.
+ return adb_fprintf(stream, "%s", buf);
+}
+
+// Version of fputc() that takes UTF-8 and can write Unicode to a
+// Windows console.
+int adb_fputc(int ch, FILE* stream) {
+ const int result = adb_fprintf(stream, "%c", ch);
+ if (result <= 0) {
+ // If there was an error, or if nothing was printed (which should be an
+ // error), return an error, which fprintf signifies with EOF.
+ return EOF;
+ }
+ // For success, fputc returns the char, cast to unsigned char, then to int.
+ return static_cast<unsigned char>(ch);
+}
+
+// Internal function to write UTF-8 to a Win32 console. Returns the number of
+// items (of length size) written. On error, returns a short item count or 0.
+static size_t _console_fwrite(const void* ptr, size_t size, size_t nmemb,
+ FILE* stream, HANDLE console) {
+ // TODO: Note that a Unicode character could be several UTF-8 bytes. But
+ // if we're passed only some of the bytes of a character (for example, from
+ // the network socket for adb shell), we won't be able to convert the char
+ // to a complete UTF-16 char (or surrogate pair), so the output won't look
+ // right.
+ //
+ // To fix this, see libutils/Unicode.cpp for hints on decoding UTF-8.
+ //
+ // For now we ignore this problem because the alternative is that we'd have
+ // to parse UTF-8 and buffer things up (doable). At least this is better
+ // than what we had before -- always incorrect multi-byte UTF-8 output.
+ int result = _console_write_utf8(reinterpret_cast<const char*>(ptr),
+ size * nmemb, stream, console);
+ if (result == -1) {
+ return 0;
+ }
+ return result / size;
+}
+
+// Version of fwrite() that takes UTF-8 and can write Unicode to a
+// Windows console.
+size_t adb_fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) {
+ const HANDLE console = _get_console_handle(stream);
+
+ // If there is an associated Win32 console, write to it specially,
+ // otherwise defer to the regular C Runtime, passing it UTF-8.
+ if (console != NULL) {
+ return _console_fwrite(ptr, size, nmemb, stream, console);
+ } else {
+ // If fwrite is a macro, undefine it, so we can call the real
+ // C Runtime API.
+#pragma push_macro("fwrite")
+#undef fwrite
+ return fwrite(ptr, size, nmemb, stream);
+#pragma pop_macro("fwrite")
+ }
+}
+
+// Version of fopen() that takes a UTF-8 filename and can access a file with
+// a Unicode filename.
+FILE* adb_fopen(const char* f, const char* m) {
+ return _wfopen(widen(f).c_str(), widen(m).c_str());
+}
+
+// Shadow UTF-8 environment variable name/value pairs that are created from
+// _wenviron the first time that adb_getenv() is called. Note that this is not
+// currently updated if putenv, setenv, unsetenv are called. Note that no
+// thread synchronization is done, but we're called early enough in
+// single-threaded startup that things work ok.
+static std::unordered_map<std::string, char*> g_environ_utf8;
+
+// Make sure that shadow UTF-8 environment variables are setup.
+static void _ensure_env_setup() {
+ // If some name/value pairs exist, then we've already done the setup below.
+ if (g_environ_utf8.size() != 0) {
+ return;
+ }
+
+ // Read name/value pairs from UTF-16 _wenviron and write new name/value
+ // pairs to UTF-8 g_environ_utf8. Note that it probably does not make sense
+ // to use the D() macro here because that tracing only works if the
+ // ADB_TRACE environment variable is setup, but that env var can't be read
+ // until this code completes.
+ for (wchar_t** env = _wenviron; *env != nullptr; ++env) {
+ wchar_t* const equal = wcschr(*env, L'=');
+ if (equal == nullptr) {
+ // Malformed environment variable with no equal sign. Shouldn't
+ // really happen, but we should be resilient to this.
+ continue;
+ }
+
+ const std::string name_utf8(narrow(std::wstring(*env, equal - *env)));
+ char* const value_utf8 = strdup(narrow(equal + 1).c_str());
+
+ // Overwrite any duplicate name, but there shouldn't be a dup in the
+ // first place.
+ g_environ_utf8[name_utf8] = value_utf8;
+ }
+}
+
+// Version of getenv() that takes a UTF-8 environment variable name and
+// retrieves a UTF-8 value.
+char* adb_getenv(const char* name) {
+ _ensure_env_setup();
+
+ const auto it = g_environ_utf8.find(std::string(name));
+ if (it == g_environ_utf8.end()) {
+ return nullptr;
+ }
+
+ return it->second;
+}
+
+// Version of getcwd() that returns the current working directory in UTF-8.
+char* adb_getcwd(char* buf, int size) {
+ wchar_t* wbuf = _wgetcwd(nullptr, 0);
+ if (wbuf == nullptr) {
+ return nullptr;
+ }
+
+ const std::string buf_utf8(narrow(wbuf));
+ free(wbuf);
+ wbuf = nullptr;
+
+ // If size was specified, make sure all the chars will fit.
+ if (size != 0) {
+ if (size < static_cast<int>(buf_utf8.length() + 1)) {
+ errno = ERANGE;
+ return nullptr;
+ }
+ }
+
+ // If buf was not specified, allocate storage.
+ if (buf == nullptr) {
+ if (size == 0) {
+ size = buf_utf8.length() + 1;
+ }
+ buf = reinterpret_cast<char*>(malloc(size));
+ if (buf == nullptr) {
+ return nullptr;
+ }
+ }
+
+ // Destination buffer was allocated with enough space, or we've already
+ // checked an existing buffer size for enough space.
+ strcpy(buf, buf_utf8.c_str());
+
+ return buf;
+}
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..c893ad4
--- /dev/null
+++ b/adb/test_device.py
@@ -0,0 +1,479 @@
+#!/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
+
+
+def requires_root(func):
+ def wrapper(self, *args):
+ if self.device.get_prop('ro.debuggable') != '1':
+ raise unittest.SkipTest('requires rootable build')
+
+ was_root = self.device.shell(['id', '-un']).strip() == 'root'
+ if not was_root:
+ self.device.root()
+ self.device.wait()
+
+ try:
+ func(self, *args)
+ finally:
+ if not was_root:
+ self.device.unroot()
+ self.device.wait()
+
+ return wrapper
+
+
+class GetDeviceTest(unittest.TestCase):
+ def setUp(self):
+ self.android_serial = os.getenv('ANDROID_SERIAL')
+ if 'ANDROID_SERIAL' in os.environ:
+ del os.environ['ANDROID_SERIAL']
+
+ def tearDown(self):
+ if self.android_serial is not None:
+ os.environ['ANDROID_SERIAL'] = self.android_serial
+ else:
+ if 'ANDROID_SERIAL' in os.environ:
+ del os.environ['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)
+
+ def test_pty_logic(self):
+ """Verify PTY logic for shells.
+
+ Interactive shells should use a PTY, non-interactive should not.
+
+ Bug: http://b/21215503
+ """
+ proc = subprocess.Popen(
+ self.device.adb_cmd + ['shell'], stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ # [ -t 0 ] is used (rather than `tty`) to provide portability. This
+ # gives an exit code of 0 iff stdin is connected to a terminal.
+ #
+ # Closing host-side stdin doesn't currently trigger the interactive
+ # shell to exit so we need to explicitly add an exit command to
+ # close the session from the device side, and append \n to complete
+ # the interactive command.
+ result = proc.communicate('[ -t 0 ]; echo x$?; exit 0\n')[0]
+ partition = result.rpartition('x')
+ self.assertEqual(partition[1], 'x')
+ self.assertEqual(int(partition[2]), 0)
+
+ exit_code = self.device.shell_nocheck(['[ -t 0 ]'])[0]
+ self.assertEqual(exit_code, 1)
+
+
+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)."""
+ if self.device.get_prop('ro.debuggable') != '1':
+ raise unittest.SkipTest('requires rootable build')
+
+ 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')
+
+
+class SystemPropertiesTest(DeviceTest):
+ def test_get_prop(self):
+ self.assertEqual(self.device.get_prop('init.svc.adbd'), 'running')
+
+ @requires_root
+ def test_set_prop(self):
+ prop_name = 'foo.bar'
+ self.device.shell(['setprop', prop_name, '""'])
+
+ self.device.set_prop(prop_name, 'qux')
+ self.assertEqual(
+ self.device.shell(['getprop', prop_name]).strip(), 'qux')
+
+
+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 = posixpath.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])
+ 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)
+ 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)
+ rand_str = os.urandom(1024 * kbytes)
+ tmp.write(rand_str)
+ tmp.close()
+ self._test_push(tmp.name, compute_md5(rand_str))
+ os.remove(tmp.name)
+
+ # TODO: write push directory test.
+
+ def _test_pull(self, remote_file, checksum):
+ tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
+ 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)
+ 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])
+ 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)
+ 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()
+ 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)
+
+ 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()
+
+ # 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)
+
+ 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/test_track_devices.cpp b/adb/test_track_devices.cpp
index f78daeb..6f658f6 100644
--- a/adb/test_track_devices.cpp
+++ b/adb/test_track_devices.cpp
@@ -1,12 +1,13 @@
// TODO: replace this with a shell/python script.
/* a simple test program, connects to ADB server, and opens a track-devices session */
-#include <netdb.h>
-#include <sys/socket.h>
-#include <stdio.h>
-#include <stdlib.h>
#include <errno.h>
#include <memory.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h>
#include <base/file.h>
diff --git a/adb/tests/test_adb.py b/adb/tests/test_adb.py
deleted file mode 100755
index 4eccc8c..0000000
--- a/adb/tests/test_adb.py
+++ /dev/null
@@ -1,447 +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 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 = os.path.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)
-
- 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()
-
- 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(device)
- if self.out_dir:
- self.adb_cmd += "-p {} ".format(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(local, remote))
-
- def pull(self, remote, local):
- return call_checked(self.adb_cmd + "pull {} {}".format(remote, local))
-
- def sync(self, directory=""):
- return call_checked(self.adb_cmd + "sync {}".format(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()
- adb.root()
- 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("w", suffix="-text;ls;1.apk")
- self.assertIn("-text;ls;1.apk", adb.install(tf.name))
-
- # http://b/3090932
- tf = tempfile.NamedTemporaryFile("w", 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
- """
- self.assertFalse(AdbWrapper().shell("uname").endswith("\r\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="w") as tmp:
- rand_str = os.urandom(1024 * kbytes)
- tmp.write(rand_str)
- tmp.flush()
-
- 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))
-
- # 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="w") as tmp_write:
- adb.pull(remote=AdbFile.DEVICE_TEMP_FILE, local=tmp_write.name)
- with open(tmp_write.name) as tmp_read:
- host_contents = tmp_read.read()
- host_md5 = compute_md5(host_contents)
- self.assertEqual(dev_md5, host_md5)
- 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) 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 = os.path.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():
- tf.handle.close()
- 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 379c702..2ea4d44 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -29,6 +29,7 @@
#include <list>
#include <base/stringprintf.h>
+#include <base/strings.h>
#include "adb.h"
#include "adb_utils.h"
@@ -126,19 +127,18 @@
static int
read_packet(int fd, const char* name, apacket** ppacket)
{
- char *p = (char*)ppacket; /* really read a packet address */
- int r;
- int len = sizeof(*ppacket);
- char buff[8];
+ char buff[8];
if (!name) {
snprintf(buff, sizeof buff, "fd=%d", fd);
name = buff;
}
+ char* p = reinterpret_cast<char*>(ppacket); /* really read a packet address */
+ int len = sizeof(apacket*);
while(len > 0) {
- r = adb_read(fd, p, len);
+ int r = adb_read(fd, p, len);
if(r > 0) {
len -= r;
- p += r;
+ p += r;
} else {
D("%s: read_packet (fd=%d), error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno));
if((r < 0) && (errno == EINTR)) continue;
@@ -155,20 +155,18 @@
static int
write_packet(int fd, const char* name, apacket** ppacket)
{
- char *p = (char*) ppacket; /* we really write the packet address */
- int r, len = sizeof(ppacket);
char buff[8];
if (!name) {
snprintf(buff, sizeof buff, "fd=%d", fd);
name = buff;
}
-
if (ADB_TRACING) {
dump_packet(name, "to remote", *ppacket);
}
- len = sizeof(ppacket);
+ char* p = reinterpret_cast<char*>(ppacket); /* we really write the packet address */
+ int len = sizeof(apacket*);
while(len > 0) {
- r = adb_write(fd, p, len);
+ int r = adb_write(fd, p, len);
if(r > 0) {
len -= r;
p += r;
@@ -283,7 +281,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:
@@ -538,7 +536,7 @@
t = m.transport;
- if(m.action == 0){
+ if (m.action == 0) {
D("transport: %s removing and free'ing %d\n", t->serial, t->transport_socket);
/* IMPORTANT: the remove closes one half of the
@@ -579,7 +577,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 +615,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];
@@ -731,12 +729,12 @@
int ambiguous = 0;
retry:
- if (error_out) *error_out = android::base::StringPrintf("device '%s' not found", serial);
+ *error_out = serial ? android::base::StringPrintf("device '%s' not found", serial) : "no devices found";
adb_mutex_lock(&transport_lock);
for (auto t : transport_list) {
if (t->connection_state == kCsNoPerm) {
- if (error_out) *error_out = "insufficient permissions for device";
+ *error_out = "insufficient permissions for device";
continue;
}
@@ -748,7 +746,7 @@
qual_match(serial, "model:", t->model, true) ||
qual_match(serial, "device:", t->device, false)) {
if (result) {
- if (error_out) *error_out = "more than one device";
+ *error_out = "more than one device";
ambiguous = 1;
result = NULL;
break;
@@ -758,7 +756,7 @@
} else {
if (type == kTransportUsb && t->type == kTransportUsb) {
if (result) {
- if (error_out) *error_out = "more than one device";
+ *error_out = "more than one device";
ambiguous = 1;
result = NULL;
break;
@@ -766,7 +764,7 @@
result = t;
} else if (type == kTransportLocal && t->type == kTransportLocal) {
if (result) {
- if (error_out) *error_out = "more than one emulator";
+ *error_out = "more than one emulator";
ambiguous = 1;
result = NULL;
break;
@@ -774,7 +772,7 @@
result = t;
} else if (type == kTransportAny) {
if (result) {
- if (error_out) *error_out = "more than one device/emulator";
+ *error_out = "more than one device/emulator";
ambiguous = 1;
result = NULL;
break;
@@ -787,33 +785,32 @@
if (result) {
if (result->connection_state == kCsUnauthorized) {
- if (error_out) {
- *error_out = "device unauthorized.\n";
- char* ADB_VENDOR_KEYS = getenv("ADB_VENDOR_KEYS");
- *error_out += "This adbd'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 += "Otherwise check for a confirmation dialog on your device.";
- }
+ *error_out = "device unauthorized.\n";
+ char* ADB_VENDOR_KEYS = getenv("ADB_VENDOR_KEYS");
+ *error_out += "This adb server's $ADB_VENDOR_KEYS is ";
+ *error_out += ADB_VENDOR_KEYS ? ADB_VENDOR_KEYS : "not set";
+ *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;
}
/* offline devices are ignored -- they are either being born or dying */
if (result && result->connection_state == kCsOffline) {
- if (error_out) *error_out = "device offline";
+ *error_out = "device offline";
result = NULL;
}
/* check for required connection state */
if (result && state != kCsAny && result->connection_state != state) {
- if (error_out) *error_out = "invalid device state";
+ *error_out = "invalid device state";
result = NULL;
}
}
if (result) {
/* found one that we can take */
- if (error_out) *error_out = "success";
+ *error_out = "success";
} else if (state != kCsAny && (serial || !ambiguous)) {
adb_sleep_ms(1000);
goto retry;
@@ -836,6 +833,42 @@
}
}
+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;
+}
+
+// The list of features supported by the current system. Will be sent to the
+// other side of the connection in the banner.
+static const FeatureSet gSupportedFeatures = {
+ // None yet.
+};
+
+const FeatureSet& supported_features() {
+ return gSupportedFeatures;
+}
+
+bool atransport::has_feature(const std::string& feature) const {
+ return features_.count(feature) > 0;
+}
+
+void atransport::add_feature(const std::string& feature) {
+ features_.insert(feature);
+}
+
+bool atransport::CanUseFeature(const std::string& feature) const {
+ return has_feature(feature) &&
+ supported_features().count(feature) > 0;
+}
+
#if ADB_HOST
static void append_transport_info(std::string* result, const char* key,
@@ -870,6 +903,9 @@
append_transport_info(result, "product:", t->product, false);
append_transport_info(result, "model:", t->model, true);
append_transport_info(result, "device:", t->device, false);
+ append_transport_info(result, "features:",
+ android::base::Join(t->features(), ',').c_str(),
+ false);
}
*result += '\n';
}
@@ -1021,15 +1057,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..e809407 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -20,9 +20,92 @@
#include <sys/types.h>
#include <string>
+#include <unordered_set>
#include "adb.h"
+typedef std::unordered_set<std::string> FeatureSet;
+
+const FeatureSet& supported_features();
+
+class atransport {
+public:
+ // TODO(danalbert): We expose waaaaaaay too much stuff because this was
+ // historically just a struct, but making the whole thing a more idiomatic
+ // class in one go is a very large change. Given how bad our testing is,
+ // it's better to do this piece by piece.
+
+ atransport() {
+ auth_fde = {};
+ transport_fde = {};
+ protocol_version = A_VERSION;
+ max_payload = MAX_PAYLOAD;
+ }
+
+ virtual ~atransport() {}
+
+ int (*read_from_remote)(apacket* p, atransport* t) = nullptr;
+ int (*write_to_remote)(apacket* p, atransport* t) = nullptr;
+ void (*close)(atransport* t) = nullptr;
+ void (*kick)(atransport* t) = nullptr;
+
+ int fd = -1;
+ int transport_socket = -1;
+ fdevent transport_fde;
+ int ref_count = 0;
+ uint32_t sync_token = 0;
+ ConnectionState connection_state = kCsOffline;
+ bool online = false;
+ TransportType type = kTransportAny;
+
+ // USB handle or socket fd as needed.
+ usb_handle* usb = nullptr;
+ int sfd = -1;
+
+ // Used to identify transports for clients.
+ char* serial = nullptr;
+ char* product = nullptr;
+ char* model = nullptr;
+ char* device = nullptr;
+ char* devpath = nullptr;
+ int adb_port = -1; // Use for emulators (local transport)
+ bool kicked = false;
+
+ // A list of adisconnect callbacks called when the transport is kicked.
+ adisconnect disconnects = {};
+
+ void* key = nullptr;
+ unsigned char token[TOKEN_SIZE] = {};
+ fdevent auth_fde;
+ size_t failed_auth_attempts = 0;
+
+ 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;
+
+ inline const FeatureSet features() const {
+ return features_;
+ }
+
+ bool has_feature(const std::string& feature) const;
+ void add_feature(const std::string& feature);
+
+ // Returns true if both we and the other end of the transport support the
+ // feature.
+ bool CanUseFeature(const std::string& feature) const;
+
+private:
+ // A set of features transmitted in the banner with the initial connection.
+ // This is stored in the banner as 'features=feature0,feature1,etc'.
+ FeatureSet features_;
+ int protocol_version;
+ size_t max_payload;
+
+ DISALLOW_COPY_AND_ASSIGN(atransport);
+};
+
/*
* Obtain a transport from the available transports.
* If state is != kCsAny, only transports in that state are considered.
@@ -57,7 +140,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 d3c4c30..6a17497 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,23 +85,26 @@
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
+ if (find_emulator_transport_by_adb_port(adb_port) != nullptr) {
+ return -1;
+ }
+
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) {
- fd = socket_loopback_client(adb_port, SOCK_STREAM);
+ fd = network_loopback_client(adb_port, SOCK_STREAM, error);
}
if (fd >= 0) {
@@ -107,31 +112,33 @@
close_on_exec(fd);
disable_tcp_nagle(fd);
std::string serial = android::base::StringPrintf("emulator-%d", console_port);
- register_socket_transport(fd, serial.c_str(), adb_port, 1);
- return 0;
+ if (register_socket_transport(fd, serial.c_str(), adb_port, 1) == 0) {
+ return 0;
+ }
+ adb_close(fd);
}
return -1;
}
-
+#if ADB_HOST
static void *client_socket_thread(void *x)
{
-#if ADB_HOST
- int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
- int count = ADB_LOCAL_TRANSPORT_MAX;
-
D("transport: client_socket_thread() starting\n");
+ while (true) {
+ int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
+ int count = ADB_LOCAL_TRANSPORT_MAX;
- /* try to connect to any number of running emulator instances */
- /* 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);
+ // Try to connect to any number of running emulator instances.
+ for ( ; count > 0; count--, port += 2 ) {
+ local_connect(port);
+ }
+ sleep(1);
}
-#endif
return 0;
}
+#else // ADB_HOST
+
static void *server_socket_thread(void * arg)
{
int serverfd, fd;
@@ -143,9 +150,10 @@
serverfd = -1;
for(;;) {
if(serverfd == -1) {
- serverfd = socket_inaddr_any_server(port, SOCK_STREAM);
+ std::string error;
+ serverfd = network_inaddr_any_server(port, SOCK_STREAM, &error);
if(serverfd < 0) {
- D("server: cannot bind socket yet: %s\n", strerror(errno));
+ D("server: cannot bind socket yet: %s\n", error.c_str());
adb_sleep_ms(1000);
continue;
}
@@ -167,7 +175,6 @@
}
/* This is relevant only for ADB daemon running inside the emulator. */
-#if !ADB_HOST
/*
* Redefine open and write for qemu_pipe.h that contains inlined references
* to those routines. We will redifine them back after qemu_pipe.h inclusion.
@@ -279,31 +286,29 @@
void local_init(int port)
{
void* (*func)(void *);
+ const char* debug_name = "";
- if(HOST) {
- func = client_socket_thread;
- } else {
#if ADB_HOST
- func = server_socket_thread;
+ func = client_socket_thread;
+ debug_name = "client";
#else
- /* For the adbd daemon in the system image we need to distinguish
- * between the device, and the emulator. */
- char is_qemu[PROPERTY_VALUE_MAX];
- property_get("ro.kernel.qemu", is_qemu, "");
- if (!strcmp(is_qemu, "1")) {
- /* Running inside the emulator: use QEMUD pipe as the transport. */
- func = qemu_socket_thread;
- } else {
- /* Running inside the device: use TCP socket as the transport. */
- func = server_socket_thread;
- }
-#endif // !ADB_HOST
+ /* For the adbd daemon in the system image we need to distinguish
+ * between the device, and the emulator. */
+ char is_qemu[PROPERTY_VALUE_MAX];
+ property_get("ro.kernel.qemu", is_qemu, "");
+ if (!strcmp(is_qemu, "1")) {
+ /* Running inside the emulator: use QEMUD pipe as the transport. */
+ func = qemu_socket_thread;
+ } else {
+ /* Running inside the device: use TCP socket as the transport. */
+ func = server_socket_thread;
}
+ debug_name = "server";
+#endif // !ADB_HOST
- D("transport: local %s init\n", HOST ? "client" : "server");
-
+ D("transport: local %s init\n", debug_name);
if (!adb_thread_create(func, (void *) (uintptr_t) port)) {
- fatal_errno("cannot create local socket %s thread", HOST ? "client" : "server");
+ fatal_errno("cannot create local socket %s thread", debug_name);
}
}
@@ -315,23 +320,25 @@
adb_close(fd);
#if ADB_HOST
- if(HOST) {
- int nn;
- adb_mutex_lock( &local_transports_lock );
- for (nn = 0; nn < ADB_LOCAL_TRANSPORT_MAX; nn++) {
- if (local_transports[nn] == t) {
- local_transports[nn] = NULL;
- break;
- }
+ int nn;
+ adb_mutex_lock( &local_transports_lock );
+ for (nn = 0; nn < ADB_LOCAL_TRANSPORT_MAX; nn++) {
+ if (local_transports[nn] == t) {
+ local_transports[nn] = NULL;
+ break;
}
- adb_mutex_unlock( &local_transports_lock );
}
+ adb_mutex_unlock( &local_transports_lock );
#endif
}
static void remote_close(atransport *t)
{
- adb_close(t->fd);
+ int fd = t->sfd;
+ if (fd != -1) {
+ t->sfd = -1;
+ adb_close(fd);
+ }
}
@@ -392,7 +399,7 @@
t->adb_port = 0;
#if ADB_HOST
- if (HOST && local) {
+ if (local) {
adb_mutex_lock( &local_transports_lock );
{
t->adb_port = adb_port;
diff --git a/adb/transport_test.cpp b/adb/transport_test.cpp
index 4b74adf..743d97d 100644
--- a/adb/transport_test.cpp
+++ b/adb/transport_test.cpp
@@ -59,10 +59,33 @@
EXPECT_EQ(0, memcmp(&auth_fde, &rhs.auth_fde, sizeof(fdevent)));
EXPECT_EQ(failed_auth_attempts, rhs.failed_auth_attempts);
+ EXPECT_EQ(features(), rhs.features());
+
return true;
}
};
+class TransportSetup {
+public:
+ TransportSetup() {
+#ifdef _WIN32
+ // Use extern instead of including sysdeps.h which brings in various macros
+ // that conflict with APIs used in this file.
+ extern void adb_sysdeps_init(void);
+ adb_sysdeps_init();
+#else
+ // adb_sysdeps_init() is an inline function that we cannot link against.
+#endif
+ }
+};
+
+// Static initializer will call adb_sysdeps_init() before main() to initialize
+// the transport mutex before it is used in the tests. Alternatives would be to
+// use __attribute__((constructor)) here or to use that or a static initializer
+// for adb_sysdeps_init() itself in sysdeps_win32.cpp (caveats of unclear
+// init order), or to use a test fixture whose SetUp() could do the init once.
+static TransportSetup g_TransportSetup;
+
TEST(transport, kick_transport) {
TestTransport t;
@@ -99,6 +122,73 @@
// want to make sure I understand how this is working at all before I try fixing
// that.
TEST(transport, DISABLED_run_transport_disconnects_zeroed_atransport) {
- atransport t;
- run_transport_disconnects(&t);
+ atransport t;
+ run_transport_disconnects(&t);
+}
+
+TEST(transport, add_feature) {
+ atransport t;
+ ASSERT_EQ(0U, t.features().size());
+
+ t.add_feature("foo");
+ ASSERT_EQ(1U, t.features().size());
+ ASSERT_TRUE(t.has_feature("foo"));
+
+ t.add_feature("bar");
+ ASSERT_EQ(2U, t.features().size());
+ ASSERT_TRUE(t.has_feature("foo"));
+ ASSERT_TRUE(t.has_feature("bar"));
+
+ t.add_feature("foo");
+ ASSERT_EQ(2U, t.features().size());
+ ASSERT_TRUE(t.has_feature("foo"));
+ ASSERT_TRUE(t.has_feature("bar"));
+}
+
+TEST(transport, parse_banner_no_features) {
+ atransport t;
+
+ parse_banner("host::", &t);
+
+ ASSERT_EQ(0U, t.features().size());
+ ASSERT_EQ(kCsHost, t.connection_state);
+
+ ASSERT_EQ(nullptr, t.product);
+ ASSERT_EQ(nullptr, t.model);
+ ASSERT_EQ(nullptr, t.device);
+}
+
+TEST(transport, parse_banner_product_features) {
+ atransport t;
+
+ const char banner[] =
+ "host::ro.product.name=foo;ro.product.model=bar;ro.product.device=baz;";
+ parse_banner(banner, &t);
+
+ ASSERT_EQ(kCsHost, t.connection_state);
+
+ ASSERT_EQ(0U, t.features().size());
+
+ ASSERT_EQ(std::string("foo"), t.product);
+ ASSERT_EQ(std::string("bar"), t.model);
+ ASSERT_EQ(std::string("baz"), t.device);
+}
+
+TEST(transport, parse_banner_features) {
+ atransport t;
+
+ const char banner[] =
+ "host::ro.product.name=foo;ro.product.model=bar;ro.product.device=baz;"
+ "features=woodly,doodly";
+ parse_banner(banner, &t);
+
+ ASSERT_EQ(kCsHost, t.connection_state);
+
+ ASSERT_EQ(2U, t.features().size());
+ ASSERT_TRUE(t.has_feature("woodly"));
+ ASSERT_TRUE(t.has_feature("doodly"));
+
+ ASSERT_EQ(std::string("foo"), t.product);
+ ASSERT_EQ(std::string("bar"), t.model);
+ ASSERT_EQ(std::string("baz"), t.device);
}
diff --git a/adb/transport_usb.cpp b/adb/transport_usb.cpp
index eb3454d..96ccdad 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;
}
@@ -91,12 +91,6 @@
t->connection_state = state;
t->type = kTransportUsb;
t->usb = h;
-
-#if ADB_HOST
- HOST = 1;
-#else
- HOST = 0;
-#endif
}
#if ADB_HOST
diff --git a/adb/usb_linux.cpp b/adb/usb_linux.cpp
index 1e5d5c8..dd22712 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,28 +141,26 @@
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 = adb_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
if (desclength < USB_DT_DEVICE_SIZE + USB_DT_CONFIG_SIZE) {
D("desclength %zu is too small\n", desclength);
- adb_close(fd);
+ unix_close(fd);
continue;
}
@@ -182,20 +168,20 @@
bufptr += USB_DT_DEVICE_SIZE;
if((device->bLength != USB_DT_DEVICE_SIZE) || (device->bDescriptorType != USB_DT_DEVICE)) {
- adb_close(fd);
+ unix_close(fd);
continue;
}
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;
bufptr += USB_DT_CONFIG_SIZE;
if (config->bLength != USB_DT_CONFIG_SIZE || config->bDescriptorType != USB_DT_CONFIG) {
D("usb_config_descriptor not found\n");
- adb_close(fd);
+ unix_close(fd);
continue;
}
@@ -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;
@@ -277,14 +264,12 @@
// Determine the device path
if (!fstat(fd, &st) && S_ISCHR(st.st_mode)) {
- char *slash;
- ssize_t link_len;
snprintf(pathbuf, sizeof(pathbuf), "/sys/dev/char/%d:%d",
major(st.st_rdev), minor(st.st_rdev));
- link_len = readlink(pathbuf, link, sizeof(link) - 1);
+ ssize_t link_len = readlink(pathbuf, link, sizeof(link) - 1);
if (link_len > 0) {
link[link_len] = '\0';
- slash = strrchr(link, '/');
+ const char* slash = strrchr(link, '/');
if (slash) {
snprintf(pathbuf, sizeof(pathbuf),
"usb:%s", slash + 1);
@@ -293,7 +278,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;
@@ -303,73 +288,55 @@
}
} // end of while
- adb_close(fd);
- } // end of devdir while
- closedir(devdir);
- } //end of busdir while
- closedir(busdir);
+ unix_close(fd);
+ }
+ }
}
-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 +344,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 +427,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 +454,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 +474,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);
- adb_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 +507,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));
- adb_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 +563,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 +582,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 b532a7a..c6ad00d 100644
--- a/adb/usb_linux_client.cpp
+++ b/adb/usb_linux_client.cpp
@@ -246,7 +246,7 @@
int n;
D("about to write (fd=%d, len=%d)\n", h->fd, len);
- n = adb_write(h->fd, data, len);
+ n = unix_write(h->fd, data, len);
if(n != len) {
D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
h->fd, n, errno, strerror(errno));
@@ -258,14 +258,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 = adb_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;
@@ -275,7 +281,7 @@
{
D("usb_kick\n");
adb_mutex_lock(&h->lock);
- adb_close(h->fd);
+ unix_close(h->fd);
h->fd = -1;
// notify usb_adb_open_thread that we are disconnected
@@ -492,11 +498,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_osx.cpp b/adb/usb_osx.cpp
index af65130..2ba3ff7 100644
--- a/adb/usb_osx.cpp
+++ b/adb/usb_osx.cpp
@@ -29,21 +29,28 @@
#include <inttypes.h>
#include <stdio.h>
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
#include "adb.h"
#include "transport.h"
#define DBG D
+// There's no strerror equivalent for the errors returned by IOKit.
+// https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/AccessingHardware/AH_Handling_Errors/AH_Handling_Errors.html
+// Search the web for "IOReturn.h" to find a complete up to date list.
+
static IONotificationPortRef notificationPort = 0;
static io_iterator_t notificationIterator;
struct usb_handle
{
- UInt8 bulkIn;
- UInt8 bulkOut;
- IOUSBInterfaceInterface **interface;
- io_object_t usbNotification;
- unsigned int zero_mask;
+ UInt8 bulkIn;
+ UInt8 bulkOut;
+ IOUSBInterfaceInterface190** interface;
+ io_object_t usbNotification;
+ unsigned int zero_mask;
};
static CFRunLoopRef currentRunLoop = 0;
@@ -55,7 +62,7 @@
static void AndroidInterfaceNotify(void *refCon, io_iterator_t iterator,
natural_t messageType,
void *messageArgument);
-static usb_handle* CheckInterface(IOUSBInterfaceInterface **iface,
+static usb_handle* CheckInterface(IOUSBInterfaceInterface190 **iface,
UInt16 vendor, UInt16 product);
static int
@@ -77,7 +84,7 @@
matchingDict = IOServiceMatching(kIOUSBInterfaceClassName);
if (!matchingDict) {
- DBG("ERR: Couldn't create USB matching dictionary.\n");
+ LOG(ERROR) << "Couldn't create USB matching dictionary.";
return -1;
}
@@ -128,7 +135,7 @@
&plugInInterface, &score);
IOObjectRelease(usbInterface);
if ((kIOReturnSuccess != kr) || (!plugInInterface)) {
- DBG("ERR: Unable to create an interface plug-in (%08x)\n", kr);
+ LOG(ERROR) << "Unable to create an interface plug-in (" << std::hex << kr << ")";
continue;
}
@@ -139,7 +146,7 @@
//* We only needed the plugin to get the interface, so discard it
(*plugInInterface)->Release(plugInInterface);
if (result || !iface) {
- DBG("ERR: Couldn't query the interface (%08x)\n", (int) result);
+ LOG(ERROR) << "Couldn't query the interface (" << std::hex << result << ")";
continue;
}
@@ -148,7 +155,8 @@
kr = (*iface)->GetInterfaceProtocol(iface, &protocol);
if(if_class != ADB_CLASS || subclass != ADB_SUBCLASS || protocol != ADB_PROTOCOL) {
// Ignore non-ADB devices.
- DBG("Ignoring interface with incorrect class/subclass/protocol - %d, %d, %d\n", if_class, subclass, protocol);
+ LOG(DEBUG) << "Ignoring interface with incorrect class/subclass/protocol - " << if_class
+ << ", " << subclass << ", " << protocol;
(*iface)->Release(iface);
continue;
}
@@ -159,7 +167,7 @@
//* Gotta love OS X
kr = (*iface)->GetDevice(iface, &usbDevice);
if (kIOReturnSuccess != kr || !usbDevice) {
- DBG("ERR: Couldn't grab device from interface (%08x)\n", kr);
+ LOG(ERROR) << "Couldn't grab device from interface (" << std::hex << kr << ")";
continue;
}
@@ -173,7 +181,7 @@
//* only needed this to find the plugin
(void)IOObjectRelease(usbDevice);
if ((kIOReturnSuccess != kr) || (!plugInInterface)) {
- DBG("ERR: Unable to create a device plug-in (%08x)\n", kr);
+ LOG(ERROR) << "Unable to create a device plug-in (" << std::hex << kr << ")";
continue;
}
@@ -182,8 +190,7 @@
//* only needed this to query the plugin
(*plugInInterface)->Release(plugInInterface);
if (result || !dev) {
- DBG("ERR: Couldn't create a device interface (%08x)\n",
- (int) result);
+ LOG(ERROR) << "Couldn't create a device interface (" << std::hex << result << ")";
continue;
}
@@ -193,74 +200,74 @@
kr = (*dev)->GetDeviceProduct(dev, &product);
kr = (*dev)->GetLocationID(dev, &locationId);
if (kr == 0) {
- snprintf(devpathBuf, sizeof(devpathBuf), "usb:%" PRIu32 "X",
- (unsigned int)locationId);
+ snprintf(devpathBuf, sizeof(devpathBuf), "usb:%" PRIu32 "X", locationId);
devpath = devpathBuf;
}
kr = (*dev)->USBGetSerialNumberStringIndex(dev, &serialIndex);
- if (serialIndex > 0) {
- IOUSBDevRequest req;
- UInt16 buffer[256];
- UInt16 languages[128];
+ if (serialIndex > 0) {
+ IOUSBDevRequest req;
+ UInt16 buffer[256];
+ UInt16 languages[128];
- memset(languages, 0, sizeof(languages));
+ memset(languages, 0, sizeof(languages));
- req.bmRequestType =
- USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
- req.bRequest = kUSBRqGetDescriptor;
- req.wValue = (kUSBStringDesc << 8) | 0;
- req.wIndex = 0;
- req.pData = languages;
- req.wLength = sizeof(languages);
- kr = (*dev)->DeviceRequest(dev, &req);
+ req.bmRequestType =
+ USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
+ req.bRequest = kUSBRqGetDescriptor;
+ req.wValue = (kUSBStringDesc << 8) | 0;
+ req.wIndex = 0;
+ req.pData = languages;
+ req.wLength = sizeof(languages);
+ kr = (*dev)->DeviceRequest(dev, &req);
- if (kr == kIOReturnSuccess && req.wLenDone > 0) {
+ if (kr == kIOReturnSuccess && req.wLenDone > 0) {
- int langCount = (req.wLenDone - 2) / 2, lang;
+ int langCount = (req.wLenDone - 2) / 2, lang;
- for (lang = 1; lang <= langCount; lang++) {
+ for (lang = 1; lang <= langCount; lang++) {
- memset(buffer, 0, sizeof(buffer));
- memset(&req, 0, sizeof(req));
+ memset(buffer, 0, sizeof(buffer));
+ memset(&req, 0, sizeof(req));
- req.bmRequestType =
- USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
- req.bRequest = kUSBRqGetDescriptor;
- req.wValue = (kUSBStringDesc << 8) | serialIndex;
- req.wIndex = languages[lang];
- req.pData = buffer;
- req.wLength = sizeof(buffer);
- kr = (*dev)->DeviceRequest(dev, &req);
+ req.bmRequestType =
+ USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
+ req.bRequest = kUSBRqGetDescriptor;
+ req.wValue = (kUSBStringDesc << 8) | serialIndex;
+ req.wIndex = languages[lang];
+ req.pData = buffer;
+ req.wLength = sizeof(buffer);
+ kr = (*dev)->DeviceRequest(dev, &req);
- if (kr == kIOReturnSuccess && req.wLenDone > 0) {
- int i, count;
+ if (kr == kIOReturnSuccess && req.wLenDone > 0) {
+ int i, count;
- // skip first word, and copy the rest to the serial string,
- // changing shorts to bytes.
- count = (req.wLenDone - 1) / 2;
- for (i = 0; i < count; i++)
- serial[i] = buffer[i + 1];
- serial[i] = 0;
- break;
- }
- }
- }
- }
+ // skip first word, and copy the rest to the serial string,
+ // changing shorts to bytes.
+ count = (req.wLenDone - 1) / 2;
+ for (i = 0; i < count; i++)
+ serial[i] = buffer[i + 1];
+ serial[i] = 0;
+ break;
+ }
+ }
+ }
+ }
+
(*dev)->Release(dev);
- DBG("INFO: Found vid=%04x pid=%04x serial=%s\n", vendor, product,
- serial);
+ LOG(INFO) << android::base::StringPrintf("Found vid=%04x pid=%04x serial=%s\n",
+ vendor, product, serial);
- usb_handle* handle = CheckInterface((IOUSBInterfaceInterface**)iface,
+ usb_handle* handle = CheckInterface((IOUSBInterfaceInterface190**)iface,
vendor, product);
if (handle == NULL) {
- DBG("ERR: Could not find device interface: %08x\n", kr);
+ LOG(ERROR) << "Could not find device interface";
(*iface)->Release(iface);
continue;
}
- DBG("AndroidDeviceAdded calling register_usb_transport\n");
+ LOG(DEBUG) << "AndroidDeviceAdded calling register_usb_transport";
register_usb_transport(handle, (serial[0] ? serial : NULL), devpath, 1);
// Register for an interest notification of this device being removed.
@@ -274,7 +281,7 @@
&handle->usbNotification);
if (kIOReturnSuccess != kr) {
- DBG("ERR: Unable to create interest notification (%08x)\n", kr);
+ LOG(ERROR) << "Unable to create interest notification (" << std::hex << kr << ")";
}
}
}
@@ -286,38 +293,49 @@
if (messageType == kIOMessageServiceIsTerminated) {
if (!handle) {
- DBG("ERR: NULL handle\n");
+ LOG(ERROR) << "NULL handle";
return;
}
- DBG("AndroidInterfaceNotify\n");
+ LOG(DEBUG) << "AndroidInterfaceNotify";
IOObjectRelease(handle->usbNotification);
usb_kick(handle);
}
}
+// Used to clear both the endpoints before starting.
+// When adb quits, we might clear the host endpoint but not the device.
+// So we make sure both sides are clear before starting up.
+static bool ClearPipeStallBothEnds(IOUSBInterfaceInterface190** interface, UInt8 bulkEp) {
+ IOReturn rc = (*interface)->ClearPipeStallBothEnds(interface, bulkEp);
+ if (rc != kIOReturnSuccess) {
+ LOG(ERROR) << "Could not clear pipe stall both ends: " << std::hex << rc;
+ return false;
+ }
+ return true;
+}
+
//* TODO: simplify this further since we only register to get ADB interface
//* subclass+protocol events
static usb_handle*
-CheckInterface(IOUSBInterfaceInterface **interface, UInt16 vendor, UInt16 product)
+CheckInterface(IOUSBInterfaceInterface190 **interface, UInt16 vendor, UInt16 product)
{
- usb_handle* handle = NULL;
- IOReturn kr;
- UInt8 interfaceNumEndpoints, interfaceClass, interfaceSubClass, interfaceProtocol;
- UInt8 endpoint;
-
+ usb_handle* handle;
+ IOReturn kr;
+ UInt8 interfaceNumEndpoints, interfaceClass, interfaceSubClass, interfaceProtocol;
+ UInt8 endpoint;
//* Now open the interface. This will cause the pipes associated with
//* the endpoints in the interface descriptor to be instantiated
kr = (*interface)->USBInterfaceOpen(interface);
if (kr != kIOReturnSuccess) {
- DBG("ERR: Could not open interface: (%08x)\n", kr);
+ LOG(ERROR) << "Could not open interface: " << std::hex << kr;
return NULL;
}
//* Get the number of endpoints associated with this interface
kr = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
if (kr != kIOReturnSuccess) {
- DBG("ERR: Unable to get number of endpoints: (%08x)\n", kr);
+ LOG(ERROR) << "Unable to get number of endpoints: " << std::hex << kr;
goto err_get_num_ep;
}
@@ -325,22 +343,22 @@
if ((*interface)->GetInterfaceClass(interface, &interfaceClass) != kIOReturnSuccess ||
(*interface)->GetInterfaceSubClass(interface, &interfaceSubClass) != kIOReturnSuccess ||
(*interface)->GetInterfaceProtocol(interface, &interfaceProtocol) != kIOReturnSuccess) {
- DBG("ERR: Unable to get interface class, subclass and protocol\n");
+ LOG(ERROR) << "Unable to get interface class, subclass and protocol";
goto err_get_interface_class;
}
//* check to make sure interface class, subclass and protocol match ADB
//* avoid opening mass storage endpoints
- if (!is_adb_interface(vendor, product, interfaceClass,
- interfaceSubClass, interfaceProtocol))
+ if (!is_adb_interface(vendor, product, interfaceClass, interfaceSubClass, interfaceProtocol)) {
goto err_bad_adb_interface;
+ }
handle = reinterpret_cast<usb_handle*>(calloc(1, sizeof(usb_handle)));
if (handle == nullptr) goto err_bad_adb_interface;
//* Iterate over the endpoints for this interface and find the first
//* bulk in/out pipes available. These will be our read/write pipes.
- for (endpoint = 0; endpoint <= interfaceNumEndpoints; endpoint++) {
+ for (endpoint = 1; endpoint <= interfaceNumEndpoints; endpoint++) {
UInt8 transferType;
UInt16 maxPacketSize;
UInt8 interval;
@@ -349,22 +367,25 @@
kr = (*interface)->GetPipeProperties(interface, endpoint, &direction,
&number, &transferType, &maxPacketSize, &interval);
-
- if (kIOReturnSuccess == kr) {
- if (kUSBBulk != transferType)
- continue;
-
- if (kUSBIn == direction)
- handle->bulkIn = endpoint;
-
- if (kUSBOut == direction)
- handle->bulkOut = endpoint;
-
- handle->zero_mask = maxPacketSize - 1;
- } else {
- DBG("ERR: FindDeviceInterface - could not get pipe properties\n");
+ if (kr != kIOReturnSuccess) {
+ LOG(ERROR) << "FindDeviceInterface - could not get pipe properties: "
+ << std::hex << kr;
goto err_get_pipe_props;
}
+
+ if (kUSBBulk != transferType) continue;
+
+ if (kUSBIn == direction) {
+ handle->bulkIn = endpoint;
+ if (!ClearPipeStallBothEnds(interface, handle->bulkIn)) goto err_get_pipe_props;
+ }
+
+ if (kUSBOut == direction) {
+ handle->bulkOut = endpoint;
+ if (!ClearPipeStallBothEnds(interface, handle->bulkOut)) goto err_get_pipe_props;
+ }
+
+ handle->zero_mask = maxPacketSize - 1;
}
handle->interface = interface;
@@ -397,12 +418,12 @@
IOObjectRelease(notificationIterator);
IONotificationPortDestroy(notificationPort);
- DBG("RunLoopThread done\n");
+ LOG(DEBUG) << "RunLoopThread done";
return NULL;
}
static void usb_cleanup() {
- DBG("usb_cleanup\n");
+ LOG(DEBUG) << "usb_cleanup";
close_usb_devices();
if (currentRunLoop)
CFRunLoopStop(currentRunLoop);
@@ -443,18 +464,17 @@
return -1;
if (NULL == handle->interface) {
- DBG("ERR: usb_write interface was null\n");
+ LOG(ERROR) << "usb_write interface was null";
return -1;
}
if (0 == handle->bulkOut) {
- DBG("ERR: bulkOut endpoint not assigned\n");
+ LOG(ERROR) << "bulkOut endpoint not assigned";
return -1;
}
result =
- (*handle->interface)->WritePipe(
- handle->interface, handle->bulkOut, (void *)buf, len);
+ (*handle->interface)->WritePipe(handle->interface, handle->bulkOut, (void *)buf, len);
if ((result == 0) && (handle->zero_mask)) {
/* we need 0-markers and our transfer */
@@ -468,7 +488,7 @@
if (0 == result)
return 0;
- DBG("ERR: usb_write failed with status %d\n", result);
+ LOG(ERROR) << "usb_write failed with status: " << std::hex << result;
return -1;
}
@@ -486,19 +506,19 @@
}
if (NULL == handle->interface) {
- DBG("ERR: usb_read interface was null\n");
+ LOG(ERROR) << "usb_read interface was null";
return -1;
}
if (0 == handle->bulkIn) {
- DBG("ERR: bulkIn endpoint not assigned\n");
+ LOG(ERROR) << "bulkIn endpoint not assigned";
return -1;
}
result = (*handle->interface)->ReadPipe(handle->interface, handle->bulkIn, buf, &numBytes);
if (kIOUSBPipeStalled == result) {
- DBG(" Pipe stalled, clearing stall.\n");
+ LOG(ERROR) << "Pipe stalled, clearing stall.\n";
(*handle->interface)->ClearPipeStall(handle->interface, handle->bulkIn);
result = (*handle->interface)->ReadPipe(handle->interface, handle->bulkIn, buf, &numBytes);
}
@@ -506,7 +526,7 @@
if (kIOReturnSuccess == result)
return 0;
else {
- DBG("ERR: usb_read failed with status %x\n", result);
+ LOG(ERROR) << "usb_read failed with status: " << std::hex << result;
}
return -1;
@@ -519,6 +539,7 @@
void usb_kick(usb_handle *handle)
{
+ LOG(INFO) << "Kicking handle";
/* release the interface */
if (!handle)
return;
diff --git a/adb/usb_windows.cpp b/adb/usb_windows.cpp
index 25deb1b..b8cc5cf 100644
--- a/adb/usb_windows.cpp
+++ b/adb/usb_windows.cpp
@@ -33,6 +33,10 @@
/** Structure usb_handle describes our connection to the usb device via
AdbWinApi.dll. This structure is returned from usb_open() routine and
is expected in each subsequent call that is accessing the device.
+
+ Most members are protected by usb_lock, except for adb_{read,write}_pipe which
+ rely on AdbWinApi.dll's handle validation and AdbCloseHandle(endpoint)'s
+ ability to break a thread out of pipe IO.
*/
struct usb_handle {
/// Previous entry in the list of opened usb handles
@@ -86,6 +90,9 @@
/// registers usb transport for them.
void find_devices();
+/// Kicks all USB devices
+static void kick_devices();
+
/// Entry point for thread that polls (every second) for new usb interfaces.
/// This routine calls find_devices in infinite loop.
void* device_poll_thread(void* unused);
@@ -111,9 +118,6 @@
/// Closes opened usb handle
int usb_close(usb_handle* handle);
-/// Gets interface (device) name for an opened usb handle
-const char *usb_name(usb_handle* handle);
-
int known_device_locked(const char* dev_name) {
usb_handle* usb;
@@ -177,17 +181,99 @@
return NULL;
}
+static LRESULT CALLBACK _power_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam) {
+ switch (uMsg) {
+ case WM_POWERBROADCAST:
+ switch (wParam) {
+ case PBT_APMRESUMEAUTOMATIC:
+ // Resuming from sleep or hibernation, so kick all existing USB devices
+ // and then allow the device_poll_thread to redetect USB devices from
+ // scratch. If we don't do this, existing USB devices will never respond
+ // to us because they'll be waiting for the connect/auth handshake.
+ D("Received (WM_POWERBROADCAST, PBT_APMRESUMEAUTOMATIC) notification, "
+ "so kicking all USB devices\n");
+ kick_devices();
+ return TRUE;
+ }
+ }
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+}
+
+static void* _power_notification_thread(void* unused) {
+ // This uses a thread with its own window message pump to get power
+ // notifications. If adb runs from a non-interactive service account, this
+ // might not work (not sure). If that happens to not work, we could use
+ // heavyweight WMI APIs to get power notifications. But for the common case
+ // of a developer's interactive session, a window message pump is more
+ // appropriate.
+ D("Created power notification thread\n");
+
+ // Window class names are process specific.
+ static const WCHAR kPowerNotificationWindowClassName[] =
+ L"PowerNotificationWindow";
+
+ // Get the HINSTANCE corresponding to the module that _power_window_proc
+ // is in (the main module).
+ const HINSTANCE instance = GetModuleHandleW(NULL);
+ if (!instance) {
+ // This is such a common API call that this should never fail.
+ fatal("GetModuleHandleW failed: %s",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ }
+
+ WNDCLASSEXW wndclass;
+ memset(&wndclass, 0, sizeof(wndclass));
+ wndclass.cbSize = sizeof(wndclass);
+ wndclass.lpfnWndProc = _power_window_proc;
+ wndclass.hInstance = instance;
+ wndclass.lpszClassName = kPowerNotificationWindowClassName;
+ if (!RegisterClassExW(&wndclass)) {
+ fatal("RegisterClassExW failed: %s",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ }
+
+ if (!CreateWindowExW(WS_EX_NOACTIVATE, kPowerNotificationWindowClassName,
+ L"ADB Power Notification Window", WS_POPUP, 0, 0, 0, 0,
+ NULL, NULL, instance, NULL)) {
+ fatal("CreateWindowExW failed: %s",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ }
+
+ MSG msg;
+ while (GetMessageW(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
+
+ // GetMessageW() will return false if a quit message is posted. We don't
+ // do that, but it might be possible for that to occur when logging off or
+ // shutting down. Not a big deal since the whole process will be going away
+ // soon anyway.
+ D("Power notification thread exiting\n");
+
+ return NULL;
+}
+
void usb_init() {
if (!adb_thread_create(device_poll_thread, nullptr)) {
- fatal_errno("cannot create input thread");
+ fatal_errno("cannot create device poll thread");
+ }
+ if (!adb_thread_create(_power_notification_thread, nullptr)) {
+ fatal_errno("cannot create power notification thread");
}
}
usb_handle* do_usb_open(const wchar_t* interface_name) {
+ unsigned long name_len = 0;
+
// Allocate our handle
- usb_handle* ret = (usb_handle*)malloc(sizeof(usb_handle));
- if (NULL == ret)
- return NULL;
+ usb_handle* ret = (usb_handle*)calloc(1, sizeof(usb_handle));
+ if (NULL == ret) {
+ D("Could not allocate %u bytes for usb_handle: %s\n", sizeof(usb_handle),
+ strerror(errno));
+ goto fail;
+ }
// Set linkers back to the handle
ret->next = ret;
@@ -195,11 +281,10 @@
// Create interface.
ret->adb_interface = AdbCreateInterfaceByName(interface_name);
-
if (NULL == ret->adb_interface) {
- free(ret);
- errno = GetLastError();
- return NULL;
+ D("AdbCreateInterfaceByName failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ goto fail;
}
// Open read pipe (endpoint)
@@ -207,45 +292,60 @@
AdbOpenDefaultBulkReadEndpoint(ret->adb_interface,
AdbOpenAccessTypeReadWrite,
AdbOpenSharingModeReadWrite);
- if (NULL != ret->adb_read_pipe) {
- // Open write pipe (endpoint)
- ret->adb_write_pipe =
- AdbOpenDefaultBulkWriteEndpoint(ret->adb_interface,
- AdbOpenAccessTypeReadWrite,
- AdbOpenSharingModeReadWrite);
- if (NULL != ret->adb_write_pipe) {
- // Save interface name
- unsigned long name_len = 0;
-
- // First get expected name length
- AdbGetInterfaceName(ret->adb_interface,
- NULL,
- &name_len,
- true);
- if (0 != name_len) {
- ret->interface_name = (char*)malloc(name_len);
-
- if (NULL != ret->interface_name) {
- // Now save the name
- if (AdbGetInterfaceName(ret->adb_interface,
- ret->interface_name,
- &name_len,
- true)) {
- // We're done at this point
- return ret;
- }
- } else {
- SetLastError(ERROR_OUTOFMEMORY);
- }
- }
- }
+ if (NULL == ret->adb_read_pipe) {
+ D("AdbOpenDefaultBulkReadEndpoint failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ goto fail;
}
- // Something went wrong.
- int saved_errno = GetLastError();
- usb_cleanup_handle(ret);
- free(ret);
- SetLastError(saved_errno);
+ // Open write pipe (endpoint)
+ ret->adb_write_pipe =
+ AdbOpenDefaultBulkWriteEndpoint(ret->adb_interface,
+ AdbOpenAccessTypeReadWrite,
+ AdbOpenSharingModeReadWrite);
+ if (NULL == ret->adb_write_pipe) {
+ D("AdbOpenDefaultBulkWriteEndpoint failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ goto fail;
+ }
+
+ // Save interface name
+ // First get expected name length
+ AdbGetInterfaceName(ret->adb_interface,
+ NULL,
+ &name_len,
+ true);
+ if (0 == name_len) {
+ D("AdbGetInterfaceName returned name length of zero: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ goto fail;
+ }
+
+ ret->interface_name = (char*)malloc(name_len);
+ if (NULL == ret->interface_name) {
+ D("Could not allocate %lu bytes for interface_name: %s\n", name_len,
+ strerror(errno));
+ goto fail;
+ }
+
+ // Now save the name
+ if (!AdbGetInterfaceName(ret->adb_interface,
+ ret->interface_name,
+ &name_len,
+ true)) {
+ D("AdbGetInterfaceName failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ goto fail;
+ }
+
+ // We're done at this point
+ return ret;
+
+fail:
+ if (NULL != ret) {
+ usb_cleanup_handle(ret);
+ free(ret);
+ }
return NULL;
}
@@ -253,99 +353,130 @@
int usb_write(usb_handle* handle, const void* data, int len) {
unsigned long time_out = 5000;
unsigned long written = 0;
- int ret;
+ int err = 0;
D("usb_write %d\n", len);
- if (NULL != handle) {
- // Perform write
- ret = AdbWriteEndpointSync(handle->adb_write_pipe,
- (void*)data,
- (unsigned long)len,
- &written,
- time_out);
- int saved_errno = GetLastError();
-
- if (ret) {
- // Make sure that we've written what we were asked to write
- D("usb_write got: %ld, expected: %d\n", written, len);
- if (written == (unsigned long)len) {
- if(handle->zero_mask && (len & handle->zero_mask) == 0) {
- // Send a zero length packet
- AdbWriteEndpointSync(handle->adb_write_pipe,
- (void*)data,
- 0,
- &written,
- time_out);
- }
- return 0;
- }
- } else {
- // assume ERROR_INVALID_HANDLE indicates we are disconnected
- if (saved_errno == ERROR_INVALID_HANDLE)
- usb_kick(handle);
- }
- errno = saved_errno;
- } else {
- D("usb_write NULL handle\n");
- SetLastError(ERROR_INVALID_HANDLE);
+ if (NULL == handle) {
+ D("usb_write was passed NULL handle\n");
+ err = EINVAL;
+ goto fail;
}
- D("usb_write failed: %d\n", errno);
+ // Perform write
+ if (!AdbWriteEndpointSync(handle->adb_write_pipe,
+ (void*)data,
+ (unsigned long)len,
+ &written,
+ time_out)) {
+ D("AdbWriteEndpointSync failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ err = EIO;
+ goto fail;
+ }
+ // Make sure that we've written what we were asked to write
+ D("usb_write got: %ld, expected: %d\n", written, len);
+ if (written != (unsigned long)len) {
+ // If this occurs, this code should be changed to repeatedly call
+ // AdbWriteEndpointSync() until all bytes are written.
+ D("AdbWriteEndpointSync was supposed to write %d, but only wrote %ld\n",
+ len, written);
+ err = EIO;
+ goto fail;
+ }
+
+ if (handle->zero_mask && (len & handle->zero_mask) == 0) {
+ // Send a zero length packet
+ if (!AdbWriteEndpointSync(handle->adb_write_pipe,
+ (void*)data,
+ 0,
+ &written,
+ time_out)) {
+ D("AdbWriteEndpointSync of zero length packet failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ err = EIO;
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ // Any failure should cause us to kick the device instead of leaving it a
+ // zombie state with potential to hang.
+ if (NULL != handle) {
+ D("Kicking device due to error in usb_write\n");
+ usb_kick(handle);
+ }
+
+ D("usb_write failed\n");
+ errno = err;
return -1;
}
int usb_read(usb_handle *handle, void* data, int len) {
unsigned long time_out = 0;
unsigned long read = 0;
- int ret;
+ int err = 0;
D("usb_read %d\n", len);
- if (NULL != handle) {
- while (len > 0) {
- int xfer = (len > 4096) ? 4096 : len;
-
- ret = AdbReadEndpointSync(handle->adb_read_pipe,
- data,
- (unsigned long)xfer,
- &read,
- time_out);
- int saved_errno = GetLastError();
- D("usb_write got: %ld, expected: %d, errno: %d\n", read, xfer, saved_errno);
- if (ret) {
- data = (char *)data + read;
- len -= read;
-
- if (len == 0)
- return 0;
- } else {
- // assume ERROR_INVALID_HANDLE indicates we are disconnected
- if (saved_errno == ERROR_INVALID_HANDLE)
- usb_kick(handle);
- break;
- }
- errno = saved_errno;
- }
- } else {
- D("usb_read NULL handle\n");
- SetLastError(ERROR_INVALID_HANDLE);
+ if (NULL == handle) {
+ D("usb_read was passed NULL handle\n");
+ err = EINVAL;
+ goto fail;
}
- D("usb_read failed: %d\n", errno);
+ while (len > 0) {
+ if (!AdbReadEndpointSync(handle->adb_read_pipe, data, len, &read,
+ time_out)) {
+ D("AdbReadEndpointSync failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ err = EIO;
+ goto fail;
+ }
+ D("usb_read got: %ld, expected: %d\n", read, len);
+ data = (char *)data + read;
+ len -= read;
+ }
+
+ return 0;
+
+fail:
+ // Any failure should cause us to kick the device instead of leaving it a
+ // zombie state with potential to hang.
+ if (NULL != handle) {
+ D("Kicking device due to error in usb_read\n");
+ usb_kick(handle);
+ }
+
+ D("usb_read failed\n");
+ errno = err;
return -1;
}
+// Wrapper around AdbCloseHandle() that logs diagnostics.
+static void _adb_close_handle(ADBAPIHANDLE adb_handle) {
+ if (!AdbCloseHandle(adb_handle)) {
+ D("AdbCloseHandle(%p) failed: %s\n", adb_handle,
+ SystemErrorCodeToString(GetLastError()).c_str());
+ }
+}
+
void usb_cleanup_handle(usb_handle* handle) {
+ D("usb_cleanup_handle\n");
if (NULL != handle) {
if (NULL != handle->interface_name)
free(handle->interface_name);
+ // AdbCloseHandle(pipe) will break any threads out of pending IO calls and
+ // wait until the pipe no longer uses the interface. Then we can
+ // AdbCloseHandle() the interface.
if (NULL != handle->adb_write_pipe)
- AdbCloseHandle(handle->adb_write_pipe);
+ _adb_close_handle(handle->adb_write_pipe);
if (NULL != handle->adb_read_pipe)
- AdbCloseHandle(handle->adb_read_pipe);
+ _adb_close_handle(handle->adb_read_pipe);
if (NULL != handle->adb_interface)
- AdbCloseHandle(handle->adb_interface);
+ _adb_close_handle(handle->adb_interface);
handle->interface_name = NULL;
handle->adb_write_pipe = NULL;
@@ -354,16 +485,22 @@
}
}
+static void usb_kick_locked(usb_handle* handle) {
+ // The reason the lock must be acquired before calling this function is in
+ // case multiple threads are trying to kick the same device at the same time.
+ usb_cleanup_handle(handle);
+}
+
void usb_kick(usb_handle* handle) {
+ D("usb_kick\n");
if (NULL != handle) {
adb_mutex_lock(&usb_lock);
- usb_cleanup_handle(handle);
+ usb_kick_locked(handle);
adb_mutex_unlock(&usb_lock);
} else {
- SetLastError(ERROR_INVALID_HANDLE);
- errno = ERROR_INVALID_HANDLE;
+ errno = EINVAL;
}
}
@@ -391,16 +528,6 @@
return 0;
}
-const char *usb_name(usb_handle* handle) {
- if (NULL == handle) {
- SetLastError(ERROR_INVALID_HANDLE);
- errno = ERROR_INVALID_HANDLE;
- return NULL;
- }
-
- return (const char*)handle->interface_name;
-}
-
int recognized_device(usb_handle* handle) {
if (NULL == handle)
return 0;
@@ -410,6 +537,8 @@
if (!AdbGetUsbDeviceDescriptor(handle->adb_interface,
&device_desc)) {
+ D("AdbGetUsbDeviceDescriptor failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
return 0;
}
@@ -418,6 +547,8 @@
if (!AdbGetUsbInterfaceDescriptor(handle->adb_interface,
&interf_desc)) {
+ D("AdbGetUsbInterfaceDescriptor failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
return 0;
}
@@ -434,6 +565,10 @@
// assuming zero is a valid bulk endpoint ID
if (AdbGetEndpointInformation(handle->adb_interface, 0, &endpoint_info)) {
handle->zero_mask = endpoint_info.max_packet_size - 1;
+ D("device zero_mask: 0x%x\n", handle->zero_mask);
+ } else {
+ D("AdbGetEndpointInformation failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
}
}
@@ -455,8 +590,11 @@
ADBAPIHANDLE enum_handle =
AdbEnumInterfaces(usb_class_id, true, true, true);
- if (NULL == enum_handle)
+ if (NULL == enum_handle) {
+ D("AdbEnumInterfaces failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
return;
+ }
while (AdbNextInterface(enum_handle, next_interface, &entry_buffer_size)) {
// TODO: FIXME - temp hack converting wchar_t into char.
@@ -493,7 +631,8 @@
free(handle);
}
} else {
- D("cannot get serial number\n");
+ D("cannot get serial number: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
usb_cleanup_handle(handle);
free(handle);
}
@@ -507,5 +646,21 @@
entry_buffer_size = sizeof(entry_buffer);
}
- AdbCloseHandle(enum_handle);
+ if (GetLastError() != ERROR_NO_MORE_ITEMS) {
+ // Only ERROR_NO_MORE_ITEMS is expected at the end of enumeration.
+ D("AdbNextInterface failed: %s\n",
+ SystemErrorCodeToString(GetLastError()).c_str());
+ }
+
+ _adb_close_handle(enum_handle);
+}
+
+static void kick_devices() {
+ // Need to acquire lock to safely walk the list which might be modified
+ // by another thread.
+ adb_mutex_lock(&usb_lock);
+ for (usb_handle* usb = handle_list.next; usb != &handle_list; usb = usb->next) {
+ usb_kick_locked(usb);
+ }
+ adb_mutex_unlock(&usb_lock);
}
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/base/Android.mk b/base/Android.mk
index 7bd317b..4e135f6 100644
--- a/base/Android.mk
+++ b/base/Android.mk
@@ -21,6 +21,7 @@
logging.cpp \
stringprintf.cpp \
strings.cpp \
+ test_utils.cpp \
libbase_test_src_files := \
file_test.cpp \
@@ -28,7 +29,6 @@
stringprintf_test.cpp \
strings_test.cpp \
test_main.cpp \
- test_utils.cpp \
libbase_cppflags := \
-Wall \
diff --git a/base/file_test.cpp b/base/file_test.cpp
index b138094..77b9268 100644
--- a/base/file_test.cpp
+++ b/base/file_test.cpp
@@ -24,7 +24,7 @@
#include <string>
-#include "test_utils.h"
+#include "base/test_utils.h"
TEST(file, ReadFileToString_ENOENT) {
std::string s("hello");
@@ -34,23 +34,13 @@
EXPECT_EQ("", s); // s was cleared.
}
-TEST(file, ReadFileToString_success) {
- std::string s("hello");
- ASSERT_TRUE(android::base::ReadFileToString("/proc/version", &s))
- << strerror(errno);
- EXPECT_GT(s.length(), 6U);
- EXPECT_EQ('\n', s[s.length() - 1]);
- s[5] = 0;
- EXPECT_STREQ("Linux", s.c_str());
-}
-
-TEST(file, WriteStringToFile) {
+TEST(file, ReadFileToString_WriteStringToFile) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
- ASSERT_TRUE(android::base::WriteStringToFile("abc", tf.filename))
+ ASSERT_TRUE(android::base::WriteStringToFile("abc", tf.path))
<< strerror(errno);
std::string s;
- ASSERT_TRUE(android::base::ReadFileToString(tf.filename, &s))
+ ASSERT_TRUE(android::base::ReadFileToString(tf.path, &s))
<< strerror(errno);
EXPECT_EQ("abc", s);
}
@@ -61,16 +51,16 @@
TEST(file, WriteStringToFile2) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
- ASSERT_TRUE(android::base::WriteStringToFile("abc", tf.filename, 0660,
+ ASSERT_TRUE(android::base::WriteStringToFile("abc", tf.path, 0660,
getuid(), getgid()))
<< strerror(errno);
struct stat sb;
- ASSERT_EQ(0, stat(tf.filename, &sb));
+ ASSERT_EQ(0, stat(tf.path, &sb));
ASSERT_EQ(0660U, static_cast<unsigned int>(sb.st_mode & ~S_IFMT));
ASSERT_EQ(getuid(), sb.st_uid);
ASSERT_EQ(getgid(), sb.st_gid);
std::string s;
- ASSERT_TRUE(android::base::ReadFileToString(tf.filename, &s))
+ ASSERT_TRUE(android::base::ReadFileToString(tf.path, &s))
<< strerror(errno);
EXPECT_EQ("abc", s);
}
@@ -88,28 +78,21 @@
EXPECT_EQ("abc", s);
}
-TEST(file, ReadFully) {
- int fd = open("/proc/version", O_RDONLY);
- ASSERT_NE(-1, fd) << strerror(errno);
-
- char buf[1024];
- memset(buf, 0, sizeof(buf));
- ASSERT_TRUE(android::base::ReadFully(fd, buf, 5));
- ASSERT_STREQ("Linux", buf);
-
- ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno);
-
- ASSERT_FALSE(android::base::ReadFully(fd, buf, sizeof(buf)));
-
- close(fd);
-}
-
TEST(file, WriteFully) {
TemporaryFile tf;
ASSERT_TRUE(tf.fd != -1);
ASSERT_TRUE(android::base::WriteFully(tf.fd, "abc", 3));
+
+ ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
+
std::string s;
- ASSERT_TRUE(android::base::ReadFileToString(tf.filename, &s))
+ s.resize(3);
+ ASSERT_TRUE(android::base::ReadFully(tf.fd, &s[0], s.size()))
<< strerror(errno);
EXPECT_EQ("abc", s);
+
+ ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)) << strerror(errno);
+
+ s.resize(1024);
+ ASSERT_FALSE(android::base::ReadFully(tf.fd, &s[0], s.size()));
}
diff --git a/base/test_utils.h b/base/include/base/test_utils.h
similarity index 69%
copy from base/test_utils.h
copy to base/include/base/test_utils.h
index 132d3a7..402e0a5 100644
--- a/base/test_utils.h
+++ b/base/include/base/test_utils.h
@@ -17,16 +17,35 @@
#ifndef TEST_UTILS_H
#define TEST_UTILS_H
+#include <string>
+
+#include <base/macros.h>
+
class TemporaryFile {
public:
TemporaryFile();
~TemporaryFile();
int fd;
- char filename[1024];
+ char path[1024];
private:
- void init(const char* tmp_dir);
+ void init(const std::string& tmp_dir);
+
+ DISALLOW_COPY_AND_ASSIGN(TemporaryFile);
+};
+
+class TemporaryDir {
+ public:
+ TemporaryDir();
+ ~TemporaryDir();
+
+ char path[1024];
+
+ private:
+ bool init(const std::string& tmp_dir);
+
+ DISALLOW_COPY_AND_ASSIGN(TemporaryDir);
};
#endif // TEST_UTILS_H
diff --git a/base/logging.cpp b/base/logging.cpp
index 34229a2..e9e06df 100644
--- a/base/logging.cpp
+++ b/base/logging.cpp
@@ -70,9 +70,14 @@
static char progname[MAX_PATH] = {};
if (first) {
- // TODO(danalbert): This is a full path on Windows. Just get the basename.
- DWORD nchars = GetModuleFileName(nullptr, progname, sizeof(progname));
- DCHECK_GT(nchars, 0U);
+ CHAR longname[MAX_PATH];
+ DWORD nchars = GetModuleFileNameA(nullptr, longname, arraysize(longname));
+ if ((nchars >= arraysize(longname)) || (nchars == 0)) {
+ // String truncation or some other error.
+ strcpy(progname, "<unknown>");
+ } else {
+ strcpy(progname, basename(longname));
+ }
first = false;
}
@@ -82,25 +87,22 @@
class mutex {
public:
mutex() {
- semaphore_ = CreateSemaphore(nullptr, 1, 1, nullptr);
- CHECK(semaphore_ != nullptr) << "Failed to create Mutex";
+ InitializeCriticalSection(&critical_section_);
}
~mutex() {
- CloseHandle(semaphore_);
+ DeleteCriticalSection(&critical_section_);
}
void lock() {
- DWORD result = WaitForSingleObject(semaphore_, INFINITE);
- CHECK_EQ(result, WAIT_OBJECT_0) << GetLastError();
+ EnterCriticalSection(&critical_section_);
}
void unlock() {
- bool result = ReleaseSemaphore(semaphore_, 1, nullptr);
- CHECK(result);
+ LeaveCriticalSection(&critical_section_);
}
private:
- HANDLE semaphore_;
+ CRITICAL_SECTION critical_section_;
};
template <typename LockT>
@@ -147,8 +149,9 @@
void StderrLogger(LogId, LogSeverity severity, const char*, const char* file,
unsigned int line, const char* message) {
- static const char* log_characters = "VDIWEF";
- CHECK_EQ(strlen(log_characters), FATAL + 1U);
+ static const char log_characters[] = "VDIWEF";
+ static_assert(arraysize(log_characters) - 1 == FATAL + 1,
+ "Mismatch in size of log_characters and values in LogSeverity");
char severity_char = log_characters[severity];
fprintf(stderr, "%s %c %5d %5d %s:%u] %s\n", ProgramInvocationName(),
severity_char, getpid(), gettid(), file, line, message);
@@ -197,6 +200,20 @@
InitLogging(argv);
}
+// TODO: make this public; it's independently useful.
+class ErrnoRestorer {
+ public:
+ ErrnoRestorer(int saved_errno) : saved_errno_(saved_errno) {
+ }
+
+ ~ErrnoRestorer() {
+ errno = saved_errno_;
+ }
+
+ private:
+ const int saved_errno_;
+};
+
void InitLogging(char* argv[]) {
if (gInitialized) {
return;
@@ -257,19 +274,25 @@
gLogger = std::move(logger);
}
+// We can't use basename(3) because this code runs on the Mac, which doesn't
+// have a non-modifying basename.
+static const char* GetFileBasename(const char* file) {
+ const char* last_slash = strrchr(file, '/');
+ return (last_slash == nullptr) ? file : last_slash + 1;
+}
+
// This indirection greatly reduces the stack impact of having lots of
// checks/logging in a function.
class LogMessageData {
public:
LogMessageData(const char* file, unsigned int line, LogId id,
- LogSeverity severity, int error)
- : file_(file),
+ LogSeverity severity, int error, int saved_errno)
+ : file_(GetFileBasename(file)),
line_number_(line),
id_(id),
severity_(severity),
- error_(error) {
- const char* last_slash = strrchr(file, '/');
- file = (last_slash == nullptr) ? file : last_slash + 1;
+ error_(error),
+ errno_restorer_(saved_errno) {
}
const char* GetFile() const {
@@ -307,13 +330,14 @@
const LogId id_;
const LogSeverity severity_;
const int error_;
+ ErrnoRestorer errno_restorer_;
DISALLOW_COPY_AND_ASSIGN(LogMessageData);
};
LogMessage::LogMessage(const char* file, unsigned int line, LogId id,
LogSeverity severity, int error)
- : data_(new LogMessageData(file, line, id, severity, error)) {
+ : data_(new LogMessageData(file, line, id, severity, error, errno)) {
}
LogMessage::~LogMessage() {
diff --git a/base/logging_test.cpp b/base/logging_test.cpp
index c91857a..c12dfa5 100644
--- a/base/logging_test.cpp
+++ b/base/logging_test.cpp
@@ -16,12 +16,14 @@
#include "base/logging.h"
+#include <libgen.h>
+
#include <regex>
#include <string>
#include "base/file.h"
#include "base/stringprintf.h"
-#include "test_utils.h"
+#include "base/test_utils.h"
#include <gtest/gtest.h>
@@ -76,10 +78,10 @@
const char* message) {
static const char* log_characters = "VDIWEF";
char log_char = log_characters[severity];
+ std::string holder(__FILE__);
return android::base::StringPrintf(
- "%c[[:space:]]+[[:digit:]]+[[:space:]]+[[:digit:]]+ " __FILE__
- ":[[:digit:]]+] %s",
- log_char, message);
+ "%c[[:space:]]+[[:digit:]]+[[:space:]]+[[:digit:]]+ %s:[[:digit:]]+] %s",
+ log_char, basename(&holder[0]), message);
}
TEST(logging, LOG) {
@@ -100,7 +102,7 @@
#if !defined(_WIN32)
std::regex message_regex(
make_log_pattern(android::base::WARNING, "foobar"));
- ASSERT_TRUE(std::regex_search(output, message_regex));
+ ASSERT_TRUE(std::regex_search(output, message_regex)) << output;
#endif
}
@@ -116,7 +118,7 @@
#if !defined(_WIN32)
std::regex message_regex(
make_log_pattern(android::base::INFO, "foobar"));
- ASSERT_TRUE(std::regex_search(output, message_regex));
+ ASSERT_TRUE(std::regex_search(output, message_regex)) << output;
#endif
}
@@ -143,7 +145,7 @@
#if !defined(_WIN32)
std::regex message_regex(
make_log_pattern(android::base::DEBUG, "foobar"));
- ASSERT_TRUE(std::regex_search(output, message_regex));
+ ASSERT_TRUE(std::regex_search(output, message_regex)) << output;
#endif
}
}
@@ -162,7 +164,7 @@
#if !defined(_WIN32)
std::regex message_regex(make_log_pattern(
android::base::INFO, "foobar: No such file or directory"));
- ASSERT_TRUE(std::regex_search(output, message_regex));
+ ASSERT_TRUE(std::regex_search(output, message_regex)) << output;
#endif
}
}
@@ -183,7 +185,7 @@
android::base::StringPrintf("%s unimplemented ", __PRETTY_FUNCTION__);
std::regex message_regex(
make_log_pattern(android::base::ERROR, expected_message.c_str()));
- ASSERT_TRUE(std::regex_search(output, message_regex));
+ ASSERT_TRUE(std::regex_search(output, message_regex)) << output;
#endif
}
}
diff --git a/base/stringprintf_test.cpp b/base/stringprintf_test.cpp
index 54b2b6c..5cc2086 100644
--- a/base/stringprintf_test.cpp
+++ b/base/stringprintf_test.cpp
@@ -20,14 +20,11 @@
#include <string>
-// The z size sepcifier isn't supported on Windows, so this test isn't useful.
-#if !defined(_WIN32)
TEST(StringPrintfTest, HexSizeT) {
size_t size = 0x00107e59;
EXPECT_EQ("00107e59", android::base::StringPrintf("%08zx", size));
EXPECT_EQ("0x00107e59", android::base::StringPrintf("0x%08zx", size));
}
-#endif
TEST(StringPrintfTest, StringAppendF) {
std::string s("a");
diff --git a/base/test_utils.cpp b/base/test_utils.cpp
index 0517bc7..b0c5a12 100644
--- a/base/test_utils.cpp
+++ b/base/test_utils.cpp
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-#include "test_utils.h"
+#include "base/logging.h"
+#include "base/test_utils.h"
+#include "utils/Compat.h" // For OS_PATH_SEPARATOR.
#include <fcntl.h>
#include <stdio.h>
@@ -24,36 +26,77 @@
#if defined(_WIN32)
#include <windows.h>
+#include <direct.h>
#endif
-TemporaryFile::TemporaryFile() {
-#if defined(__ANDROID__)
- init("/data/local/tmp");
-#elif defined(_WIN32)
- char wd[MAX_PATH] = {};
- _getcwd(wd, sizeof(wd));
- init(wd);
-#else
- init("/tmp");
+#include <string>
+
+#ifdef _WIN32
+int mkstemp(char* template_name) {
+ if (_mktemp(template_name) == nullptr) {
+ return -1;
+ }
+ // Use open() to match the close() that TemporaryFile's destructor does.
+ // Note that on Windows, this does CR/LF translation and _setmode() should
+ // be used to change that if appropriate.
+ return open(template_name, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
+}
+
+char* mkdtemp(char* template_name) {
+ if (_mktemp(template_name) == nullptr) {
+ return nullptr;
+ }
+ if (_mkdir(template_name) == -1) {
+ return nullptr;
+ }
+ return template_name;
+}
#endif
+
+static std::string GetSystemTempDir() {
+#if defined(__ANDROID__)
+ return "/data/local/tmp";
+#elif defined(_WIN32)
+ char tmp_dir[MAX_PATH];
+ DWORD result = GetTempPathA(sizeof(tmp_dir), tmp_dir);
+ CHECK_NE(result, 0ul) << "GetTempPathA failed, error: " << GetLastError();
+ CHECK_LT(result, sizeof(tmp_dir)) << "path truncated to: " << result;
+
+ // GetTempPath() returns a path with a trailing slash, but init()
+ // does not expect that, so remove it.
+ CHECK_EQ(tmp_dir[result - 1], '\\');
+ tmp_dir[result - 1] = '\0';
+ return tmp_dir;
+#else
+ return "/tmp";
+#endif
+}
+
+TemporaryFile::TemporaryFile() {
+ init(GetSystemTempDir());
}
TemporaryFile::~TemporaryFile() {
close(fd);
- unlink(filename);
+ unlink(path);
}
-void TemporaryFile::init(const char* tmp_dir) {
- snprintf(filename, sizeof(filename), "%s/TemporaryFile-XXXXXX", tmp_dir);
-#if !defined(_WIN32)
- fd = mkstemp(filename);
-#else
- // Windows doesn't have mkstemp, and tmpfile creates the file in the root
- // directory, requiring root (?!) permissions. We have to settle for mktemp.
- if (mktemp(filename) == nullptr) {
- abort();
- }
+void TemporaryFile::init(const std::string& tmp_dir) {
+ snprintf(path, sizeof(path), "%s%cTemporaryFile-XXXXXX", tmp_dir.c_str(),
+ OS_PATH_SEPARATOR);
+ fd = mkstemp(path);
+}
- fd = open(filename, O_RDWR | O_NOINHERIT | O_CREAT, _S_IREAD | _S_IWRITE);
-#endif
+TemporaryDir::TemporaryDir() {
+ init(GetSystemTempDir());
+}
+
+TemporaryDir::~TemporaryDir() {
+ rmdir(path);
+}
+
+bool TemporaryDir::init(const std::string& tmp_dir) {
+ snprintf(path, sizeof(path), "%s%cTemporaryDir-XXXXXX", tmp_dir.c_str(),
+ OS_PATH_SEPARATOR);
+ return (mkdtemp(path) != nullptr);
}
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/Android.mk b/crash_reporter/Android.mk
new file mode 100644
index 0000000..6b98af4
--- /dev/null
+++ b/crash_reporter/Android.mk
@@ -0,0 +1,129 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(HOST_OS),linux)
+
+crash_reporter_cpp_extension := .cc
+
+crash_reporter_src := crash_collector.cc \
+ kernel_collector.cc \
+ kernel_warning_collector.cc \
+ udev_collector.cc \
+ unclean_shutdown_collector.cc \
+ user_collector.cc
+
+crash_reporter_includes := external/gtest/include
+
+crash_reporter_test_src := crash_collector_test.cc \
+ crash_reporter_logs_test.cc \
+ kernel_collector_test.cc \
+ testrunner.cc \
+ udev_collector_test.cc \
+ unclean_shutdown_collector_test.cc \
+ user_collector_test.cc
+
+warn_collector_src := warn_collector.l
+
+# Crash reporter static library.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libcrash
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+LOCAL_C_INCLUDES := $(crash_reporter_includes)
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_SHARED_LIBRARIES := libchrome \
+ libchromeos \
+ libcutils \
+ libdbus \
+ libmetrics \
+ libpcrecpp
+LOCAL_SRC_FILES := $(crash_reporter_src)
+include $(BUILD_STATIC_LIBRARY)
+
+# Crash reporter client.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_reporter
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+LOCAL_C_INCLUDES := $(crash_reporter_includes)
+LOCAL_REQUIRED_MODULES := core2md \
+ crash_reporter_logs.conf \
+ crash_sender \
+ dbus-send \
+ init.crash_reporter.rc
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_SHARED_LIBRARIES := libchrome \
+ libchromeos \
+ libcutils \
+ libdbus \
+ libmetrics \
+ libpcrecpp
+LOCAL_SRC_FILES := crash_reporter.cc
+LOCAL_STATIC_LIBRARIES := libcrash
+include $(BUILD_EXECUTABLE)
+
+# Crash sender script.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_sender
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)
+LOCAL_SRC_FILES := crash_sender
+include $(BUILD_PREBUILT)
+
+# Warn collector client.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := warn_collector
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+LOCAL_SHARED_LIBRARIES := libmetrics
+LOCAL_SRC_FILES := $(warn_collector_src)
+include $(BUILD_EXECUTABLE)
+
+# Crash reporter init script.
+# ========================================================
+ifdef TARGET_COPY_OUT_INITRCD
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.crash_reporter.rc
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_INITRCD)
+LOCAL_SRC_FILES := init.crash_reporter.rc
+include $(BUILD_PREBUILT)
+endif
+
+# Crash reporter logs conf file.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_reporter_logs.conf
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/etc
+LOCAL_SRC_FILES := crash_reporter_logs.conf
+include $(BUILD_PREBUILT)
+
+# Crash reporter tests.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := crash_reporter_tests
+LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
+LOCAL_SHARED_LIBRARIES := libchrome \
+ libchromeos \
+ libdbus \
+ libpcrecpp
+LOCAL_SRC_FILES := $(crash_reporter_test_src)
+LOCAL_STATIC_LIBRARIES := libcrash libgmock
+include $(BUILD_NATIVE_TEST)
+
+endif # HOST_OS == linux
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/crash_collector.cc b/crash_reporter/crash_collector.cc
new file mode 100644
index 0000000..77755f4
--- /dev/null
+++ b/crash_reporter/crash_collector.cc
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2012 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 "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/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[] = "/data/misc/crash_reporter/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() {
+}
+
+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;
+}
+
+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()));
+}
+
+FilePath CrashCollector::GetCrashDirectoryInfo(
+ mode_t *mode,
+ uid_t *directory_owner,
+ gid_t *directory_group) {
+ *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) {
+ if (out_of_capacity) *out_of_capacity = false;
+
+ // For testing.
+ if (!forced_crash_directory_.empty()) {
+ *crash_directory = forced_crash_directory_;
+ return true;
+ }
+
+ mode_t directory_mode;
+ uid_t directory_owner;
+ gid_t directory_group;
+ *crash_directory =
+ GetCrashDirectoryInfo(&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;
+ LOG(ERROR) << "Directory " << crash_directory->value()
+ << " is out of capacity.";
+ 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) {
+ LOG(WARNING) << "Unable to open crash directory "
+ << crash_directory.value();
+ 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));
+}
diff --git a/crash_reporter/crash_collector.h b/crash_reporter/crash_collector.h
new file mode 100644
index 0000000..cfd76fd
--- /dev/null
+++ b/crash_reporter/crash_collector.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef 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 <gtest/gtest_prod.h> // for FRIEND_TEST
+
+// 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, 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;
+
+ // 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;
+ }
+
+ base::FilePath GetCrashDirectoryInfo(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();
+
+ 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_;
+
+ private:
+ 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..32cbe9f
--- /dev/null
+++ b/crash_reporter/crash_collector_test.cc
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2012 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 "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_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;
+}
+
+} // 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, 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..cfbb97b
--- /dev/null
+++ b/crash_reporter/crash_collector_test.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
+#define CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
+
+#include "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..23bd342
--- /dev/null
+++ b/crash_reporter/crash_reporter.cc
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2012 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> // 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 "kernel_collector.h"
+#include "kernel_warning_collector.h"
+#include "udev_collector.h"
+#include "unclean_shutdown_collector.h"
+#include "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(
+ "/system/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 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 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(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);
+
+ 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);
+ }
+
+ 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..7db308c
--- /dev/null
+++ b/crash_reporter/crash_reporter_logs.conf
@@ -0,0 +1,122 @@
+# Copyright (C) 2012 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.
+
+# 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..c9ca02d
--- /dev/null
+++ b/crash_reporter/crash_reporter_logs_test.cc
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#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..fa2f8fc
--- /dev/null
+++ b/crash_reporter/crash_sender
@@ -0,0 +1,723 @@
+#!/bin/sh
+
+# Copyright (C) 2010 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.
+
+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.rc b/crash_reporter/init.crash_reporter.rc
new file mode 100644
index 0000000..6882b77
--- /dev/null
+++ b/crash_reporter/init.crash_reporter.rc
@@ -0,0 +1,18 @@
+on property:crash_reporter.coredump.enabled=1
+ write /proc/sys/kernel/core_pattern \
+ "|/system/bin/crash_reporter --user=%P:%s:%u:%e"
+
+on property:crash_reporter.coredump.enabled=0
+ write /proc/sys/kernel/core_pattern "core"
+
+on boot
+ # Allow catching multiple unrelated concurrent crashes, but use a finite
+ # number to prevent infinitely recursing on crash handling.
+ write /proc/sys/kernel/core_pipe_limit 4
+
+ # Create crash directories.
+ mkdir /data/misc/crash_reporter 0700 root root
+
+service crash_reporter /system/bin/crash_reporter --init
+ class late_start
+ oneshot
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..12b00b9
--- /dev/null
+++ b/crash_reporter/kernel_collector.cc
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2012 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 "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..206ee26
--- /dev/null
+++ b/crash_reporter/kernel_collector.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef 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_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..e690b77
--- /dev/null
+++ b/crash_reporter/kernel_collector_test.cc
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2012 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 "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..f689e7d
--- /dev/null
+++ b/crash_reporter/kernel_collector_test.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
+#define CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
+
+#include "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..82512c2
--- /dev/null
+++ b/crash_reporter/kernel_log_collector.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+# Copyright (C) 2013 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.
+
+# 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..e28e8fd
--- /dev/null
+++ b/crash_reporter/kernel_warning_collector.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2012 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 "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..5ccb780
--- /dev/null
+++ b/crash_reporter/kernel_warning_collector.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef 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_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..a39441d
--- /dev/null
+++ b/crash_reporter/list_proxies.cc
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 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 <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/base/test_utils.h b/crash_reporter/testrunner.cc
similarity index 73%
rename from base/test_utils.h
rename to crash_reporter/testrunner.cc
index 132d3a7..a8c717e 100644
--- a/base/test_utils.h
+++ b/crash_reporter/testrunner.cc
@@ -14,19 +14,10 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
+#include <chromeos/test_helpers.h>
+#include <gtest/gtest.h>
-class TemporaryFile {
- public:
- TemporaryFile();
- ~TemporaryFile();
-
- int fd;
- char filename[1024];
-
- private:
- void init(const char* tmp_dir);
-};
-
-#endif // TEST_UTILS_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..576fdbd
--- /dev/null
+++ b/crash_reporter/udev_collector.cc
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2012 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 "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..e267b75
--- /dev/null
+++ b/crash_reporter/udev_collector.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef 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_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..a6643fb
--- /dev/null
+++ b/crash_reporter/udev_collector_test.cc
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2012 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 <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 "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..8a092ec
--- /dev/null
+++ b/crash_reporter/unclean_shutdown_collector.cc
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 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 "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..5bc9968
--- /dev/null
+++ b/crash_reporter/unclean_shutdown_collector.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef 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_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..c5c0662
--- /dev/null
+++ b/crash_reporter/unclean_shutdown_collector_test.cc
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010 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 "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..ee24648
--- /dev/null
+++ b/crash_reporter/user_collector.cc
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2012 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 "user_collector.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/cdefs.h> // For __WORDSIZE
+#include <sys/types.h> // For getpwuid_r, getgrnam_r, WEXITSTATUS.
+
+#include <string>
+#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/process.h>
+#include <chromeos/syslog_logging.h>
+#include <cutils/properties.h>
+
+static const char kCollectionErrorSignature[] =
+ "crash_reporter-user-collection";
+static const char kCorePatternProperty[] = "crash_reporter.coredump.enabled";
+static const char kCoreToMinidumpConverterPath[] = "/system/bin/core2md";
+
+static const char kStatePrefix[] = "State:\t";
+
+static const char kCoreTempFolder[] = "/data/misc/crash_reporter/tmp";
+
+// 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),
+ 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";
+
+ property_set(kCorePatternProperty, enabled ? "1" : "0");
+
+ 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("/proc/self/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("%s/%d", kCoreTempFolder, 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);
+}
+
+bool UserCollector::ShouldDump(bool has_owner_consent,
+ bool is_developer,
+ std::string *reason) {
+ reason->clear();
+
+ // 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(),
+ &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..8c38aa2
--- /dev/null
+++ b/crash_reporter/user_collector.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef 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_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);
+
+ 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,
+ std::string *reason);
+
+ bool generate_diagnostics_;
+ 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..4419e7c
--- /dev/null
+++ b/crash_reporter/user_collector_test.cc
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2012 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 "user_collector.h"
+
+#include <elf.h>
+#include <sys/cdefs.h> // For __WORDSIZE
+#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);
+ 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, 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, &reason));
+ EXPECT_EQ("developer build - not testing - always dumping", reason);
+
+ // When running a crash test, behave as normal.
+ EXPECT_FALSE(collector_.ShouldDump(false, false, &reason));
+ EXPECT_EQ("ignoring - no consent", reason);
+}
+
+TEST_F(UserCollectorTest, ShouldDumpUseConsentProductionImage) {
+ std::string result;
+ EXPECT_FALSE(collector_.ShouldDump(false, false, &result));
+ EXPECT_EQ("ignoring - no consent", result);
+
+ EXPECT_TRUE(collector_.ShouldDump(true, false, &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..70ab25c
--- /dev/null
+++ b/crash_reporter/warn_collector.l
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2013 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.
+ *
+ * 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.
+ */
+
+%option noyywrap
+
+%{
+#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;
+}
+
+#pragma GCC diagnostic ignored "-Wwrite-strings"
+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 = strchr(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);
+}
diff --git a/base/test_utils.h b/crash_reporter/warn_collector_test.c
similarity index 66%
copy from base/test_utils.h
copy to crash_reporter/warn_collector_test.c
index 132d3a7..7ebe0a8 100644
--- a/base/test_utils.h
+++ b/crash_reporter/warn_collector_test.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,19 +14,12 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
+/*
+ * Test driver for the warn_collector daemon.
+ */
+#include <stdlib.h>
-class TemporaryFile {
- public:
- TemporaryFile();
- ~TemporaryFile();
-
- int fd;
- char filename[1024];
-
- private:
- void init(const char* tmp_dir);
-};
-
-#endif // TEST_UTILS_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..a5af16c
--- /dev/null
+++ b/crash_reporter/warn_collector_test.sh
@@ -0,0 +1,90 @@
+#! /bin/bash
+
+# Copyright (C) 2013 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.
+
+# 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..b6096ed
--- /dev/null
+++ b/crash_reporter/warn_collector_test_reporter.sh
@@ -0,0 +1,27 @@
+#! /bin/sh
+
+# Copyright (C) 2013 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.
+
+# 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/debuggerd/Android.mk b/debuggerd/Android.mk
index 1a5f05e..f7a5f82 100644
--- a/debuggerd/Android.mk
+++ b/debuggerd/Android.mk
@@ -27,6 +27,9 @@
LOCAL_CPPFLAGS := $(common_cppflags)
+LOCAL_INIT_RC_32 := debuggerd.rc
+LOCAL_INIT_RC_64 := debuggerd64.rc
+
ifeq ($(TARGET_IS_64_BIT),true)
LOCAL_CPPFLAGS += -DTARGET_IS_64_BIT
endif
@@ -75,46 +78,55 @@
include $(BUILD_EXECUTABLE)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
+debuggerd_test_src_files := \
utility.cpp \
test/dump_memory_test.cpp \
+ test/elf_fake.cpp \
test/log_fake.cpp \
+ test/property_fake.cpp \
+ test/ptrace_fake.cpp \
+ test/tombstone_test.cpp \
+ test/selinux_fake.cpp \
-LOCAL_MODULE := debuggerd_test
-
-LOCAL_SHARED_LIBRARIES := \
+debuggerd_shared_libraries := \
libbacktrace \
libbase \
+ libcutils \
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/test
-LOCAL_CPPFLAGS := $(common_cppflags)
+debuggerd_c_includes := \
+ $(LOCAL_PATH)/test \
+
+debuggerd_cpp_flags := \
+ $(common_cppflags) \
+ -Wno-missing-field-initializers \
+
+# Only build the host tests on linux.
+ifeq ($(HOST_OS),linux)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := debuggerd_test
+LOCAL_SRC_FILES := $(debuggerd_test_src_files)
+LOCAL_SHARED_LIBRARIES := $(debuggerd_shared_libraries)
+LOCAL_C_INCLUDES := $(debuggerd_c_includes)
+LOCAL_CPPFLAGS := $(debuggerd_cpp_flags)
LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
LOCAL_MULTILIB := both
-
include $(BUILD_HOST_NATIVE_TEST)
+endif
+
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- utility.cpp \
- test/dump_memory_test.cpp \
- test/log_fake.cpp \
-
LOCAL_MODULE := debuggerd_test
-
-LOCAL_SHARED_LIBRARIES := \
- libbacktrace \
- libbase \
-
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/test
-LOCAL_CPPFLAGS := $(common_cppflags)
+LOCAL_SRC_FILES := $(debuggerd_test_src_files)
+LOCAL_SHARED_LIBRARIES := $(debuggerd_shared_libraries)
+LOCAL_C_INCLUDES := $(debuggerd_c_includes)
+LOCAL_CPPFLAGS := $(debuggerd_cpp_flags)
LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
LOCAL_MULTILIB := both
-
include $(BUILD_NATIVE_TEST)
diff --git a/debuggerd/arm/machine.cpp b/debuggerd/arm/machine.cpp
index b7d6997..78c2306 100644
--- a/debuggerd/arm/machine.cpp
+++ b/debuggerd/arm/machine.cpp
@@ -15,12 +15,15 @@
* limitations under the License.
*/
+#define LOG_TAG "DEBUG"
+
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <sys/ptrace.h>
#include <backtrace/Backtrace.h>
+#include <log/log.h>
#include "machine.h"
#include "utility.h"
@@ -28,7 +31,7 @@
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
pt_regs regs;
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, ®s)) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
@@ -48,7 +51,7 @@
void dump_registers(log_t* log, pid_t tid) {
pt_regs r;
if (ptrace(PTRACE_GETREGS, tid, 0, &r)) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
@@ -68,7 +71,7 @@
user_vfp vfp_regs;
if (ptrace(PTRACE_GETVFPREGS, tid, 0, &vfp_regs)) {
- _LOG(log, logtype::ERROR, "cannot get FP registers: %s\n", strerror(errno));
+ ALOGE("cannot get FP registers: %s\n", strerror(errno));
return;
}
diff --git a/debuggerd/arm64/machine.cpp b/debuggerd/arm64/machine.cpp
index 2e097da..e7bf79a 100644
--- a/debuggerd/arm64/machine.cpp
+++ b/debuggerd/arm64/machine.cpp
@@ -15,6 +15,8 @@
* limitations under the License.
*/
+#define LOG_TAG "DEBUG"
+
#include <elf.h>
#include <errno.h>
#include <stdint.h>
@@ -23,6 +25,7 @@
#include <sys/uio.h>
#include <backtrace/Backtrace.h>
+#include <log/log.h>
#include "machine.h"
#include "utility.h"
@@ -34,8 +37,7 @@
io.iov_len = sizeof(regs);
if (ptrace(PTRACE_GETREGSET, backtrace->Tid(), reinterpret_cast<void*>(NT_PRSTATUS), &io) == -1) {
- _LOG(log, logtype::ERROR, "%s: ptrace failed to get registers: %s",
- __func__, strerror(errno));
+ ALOGE("ptrace failed to get registers: %s", strerror(errno));
return;
}
@@ -57,7 +59,7 @@
io.iov_len = sizeof(r);
if (ptrace(PTRACE_GETREGSET, tid, (void*) NT_PRSTATUS, (void*) &io) == -1) {
- _LOG(log, logtype::ERROR, "ptrace error: %s\n", strerror(errno));
+ ALOGE("ptrace error: %s\n", strerror(errno));
return;
}
@@ -81,7 +83,7 @@
io.iov_len = sizeof(f);
if (ptrace(PTRACE_GETREGSET, tid, (void*) NT_PRFPREG, (void*) &io) == -1) {
- _LOG(log, logtype::ERROR, "ptrace error: %s\n", strerror(errno));
+ ALOGE("ptrace error: %s\n", strerror(errno));
return;
}
diff --git a/debuggerd/backtrace.cpp b/debuggerd/backtrace.cpp
index b8084c5..b46f8f4 100644
--- a/debuggerd/backtrace.cpp
+++ b/debuggerd/backtrace.cpp
@@ -105,7 +105,7 @@
}
if (!attached && ptrace(PTRACE_DETACH, tid, 0, 0) != 0) {
- _LOG(log, logtype::ERROR, "ptrace detach from %d failed: %s\n", tid, strerror(errno));
+ ALOGE("ptrace detach from %d failed: %s\n", tid, strerror(errno));
*detach_failed = true;
}
}
diff --git a/debuggerd/crasher.c b/debuggerd/crasher.c
index af86fe9..b5064b4 100644
--- a/debuggerd/crasher.c
+++ b/debuggerd/crasher.c
@@ -157,12 +157,6 @@
} else if (!strcmp(arg, "SIGFPE")) {
raise(SIGFPE);
return EXIT_SUCCESS;
- } else if (!strcmp(arg, "SIGPIPE")) {
- int pipe_fds[2];
- pipe(pipe_fds);
- close(pipe_fds[0]);
- write(pipe_fds[1], "oops", 4);
- return EXIT_SUCCESS;
} else if (!strcmp(arg, "SIGTRAP")) {
raise(SIGTRAP);
return EXIT_SUCCESS;
@@ -189,7 +183,6 @@
fprintf(stderr, " LOG_ALWAYS_FATAL call LOG_ALWAYS_FATAL\n");
fprintf(stderr, " LOG_ALWAYS_FATAL_IF call LOG_ALWAYS_FATAL\n");
fprintf(stderr, " SIGFPE cause a SIGFPE\n");
- fprintf(stderr, " SIGPIPE cause a SIGPIPE\n");
fprintf(stderr, " SIGSEGV cause a SIGSEGV at address 0x0 (synonym: crash)\n");
fprintf(stderr, " SIGSEGV-non-null cause a SIGSEGV at a non-zero address\n");
fprintf(stderr, " SIGSEGV-unmapped mmap/munmap a region of memory and then attempt to access it\n");
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index b84a4e5..599995c 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -63,32 +63,18 @@
int32_t original_si_code;
};
-static void wait_for_user_action(const debugger_request_t &request) {
- // Find out the name of the process that crashed.
- char path[64];
- snprintf(path, sizeof(path), "/proc/%d/exe", request.pid);
-
- char exe[PATH_MAX];
- int count;
- if ((count = readlink(path, exe, sizeof(exe) - 1)) == -1) {
- ALOGE("readlink('%s') failed: %s", path, strerror(errno));
- strlcpy(exe, "unknown", sizeof(exe));
- } else {
- exe[count] = '\0';
- }
-
+static void wait_for_user_action(const debugger_request_t& request) {
// Explain how to attach the debugger.
- ALOGI("********************************************************\n"
+ ALOGI("***********************************************************\n"
"* Process %d has been suspended while crashing.\n"
- "* To attach gdbserver for a gdb connection on port 5039\n"
- "* and start gdbclient:\n"
+ "* To attach gdbserver and start gdb, run this on the host:\n"
"*\n"
- "* gdbclient %s :5039 %d\n"
+ "* gdbclient %d\n"
"*\n"
"* Wait for gdb to start, then press the VOLUME DOWN key\n"
"* to let the process continue crashing.\n"
- "********************************************************",
- request.pid, exe, request.tid);
+ "***********************************************************",
+ request.pid, request.tid);
// Wait for VOLUME DOWN.
if (init_getevent() == 0) {
@@ -134,8 +120,6 @@
return fields == 7 ? 0 : -1;
}
-static int selinux_enabled;
-
/*
* Corresponds with debugger_action_t enum type in
* include/cutils/debugger.h.
@@ -153,9 +137,6 @@
const char *perm;
bool allowed = false;
- if (selinux_enabled <= 0)
- return true;
-
if (action <= 0 || action >= (sizeof(debuggerd_perms)/sizeof(debuggerd_perms[0]))) {
ALOGE("SELinux: No permission defined for debugger action %d", action);
return false;
@@ -255,10 +236,7 @@
static bool should_attach_gdb(debugger_request_t* request) {
if (request->action == DEBUGGER_ACTION_CRASH) {
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.db.uid", value, "-1");
- int debug_uid = atoi(value);
- return debug_uid >= 0 && request->uid <= (uid_t)debug_uid;
+ return property_get_bool("debug.debuggerd.wait_for_gdb", false);
}
return false;
}
@@ -430,7 +408,6 @@
case SIGBUS:
case SIGFPE:
case SIGILL:
- case SIGPIPE:
case SIGSEGV:
#ifdef SIGSTKFLT
case SIGSTKFLT:
@@ -589,7 +566,6 @@
int main(int argc, char** argv) {
union selinux_callback cb;
if (argc == 1) {
- selinux_enabled = is_selinux_enabled();
cb.func_log = selinux_log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
return do_server();
diff --git a/debuggerd/debuggerd.rc b/debuggerd/debuggerd.rc
new file mode 100644
index 0000000..4338ae9
--- /dev/null
+++ b/debuggerd/debuggerd.rc
@@ -0,0 +1,2 @@
+service debuggerd /system/bin/debuggerd
+ class main
diff --git a/debuggerd/debuggerd64.rc b/debuggerd/debuggerd64.rc
new file mode 100644
index 0000000..341a329
--- /dev/null
+++ b/debuggerd/debuggerd64.rc
@@ -0,0 +1,2 @@
+service debuggerd64 /system/bin/debuggerd64
+ class main
diff --git a/debuggerd/mips/machine.cpp b/debuggerd/mips/machine.cpp
index f7b8a86..cbf272a 100644
--- a/debuggerd/mips/machine.cpp
+++ b/debuggerd/mips/machine.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "DEBUG"
+
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
@@ -21,6 +23,7 @@
#include <sys/ptrace.h>
#include <backtrace/Backtrace.h>
+#include <log/log.h>
#include "machine.h"
#include "utility.h"
@@ -32,7 +35,7 @@
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
pt_regs r;
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r)) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
@@ -61,7 +64,7 @@
void dump_registers(log_t* log, pid_t tid) {
pt_regs r;
if(ptrace(PTRACE_GETREGS, tid, 0, &r)) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
diff --git a/debuggerd/mips64/machine.cpp b/debuggerd/mips64/machine.cpp
index 293dcf6..0a8d532 100644
--- a/debuggerd/mips64/machine.cpp
+++ b/debuggerd/mips64/machine.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "DEBUG"
+
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
@@ -21,6 +23,7 @@
#include <sys/ptrace.h>
#include <backtrace/Backtrace.h>
+#include <log/log.h>
#include "machine.h"
#include "utility.h"
@@ -32,7 +35,7 @@
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
pt_regs r;
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r)) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
@@ -61,7 +64,7 @@
void dump_registers(log_t* log, pid_t tid) {
pt_regs r;
if(ptrace(PTRACE_GETREGS, tid, 0, &r)) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
diff --git a/debuggerd/test/BacktraceMock.h b/debuggerd/test/BacktraceMock.h
index 05ad12b..f75534e 100644
--- a/debuggerd/test/BacktraceMock.h
+++ b/debuggerd/test/BacktraceMock.h
@@ -32,6 +32,10 @@
public:
BacktraceMapMock() : BacktraceMap(0) {}
virtual ~BacktraceMapMock() {}
+
+ void AddMap(backtrace_map_t& map) {
+ maps_.push_back(map);
+ }
};
@@ -56,12 +60,14 @@
}
size_t bytes_available = buffer_.size() - offset;
- if (bytes_partial_read_ > 0) {
+ if (do_partial_read_) {
// Do a partial read.
if (bytes > bytes_partial_read_) {
bytes = bytes_partial_read_;
}
bytes_partial_read_ -= bytes;
+ // Only support a single partial read.
+ do_partial_read_ = false;
} else if (bytes > bytes_available) {
bytes = bytes_available;
}
@@ -78,6 +84,7 @@
buffer_.resize(bytes);
memcpy(buffer_.data(), buffer, bytes);
bytes_partial_read_ = 0;
+ do_partial_read_ = false;
last_read_addr_ = 0;
}
@@ -86,12 +93,14 @@
abort();
}
bytes_partial_read_ = bytes;
+ do_partial_read_ = true;
}
private:
std::vector<uint8_t> buffer_;
size_t bytes_partial_read_ = 0;
uintptr_t last_read_addr_ = 0;
+ bool do_partial_read_ = false;
};
#endif // _DEBUGGERD_TEST_BACKTRACE_MOCK_H
diff --git a/debuggerd/test/dump_memory_test.cpp b/debuggerd/test/dump_memory_test.cpp
index fcb0108..75e7028 100644
--- a/debuggerd/test/dump_memory_test.cpp
+++ b/debuggerd/test/dump_memory_test.cpp
@@ -288,9 +288,9 @@
ASSERT_STREQ(g_expected_partial_dump, tombstone_contents.c_str());
#if defined(__LP64__)
- ASSERT_STREQ("DEBUG Bytes read 102, is not a multiple of 8\n", getFakeLogPrint().c_str());
+ ASSERT_STREQ("6 DEBUG Bytes read 102, is not a multiple of 8\n", getFakeLogPrint().c_str());
#else
- ASSERT_STREQ("DEBUG Bytes read 102, is not a multiple of 4\n", getFakeLogPrint().c_str());
+ ASSERT_STREQ("6 DEBUG Bytes read 102, is not a multiple of 4\n", getFakeLogPrint().c_str());
#endif
// Verify that the log buf is empty, and no error messages.
@@ -313,12 +313,12 @@
ASSERT_STREQ(g_expected_partial_dump, tombstone_contents.c_str());
#if defined(__LP64__)
- ASSERT_STREQ("DEBUG Bytes read 45, is not a multiple of 8\n"
- "DEBUG Bytes after second read 106, is not a multiple of 8\n",
+ ASSERT_STREQ("6 DEBUG Bytes read 45, is not a multiple of 8\n"
+ "6 DEBUG Bytes after second read 106, is not a multiple of 8\n",
getFakeLogPrint().c_str());
#else
- ASSERT_STREQ("DEBUG Bytes read 45, is not a multiple of 4\n"
- "DEBUG Bytes after second read 106, is not a multiple of 4\n",
+ ASSERT_STREQ("6 DEBUG Bytes read 45, is not a multiple of 4\n"
+ "6 DEBUG Bytes after second read 106, is not a multiple of 4\n",
getFakeLogPrint().c_str());
#endif
@@ -502,3 +502,239 @@
ASSERT_STREQ("", getFakeLogBuf().c_str());
ASSERT_STREQ("", getFakeLogPrint().c_str());
}
+
+TEST_F(DumpMemoryTest, first_read_empty) {
+ uint8_t buffer[256];
+ for (size_t i = 0; i < sizeof(buffer); i++) {
+ buffer[i] = i;
+ }
+ backtrace_mock_->SetReadData(buffer, sizeof(buffer));
+ backtrace_mock_->SetPartialReadAmount(0);
+
+ size_t page_size = sysconf(_SC_PAGE_SIZE);
+ uintptr_t addr = 0x10000020 + page_size - 120;
+ dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory near r4:\n"
+#if defined(__LP64__)
+" 0000000010000f88 ---------------- ---------------- ................\n"
+" 0000000010000f98 ---------------- ---------------- ................\n"
+" 0000000010000fa8 ---------------- ---------------- ................\n"
+" 0000000010000fb8 ---------------- ---------------- ................\n"
+" 0000000010000fc8 ---------------- ---------------- ................\n"
+" 0000000010000fd8 ---------------- ---------------- ................\n"
+" 0000000010000fe8 ---------------- ---------------- ................\n"
+" 0000000010000ff8 ---------------- 7f7e7d7c7b7a7978 ........xyz{|}~.\n"
+" 0000000010001008 8786858483828180 8f8e8d8c8b8a8988 ................\n"
+" 0000000010001018 9796959493929190 9f9e9d9c9b9a9998 ................\n"
+" 0000000010001028 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8 ................\n"
+" 0000000010001038 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8 ................\n"
+" 0000000010001048 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8 ................\n"
+" 0000000010001058 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8 ................\n"
+" 0000000010001068 e7e6e5e4e3e2e1e0 efeeedecebeae9e8 ................\n"
+" 0000000010001078 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8 ................\n";
+#else
+" 10000f88 -------- -------- -------- -------- ................\n"
+" 10000f98 -------- -------- -------- -------- ................\n"
+" 10000fa8 -------- -------- -------- -------- ................\n"
+" 10000fb8 -------- -------- -------- -------- ................\n"
+" 10000fc8 -------- -------- -------- -------- ................\n"
+" 10000fd8 -------- -------- -------- -------- ................\n"
+" 10000fe8 -------- -------- -------- -------- ................\n"
+" 10000ff8 -------- -------- 7b7a7978 7f7e7d7c ........xyz{|}~.\n"
+" 10001008 83828180 87868584 8b8a8988 8f8e8d8c ................\n"
+" 10001018 93929190 97969594 9b9a9998 9f9e9d9c ................\n"
+" 10001028 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac ................\n"
+" 10001038 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc ................\n"
+" 10001048 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc ................\n"
+" 10001058 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc ................\n"
+" 10001068 e3e2e1e0 e7e6e5e4 ebeae9e8 efeeedec ................\n"
+" 10001078 f3f2f1f0 f7f6f5f4 fbfaf9f8 fffefdfc ................\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(DumpMemoryTest, first_read_empty_second_read_stops) {
+ uint8_t buffer[224];
+ for (size_t i = 0; i < sizeof(buffer); i++) {
+ buffer[i] = i;
+ }
+ backtrace_mock_->SetReadData(buffer, sizeof(buffer));
+ backtrace_mock_->SetPartialReadAmount(0);
+
+ size_t page_size = sysconf(_SC_PAGE_SIZE);
+ uintptr_t addr = 0x10000020 + page_size - 192;
+ dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory near r4:\n"
+#if defined(__LP64__)
+" 0000000010000f40 ---------------- ---------------- ................\n"
+" 0000000010000f50 ---------------- ---------------- ................\n"
+" 0000000010000f60 ---------------- ---------------- ................\n"
+" 0000000010000f70 ---------------- ---------------- ................\n"
+" 0000000010000f80 ---------------- ---------------- ................\n"
+" 0000000010000f90 ---------------- ---------------- ................\n"
+" 0000000010000fa0 ---------------- ---------------- ................\n"
+" 0000000010000fb0 ---------------- ---------------- ................\n"
+" 0000000010000fc0 ---------------- ---------------- ................\n"
+" 0000000010000fd0 ---------------- ---------------- ................\n"
+" 0000000010000fe0 ---------------- ---------------- ................\n"
+" 0000000010000ff0 ---------------- ---------------- ................\n"
+" 0000000010001000 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8 ................\n"
+" 0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8 ................\n"
+" 0000000010001020 ---------------- ---------------- ................\n"
+" 0000000010001030 ---------------- ---------------- ................\n";
+#else
+" 10000f40 -------- -------- -------- -------- ................\n"
+" 10000f50 -------- -------- -------- -------- ................\n"
+" 10000f60 -------- -------- -------- -------- ................\n"
+" 10000f70 -------- -------- -------- -------- ................\n"
+" 10000f80 -------- -------- -------- -------- ................\n"
+" 10000f90 -------- -------- -------- -------- ................\n"
+" 10000fa0 -------- -------- -------- -------- ................\n"
+" 10000fb0 -------- -------- -------- -------- ................\n"
+" 10000fc0 -------- -------- -------- -------- ................\n"
+" 10000fd0 -------- -------- -------- -------- ................\n"
+" 10000fe0 -------- -------- -------- -------- ................\n"
+" 10000ff0 -------- -------- -------- -------- ................\n"
+" 10001000 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc ................\n"
+" 10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc ................\n"
+" 10001020 -------- -------- -------- -------- ................\n"
+" 10001030 -------- -------- -------- -------- ................\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(DumpMemoryTest, first_read_empty_next_page_out_of_range) {
+ uint8_t buffer[256];
+ for (size_t i = 0; i < sizeof(buffer); i++) {
+ buffer[i] = i;
+ }
+ backtrace_mock_->SetReadData(buffer, sizeof(buffer));
+ backtrace_mock_->SetPartialReadAmount(0);
+
+ uintptr_t addr = 0x10000020;
+ dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory near r4:\n"
+#if defined(__LP64__)
+" 0000000010000000 ---------------- ---------------- ................\n"
+" 0000000010000010 ---------------- ---------------- ................\n"
+" 0000000010000020 ---------------- ---------------- ................\n"
+" 0000000010000030 ---------------- ---------------- ................\n"
+" 0000000010000040 ---------------- ---------------- ................\n"
+" 0000000010000050 ---------------- ---------------- ................\n"
+" 0000000010000060 ---------------- ---------------- ................\n"
+" 0000000010000070 ---------------- ---------------- ................\n"
+" 0000000010000080 ---------------- ---------------- ................\n"
+" 0000000010000090 ---------------- ---------------- ................\n"
+" 00000000100000a0 ---------------- ---------------- ................\n"
+" 00000000100000b0 ---------------- ---------------- ................\n"
+" 00000000100000c0 ---------------- ---------------- ................\n"
+" 00000000100000d0 ---------------- ---------------- ................\n"
+" 00000000100000e0 ---------------- ---------------- ................\n"
+" 00000000100000f0 ---------------- ---------------- ................\n";
+#else
+" 10000000 -------- -------- -------- -------- ................\n"
+" 10000010 -------- -------- -------- -------- ................\n"
+" 10000020 -------- -------- -------- -------- ................\n"
+" 10000030 -------- -------- -------- -------- ................\n"
+" 10000040 -------- -------- -------- -------- ................\n"
+" 10000050 -------- -------- -------- -------- ................\n"
+" 10000060 -------- -------- -------- -------- ................\n"
+" 10000070 -------- -------- -------- -------- ................\n"
+" 10000080 -------- -------- -------- -------- ................\n"
+" 10000090 -------- -------- -------- -------- ................\n"
+" 100000a0 -------- -------- -------- -------- ................\n"
+" 100000b0 -------- -------- -------- -------- ................\n"
+" 100000c0 -------- -------- -------- -------- ................\n"
+" 100000d0 -------- -------- -------- -------- ................\n"
+" 100000e0 -------- -------- -------- -------- ................\n"
+" 100000f0 -------- -------- -------- -------- ................\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(DumpMemoryTest, first_read_empty_next_page_out_of_range_fence_post) {
+ uint8_t buffer[256];
+ for (size_t i = 0; i < sizeof(buffer); i++) {
+ buffer[i] = i;
+ }
+ backtrace_mock_->SetReadData(buffer, sizeof(buffer));
+ backtrace_mock_->SetPartialReadAmount(0);
+
+ size_t page_size = sysconf(_SC_PAGE_SIZE);
+ uintptr_t addr = 0x10000020 + page_size - 256;
+
+ dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory near r4:\n"
+#if defined(__LP64__)
+" 0000000010000f00 ---------------- ---------------- ................\n"
+" 0000000010000f10 ---------------- ---------------- ................\n"
+" 0000000010000f20 ---------------- ---------------- ................\n"
+" 0000000010000f30 ---------------- ---------------- ................\n"
+" 0000000010000f40 ---------------- ---------------- ................\n"
+" 0000000010000f50 ---------------- ---------------- ................\n"
+" 0000000010000f60 ---------------- ---------------- ................\n"
+" 0000000010000f70 ---------------- ---------------- ................\n"
+" 0000000010000f80 ---------------- ---------------- ................\n"
+" 0000000010000f90 ---------------- ---------------- ................\n"
+" 0000000010000fa0 ---------------- ---------------- ................\n"
+" 0000000010000fb0 ---------------- ---------------- ................\n"
+" 0000000010000fc0 ---------------- ---------------- ................\n"
+" 0000000010000fd0 ---------------- ---------------- ................\n"
+" 0000000010000fe0 ---------------- ---------------- ................\n"
+" 0000000010000ff0 ---------------- ---------------- ................\n";
+#else
+" 10000f00 -------- -------- -------- -------- ................\n"
+" 10000f10 -------- -------- -------- -------- ................\n"
+" 10000f20 -------- -------- -------- -------- ................\n"
+" 10000f30 -------- -------- -------- -------- ................\n"
+" 10000f40 -------- -------- -------- -------- ................\n"
+" 10000f50 -------- -------- -------- -------- ................\n"
+" 10000f60 -------- -------- -------- -------- ................\n"
+" 10000f70 -------- -------- -------- -------- ................\n"
+" 10000f80 -------- -------- -------- -------- ................\n"
+" 10000f90 -------- -------- -------- -------- ................\n"
+" 10000fa0 -------- -------- -------- -------- ................\n"
+" 10000fb0 -------- -------- -------- -------- ................\n"
+" 10000fc0 -------- -------- -------- -------- ................\n"
+" 10000fd0 -------- -------- -------- -------- ................\n"
+" 10000fe0 -------- -------- -------- -------- ................\n"
+" 10000ff0 -------- -------- -------- -------- ................\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
diff --git a/base/test_utils.h b/debuggerd/test/elf_fake.cpp
similarity index 64%
copy from base/test_utils.h
copy to debuggerd/test/elf_fake.cpp
index 132d3a7..bb52b59 100644
--- a/base/test_utils.h
+++ b/debuggerd/test/elf_fake.cpp
@@ -14,19 +14,22 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
+#include <stdint.h>
-class TemporaryFile {
- public:
- TemporaryFile();
- ~TemporaryFile();
+#include <string>
- int fd;
- char filename[1024];
+class Backtrace;
- private:
- void init(const char* tmp_dir);
-};
+std::string g_build_id;
-#endif // TEST_UTILS_H
+void elf_set_fake_build_id(const std::string& build_id) {
+ g_build_id = build_id;
+}
+
+bool elf_get_build_id(Backtrace*, uintptr_t, std::string* build_id) {
+ if (g_build_id != "") {
+ *build_id = g_build_id;
+ return true;
+ }
+ return false;
+}
diff --git a/base/test_utils.h b/debuggerd/test/elf_fake.h
similarity index 73%
copy from base/test_utils.h
copy to debuggerd/test/elf_fake.h
index 132d3a7..08a8454 100644
--- a/base/test_utils.h
+++ b/debuggerd/test/elf_fake.h
@@ -14,19 +14,11 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
+#ifndef _DEBUGGERD_TEST_ELF_FAKE_H
+#define _DEBUGGERD_TEST_ELF_FAKE_H
-class TemporaryFile {
- public:
- TemporaryFile();
- ~TemporaryFile();
+#include <string>
- int fd;
- char filename[1024];
+void elf_set_fake_build_id(const std::string&);
- private:
- void init(const char* tmp_dir);
-};
-
-#endif // TEST_UTILS_H
+#endif // _DEBUGGERD_TEST_ELF_FAKE_H
diff --git a/debuggerd/test/host_signal_fixup.h b/debuggerd/test/host_signal_fixup.h
new file mode 100644
index 0000000..c7796ef
--- /dev/null
+++ b/debuggerd/test/host_signal_fixup.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H
+#define _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H
+
+#include <signal.h>
+
+#if !defined(__BIONIC__)
+
+// In order to compile parts of debuggerd for the host, we need to
+// define these values.
+
+#if !defined(NSIGILL)
+#define NSIGILL ILL_BADSTK
+#endif
+
+#if !defined(BUS_MCEERR_AR)
+#define BUS_MCEERR_AR 4
+#endif
+#if !defined(BUS_MCEERR_AO)
+#define BUS_MCEERR_AO 5
+#endif
+#if !defined(NSIGBUS)
+#define NSIGBUS BUS_MCEERR_AO
+#endif
+
+#if !defined(NSIGFPE)
+#define NSIGFPE FPE_FLTSUB
+#endif
+
+#if !defined(NSIGSEGV)
+#define NSIGSEGV SEGV_ACCERR
+#endif
+
+#if !defined(TRAP_BRANCH)
+#define TRAP_BRANCH 3
+#endif
+#if !defined(TRAP_HWBKPT)
+#define TRAP_HWBKPT 4
+#endif
+#if !defined(NSIGTRAP)
+#define NSIGTRAP TRAP_HWBKPT
+#endif
+
+#if !defined(SI_DETHREAD)
+#define SI_DETHREAD -7
+#endif
+
+#endif
+
+#endif // _DEBUGGERD_TEST_HOST_SIGNAL_FIXUP_H
diff --git a/debuggerd/test/log_fake.cpp b/debuggerd/test/log_fake.cpp
index 1161afe..d584a5e 100644
--- a/debuggerd/test/log_fake.cpp
+++ b/debuggerd/test/log_fake.cpp
@@ -14,11 +14,19 @@
* limitations under the License.
*/
+#include <errno.h>
#include <stdarg.h>
#include <string>
#include <base/stringprintf.h>
+#include <log/log.h>
+#include <log/logger.h>
+
+// Forward declarations.
+class Backtrace;
+struct EventTagMap;
+struct AndroidLogEntry;
std::string g_fake_log_buf;
@@ -29,18 +37,24 @@
g_fake_log_print = "";
}
-extern "C" int __android_log_buf_write(int, int, const char* tag, const char* msg) {
+std::string getFakeLogBuf() {
+ return g_fake_log_buf;
+}
+
+std::string getFakeLogPrint() {
+ return g_fake_log_print;
+}
+
+extern "C" int __android_log_buf_write(int bufId, int prio, const char* tag, const char* msg) {
+ g_fake_log_buf += std::to_string(bufId) + ' ' + std::to_string(prio) + ' ';
g_fake_log_buf += tag;
g_fake_log_buf += ' ';
g_fake_log_buf += msg;
return 1;
}
-std::string getFakeLogBuf() {
- return g_fake_log_buf;
-}
-
-extern "C" int __android_log_print(int, const char* tag, const char* fmt, ...) {
+extern "C" int __android_log_print(int prio, const char* tag, const char* fmt, ...) {
+ g_fake_log_print += std::to_string(prio) + ' ';
g_fake_log_print += tag;
g_fake_log_print += ' ';
@@ -54,6 +68,28 @@
return 1;
}
-std::string getFakeLogPrint() {
- return g_fake_log_print;
+extern "C" log_id_t android_name_to_log_id(const char*) {
+ return LOG_ID_SYSTEM;
+}
+
+extern "C" struct logger_list* android_logger_list_open(log_id_t, int, unsigned int, pid_t) {
+ errno = EACCES;
+ return nullptr;
+}
+
+extern "C" int android_logger_list_read(struct logger_list*, struct log_msg*) {
+ return 0;
+}
+
+extern "C" EventTagMap* android_openEventTagMap(const char*) {
+ return nullptr;
+}
+
+extern "C" int android_log_processBinaryLogBuffer(
+ struct logger_entry*,
+ AndroidLogEntry*, const EventTagMap*, char*, int) {
+ return 0;
+}
+
+extern "C" void android_logger_list_free(struct logger_list*) {
}
diff --git a/debuggerd/test/property_fake.cpp b/debuggerd/test/property_fake.cpp
new file mode 100644
index 0000000..02069f1
--- /dev/null
+++ b/debuggerd/test/property_fake.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+
+#include <string>
+#include <unordered_map>
+
+#include <sys/system_properties.h>
+
+std::unordered_map<std::string, std::string> g_properties;
+
+extern "C" int property_set(const char* name, const char* value) {
+ if (g_properties.count(name) != 0) {
+ g_properties.erase(name);
+ }
+ g_properties[name] = value;
+ return 0;
+}
+
+extern "C" int property_get(const char* key, char* value, const char* default_value) {
+ if (g_properties.count(key) == 0) {
+ if (default_value == nullptr) {
+ return 0;
+ }
+ strncpy(value, default_value, PROP_VALUE_MAX-1);
+ } else {
+ strncpy(value, g_properties[key].c_str(), PROP_VALUE_MAX-1);
+ }
+ value[PROP_VALUE_MAX-1] = '\0';
+ return strlen(value);
+}
diff --git a/debuggerd/test/ptrace_fake.cpp b/debuggerd/test/ptrace_fake.cpp
new file mode 100644
index 0000000..f40cbd4
--- /dev/null
+++ b/debuggerd/test/ptrace_fake.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <sys/ptrace.h>
+
+#include <string>
+
+#include "ptrace_fake.h"
+
+siginfo_t g_fake_si = {.si_signo = 0};
+
+void ptrace_set_fake_getsiginfo(const siginfo_t& si) {
+ g_fake_si = si;
+}
+
+#if !defined(__BIONIC__)
+extern "C" long ptrace_fake(enum __ptrace_request request, ...) {
+#else
+extern "C" long ptrace_fake(int request, ...) {
+#endif
+ if (request == PTRACE_GETSIGINFO) {
+ if (g_fake_si.si_signo == 0) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ va_list ap;
+ va_start(ap, request);
+ va_arg(ap, int);
+ va_arg(ap, int);
+ siginfo_t* si = va_arg(ap, siginfo*);
+ va_end(ap);
+ *si = g_fake_si;
+ return 0;
+ }
+ return -1;
+}
diff --git a/base/test_utils.h b/debuggerd/test/ptrace_fake.h
similarity index 73%
copy from base/test_utils.h
copy to debuggerd/test/ptrace_fake.h
index 132d3a7..fdbb663 100644
--- a/base/test_utils.h
+++ b/debuggerd/test/ptrace_fake.h
@@ -14,19 +14,11 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
+#ifndef _DEBUGGERD_TEST_PTRACE_FAKE_H
+#define _DEBUGGERD_TEST_PTRACE_FAKE_H
-class TemporaryFile {
- public:
- TemporaryFile();
- ~TemporaryFile();
+#include <signal.h>
- int fd;
- char filename[1024];
+void ptrace_set_fake_getsiginfo(const siginfo_t&);
- private:
- void init(const char* tmp_dir);
-};
-
-#endif // TEST_UTILS_H
+#endif // _DEBUGGERD_TEST_PTRACE_FAKE_H
diff --git a/base/test_utils.h b/debuggerd/test/selinux_fake.cpp
similarity index 73%
copy from base/test_utils.h
copy to debuggerd/test/selinux_fake.cpp
index 132d3a7..acdd0a9 100644
--- a/base/test_utils.h
+++ b/debuggerd/test/selinux_fake.cpp
@@ -14,19 +14,6 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
-
-class TemporaryFile {
- public:
- TemporaryFile();
- ~TemporaryFile();
-
- int fd;
- char filename[1024];
-
- private:
- void init(const char* tmp_dir);
-};
-
-#endif // TEST_UTILS_H
+extern "C" int selinux_android_restorecon(const char*, unsigned int) {
+ return 0;
+}
diff --git a/fastboot/util_windows.c b/debuggerd/test/sys/system_properties.h
similarity index 77%
copy from fastboot/util_windows.c
copy to debuggerd/test/sys/system_properties.h
index 74a5c27..9d44345 100644
--- a/fastboot/util_windows.c
+++ b/debuggerd/test/sys/system_properties.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -26,24 +26,13 @@
* SUCH DAMAGE.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <limits.h>
+#ifndef _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
+#define _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
-#include <windows.h>
+// This is just enough to get the property code to compile on
+// the host.
-void get_my_path(char exe[PATH_MAX])
-{
- char* r;
+#define PROP_NAME_MAX 32
+#define PROP_VALUE_MAX 92
- GetModuleFileName( NULL, exe, PATH_MAX-1 );
- exe[PATH_MAX-1] = 0;
- r = strrchr( exe, '\\' );
- if (r)
- *r = 0;
-}
-
+#endif // _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
diff --git a/debuggerd/test/tombstone_test.cpp b/debuggerd/test/tombstone_test.cpp
new file mode 100644
index 0000000..d945d27
--- /dev/null
+++ b/debuggerd/test/tombstone_test.cpp
@@ -0,0 +1,599 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+
+#include <memory>
+#include <string>
+
+#include <gtest/gtest.h>
+#include <base/file.h>
+
+#include "utility.h"
+
+#include "BacktraceMock.h"
+#include "elf_fake.h"
+#include "host_signal_fixup.h"
+#include "log_fake.h"
+#include "ptrace_fake.h"
+
+// In order to test this code, we need to include the tombstone.cpp code.
+// Including it, also allows us to override the ptrace function.
+#define ptrace ptrace_fake
+
+#include "tombstone.cpp"
+
+void dump_registers(log_t*, pid_t) {
+}
+
+void dump_memory_and_code(log_t*, Backtrace*) {
+}
+
+void dump_backtrace_to_log(Backtrace*, log_t*, char const*) {
+}
+
+class TombstoneTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ map_mock_.reset(new BacktraceMapMock());
+ backtrace_mock_.reset(new BacktraceMock(map_mock_.get()));
+
+ char tmp_file[256];
+ const char data_template[] = "/data/local/tmp/debuggerd_memory_testXXXXXX";
+ memcpy(tmp_file, data_template, sizeof(data_template));
+ int tombstone_fd = mkstemp(tmp_file);
+ if (tombstone_fd == -1) {
+ const char tmp_template[] = "/tmp/debuggerd_memory_testXXXXXX";
+ memcpy(tmp_file, tmp_template, sizeof(tmp_template));
+ tombstone_fd = mkstemp(tmp_file);
+ if (tombstone_fd == -1) {
+ abort();
+ }
+ }
+ if (unlink(tmp_file) == -1) {
+ abort();
+ }
+
+ log_.tfd = tombstone_fd;
+ log_.amfd = -1;
+ log_.crashed_tid = 12;
+ log_.current_tid = 12;
+ log_.should_retrieve_logcat = false;
+
+ resetLogs();
+ elf_set_fake_build_id("");
+ siginfo_t si;
+ si.si_signo = SIGABRT;
+ ptrace_set_fake_getsiginfo(si);
+ }
+
+ virtual void TearDown() {
+ if (log_.tfd >= 0) {
+ close(log_.tfd);
+ }
+ }
+
+ std::unique_ptr<BacktraceMapMock> map_mock_;
+ std::unique_ptr<BacktraceMock> backtrace_mock_;
+
+ log_t log_;
+};
+
+TEST_F(TombstoneTest, single_map) {
+ backtrace_map_t map;
+#if defined(__LP64__)
+ map.start = 0x123456789abcd000UL;
+ map.end = 0x123456789abdf000UL;
+#else
+ map.start = 0x1234000;
+ map.end = 0x1235000;
+#endif
+ map_mock_->AddMap(map);
+
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map:\n"
+#if defined(__LP64__)
+" 12345678'9abcd000-12345678'9abdefff --- 0 12000\n";
+#else
+" 01234000-01234fff --- 0 1000\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, single_map_elf_build_id) {
+ backtrace_map_t map;
+#if defined(__LP64__)
+ map.start = 0x123456789abcd000UL;
+ map.end = 0x123456789abdf000UL;
+#else
+ map.start = 0x1234000;
+ map.end = 0x1235000;
+#endif
+ map.flags = PROT_READ;
+ map.name = "/system/lib/libfake.so";
+ map_mock_->AddMap(map);
+
+ elf_set_fake_build_id("abcdef1234567890abcdef1234567890");
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map:\n"
+#if defined(__LP64__)
+" 12345678'9abcd000-12345678'9abdefff r-- 0 12000 /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n";
+#else
+" 01234000-01234fff r-- 0 1000 /system/lib/libfake.so (BuildId: abcdef1234567890abcdef1234567890)\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+// Even though build id is present, it should not be printed in either of
+// these cases.
+TEST_F(TombstoneTest, single_map_no_build_id) {
+ backtrace_map_t map;
+#if defined(__LP64__)
+ map.start = 0x123456789abcd000UL;
+ map.end = 0x123456789abdf000UL;
+#else
+ map.start = 0x1234000;
+ map.end = 0x1235000;
+#endif
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ map.name = "/system/lib/libfake.so";
+ map_mock_->AddMap(map);
+
+ elf_set_fake_build_id("abcdef1234567890abcdef1234567890");
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map:\n"
+#if defined(__LP64__)
+" 12345678'9abcd000-12345678'9abdefff -w- 0 12000\n"
+" 12345678'9abcd000-12345678'9abdefff -w- 0 12000 /system/lib/libfake.so\n";
+#else
+" 01234000-01234fff -w- 0 1000\n"
+" 01234000-01234fff -w- 0 1000 /system/lib/libfake.so\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, multiple_maps) {
+ backtrace_map_t map;
+
+ map.start = 0xa234000;
+ map.end = 0xa235000;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa334000;
+ map.end = 0xa335000;
+ map.offset = 0xf000;
+ map.flags = PROT_READ;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa434000;
+ map.end = 0xa435000;
+ map.offset = 0x1000;
+ map.load_base = 0xd000;
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa534000;
+ map.end = 0xa535000;
+ map.offset = 0x3000;
+ map.load_base = 0x2000;
+ map.flags = PROT_EXEC;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa634000;
+ map.end = 0xa635000;
+ map.offset = 0;
+ map.load_base = 0;
+ map.flags = PROT_READ | PROT_WRITE | PROT_EXEC;
+ map.name = "/system/lib/fake.so";
+ map_mock_->AddMap(map);
+
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map:\n"
+#if defined(__LP64__)
+" 00000000'0a234000-00000000'0a234fff --- 0 1000\n"
+" 00000000'0a334000-00000000'0a334fff r-- f000 1000\n"
+" 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load base 0xd000)\n"
+" 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#else
+" 0a234000-0a234fff --- 0 1000\n"
+" 0a334000-0a334fff r-- f000 1000\n"
+" 0a434000-0a434fff -w- 1000 1000 (load base 0xd000)\n"
+" 0a534000-0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, multiple_maps_fault_address_before) {
+ backtrace_map_t map;
+
+ map.start = 0xa434000;
+ map.end = 0xa435000;
+ map.offset = 0x1000;
+ map.load_base = 0xd000;
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa534000;
+ map.end = 0xa535000;
+ map.offset = 0x3000;
+ map.load_base = 0x2000;
+ map.flags = PROT_EXEC;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa634000;
+ map.end = 0xa635000;
+ map.offset = 0;
+ map.load_base = 0;
+ map.flags = PROT_READ | PROT_WRITE | PROT_EXEC;
+ map.name = "/system/lib/fake.so";
+ map_mock_->AddMap(map);
+
+ siginfo_t si;
+ si.si_signo = SIGBUS;
+ si.si_addr = reinterpret_cast<void*>(0x1000);
+ ptrace_set_fake_getsiginfo(si);
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map: (fault address prefixed with --->)\n"
+#if defined(__LP64__)
+"--->Fault address falls at 00000000'00001000 before any mapped regions\n"
+" 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load base 0xd000)\n"
+" 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#else
+"--->Fault address falls at 00001000 before any mapped regions\n"
+" 0a434000-0a434fff -w- 1000 1000 (load base 0xd000)\n"
+" 0a534000-0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, multiple_maps_fault_address_between) {
+ backtrace_map_t map;
+
+ map.start = 0xa434000;
+ map.end = 0xa435000;
+ map.offset = 0x1000;
+ map.load_base = 0xd000;
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa534000;
+ map.end = 0xa535000;
+ map.offset = 0x3000;
+ map.load_base = 0x2000;
+ map.flags = PROT_EXEC;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa634000;
+ map.end = 0xa635000;
+ map.offset = 0;
+ map.load_base = 0;
+ map.flags = PROT_READ | PROT_WRITE | PROT_EXEC;
+ map.name = "/system/lib/fake.so";
+ map_mock_->AddMap(map);
+
+ siginfo_t si;
+ si.si_signo = SIGBUS;
+ si.si_addr = reinterpret_cast<void*>(0xa533000);
+ ptrace_set_fake_getsiginfo(si);
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map: (fault address prefixed with --->)\n"
+#if defined(__LP64__)
+" 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load base 0xd000)\n"
+"--->Fault address falls at 00000000'0a533000 between mapped regions\n"
+" 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#else
+" 0a434000-0a434fff -w- 1000 1000 (load base 0xd000)\n"
+"--->Fault address falls at 0a533000 between mapped regions\n"
+" 0a534000-0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, multiple_maps_fault_address_in_map) {
+ backtrace_map_t map;
+
+ map.start = 0xa434000;
+ map.end = 0xa435000;
+ map.offset = 0x1000;
+ map.load_base = 0xd000;
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa534000;
+ map.end = 0xa535000;
+ map.offset = 0x3000;
+ map.load_base = 0x2000;
+ map.flags = PROT_EXEC;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa634000;
+ map.end = 0xa635000;
+ map.offset = 0;
+ map.load_base = 0;
+ map.flags = PROT_READ | PROT_WRITE | PROT_EXEC;
+ map.name = "/system/lib/fake.so";
+ map_mock_->AddMap(map);
+
+ siginfo_t si;
+ si.si_signo = SIGBUS;
+ si.si_addr = reinterpret_cast<void*>(0xa534040);
+ ptrace_set_fake_getsiginfo(si);
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map: (fault address prefixed with --->)\n"
+#if defined(__LP64__)
+" 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load base 0xd000)\n"
+"--->00000000'0a534000-00000000'0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#else
+" 0a434000-0a434fff -w- 1000 1000 (load base 0xd000)\n"
+"--->0a534000-0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, multiple_maps_fault_address_after) {
+ backtrace_map_t map;
+
+ map.start = 0xa434000;
+ map.end = 0xa435000;
+ map.offset = 0x1000;
+ map.load_base = 0xd000;
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa534000;
+ map.end = 0xa535000;
+ map.offset = 0x3000;
+ map.load_base = 0x2000;
+ map.flags = PROT_EXEC;
+ map_mock_->AddMap(map);
+
+ map.start = 0xa634000;
+ map.end = 0xa635000;
+ map.offset = 0;
+ map.load_base = 0;
+ map.flags = PROT_READ | PROT_WRITE | PROT_EXEC;
+ map.name = "/system/lib/fake.so";
+ map_mock_->AddMap(map);
+
+ siginfo_t si;
+ si.si_signo = SIGBUS;
+#if defined(__LP64__)
+ si.si_addr = reinterpret_cast<void*>(0x12345a534040UL);
+#else
+ si.si_addr = reinterpret_cast<void*>(0xf534040UL);
+#endif
+ ptrace_set_fake_getsiginfo(si);
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map: (fault address prefixed with --->)\n"
+#if defined(__LP64__)
+" 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load base 0xd000)\n"
+" 00000000'0a534000-00000000'0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 00000000'0a634000-00000000'0a634fff rwx 0 1000 /system/lib/fake.so\n"
+"--->Fault address falls at 00001234'5a534040 after any mapped regions\n";
+#else
+" 0a434000-0a434fff -w- 1000 1000 (load base 0xd000)\n"
+" 0a534000-0a534fff --x 3000 1000 (load base 0x2000)\n"
+" 0a634000-0a634fff rwx 0 1000 /system/lib/fake.so\n"
+"--->Fault address falls at 0f534040 after any mapped regions\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, multiple_maps_getsiginfo_fail) {
+ backtrace_map_t map;
+
+ map.start = 0xa434000;
+ map.end = 0xa435000;
+ map.offset = 0x1000;
+ map.load_base = 0xd000;
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ siginfo_t si;
+ si.si_signo = 0;
+ ptrace_set_fake_getsiginfo(si);
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ const char* expected_dump = \
+"\nmemory map:\n"
+#if defined(__LP64__)
+" 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load base 0xd000)\n";
+#else
+" 0a434000-0a434fff -w- 1000 1000 (load base 0xd000)\n";
+#endif
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("6 DEBUG Cannot get siginfo for 100: Bad address\n\n", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, multiple_maps_check_signal_has_si_addr) {
+ backtrace_map_t map;
+
+ map.start = 0xa434000;
+ map.end = 0xa435000;
+ map.flags = PROT_WRITE;
+ map_mock_->AddMap(map);
+
+ for (int i = 1; i < 255; i++) {
+ ASSERT_TRUE(ftruncate(log_.tfd, 0) == 0);
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+
+ siginfo_t si;
+ si.si_signo = i;
+ si.si_addr = reinterpret_cast<void*>(0x1000);
+ ptrace_set_fake_getsiginfo(si);
+ dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ bool has_addr = false;
+ switch (si.si_signo) {
+ case SIGBUS:
+ case SIGFPE:
+ case SIGILL:
+ case SIGSEGV:
+ case SIGTRAP:
+ has_addr = true;
+ break;
+ }
+
+ const char* expected_addr_dump = \
+"\nmemory map: (fault address prefixed with --->)\n"
+#if defined(__LP64__)
+"--->Fault address falls at 00000000'00001000 before any mapped regions\n"
+" 00000000'0a434000-00000000'0a434fff -w- 0 1000\n";
+#else
+"--->Fault address falls at 00001000 before any mapped regions\n"
+" 0a434000-0a434fff -w- 0 1000\n";
+#endif
+ const char* expected_dump = \
+"\nmemory map:\n"
+#if defined(__LP64__)
+" 00000000'0a434000-00000000'0a434fff -w- 0 1000\n";
+#else
+" 0a434000-0a434fff -w- 0 1000\n";
+#endif
+ if (has_addr) {
+ ASSERT_STREQ(expected_addr_dump, tombstone_contents.c_str())
+ << "Signal " << si.si_signo << " expected to include an address.";
+ } else {
+ ASSERT_STREQ(expected_dump, tombstone_contents.c_str())
+ << "Signal " << si.si_signo << " is not expected to include an address.";
+ }
+
+ // Verify that the log buf is empty, and no error messages.
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("", getFakeLogPrint().c_str());
+ }
+}
+
+TEST_F(TombstoneTest, dump_signal_info_error) {
+ siginfo_t si;
+ si.si_signo = 0;
+ ptrace_set_fake_getsiginfo(si);
+
+ dump_signal_info(&log_, 123, SIGSEGV, 10);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ ASSERT_STREQ("", tombstone_contents.c_str());
+
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("6 DEBUG cannot get siginfo: Bad address\n\n", getFakeLogPrint().c_str());
+}
+
+TEST_F(TombstoneTest, dump_log_file_error) {
+ log_.should_retrieve_logcat = true;
+ dump_log_file(&log_, 123, "/fake/filename", 10);
+
+ std::string tombstone_contents;
+ ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
+ ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
+ ASSERT_STREQ("", tombstone_contents.c_str());
+
+ ASSERT_STREQ("", getFakeLogBuf().c_str());
+ ASSERT_STREQ("6 DEBUG Unable to open /fake/filename: Permission denied\n\n",
+ getFakeLogPrint().c_str());
+}
diff --git a/debuggerd/tombstone.cpp b/debuggerd/tombstone.cpp
index 614edb6..e283923 100644
--- a/debuggerd/tombstone.cpp
+++ b/debuggerd/tombstone.cpp
@@ -81,7 +81,6 @@
case SIGBUS: return "SIGBUS";
case SIGFPE: return "SIGFPE";
case SIGILL: return "SIGILL";
- case SIGPIPE: return "SIGPIPE";
case SIGSEGV: return "SIGSEGV";
#if defined(SIGSTKFLT)
case SIGSTKFLT: return "SIGSTKFLT";
@@ -180,7 +179,7 @@
siginfo_t si;
memset(&si, 0, sizeof(si));
if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si) == -1) {
- _LOG(log, logtype::HEADER, "cannot get siginfo: %s\n", strerror(errno));
+ ALOGE("cannot get siginfo: %s\n", strerror(errno));
return;
}
@@ -317,16 +316,28 @@
}
}
+static std::string get_addr_string(uintptr_t addr) {
+ std::string addr_str;
+#if defined(__LP64__)
+ addr_str = android::base::StringPrintf("%08x'%08x",
+ static_cast<uint32_t>(addr >> 32),
+ static_cast<uint32_t>(addr & 0xffffffff));
+#else
+ addr_str = android::base::StringPrintf("%08x", addr);
+#endif
+ return addr_str;
+}
+
static void dump_all_maps(Backtrace* backtrace, BacktraceMap* map, log_t* log, pid_t tid) {
bool print_fault_address_marker = false;
uintptr_t addr = 0;
siginfo_t si;
memset(&si, 0, sizeof(si));
- if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) {
- _LOG(log, logtype::ERROR, "cannot get siginfo for %d: %s\n", tid, strerror(errno));
- } else {
+ if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si) != -1) {
print_fault_address_marker = signal_has_si_addr(si.si_signo);
addr = reinterpret_cast<uintptr_t>(si.si_addr);
+ } else {
+ ALOGE("Cannot get siginfo for %d: %s\n", tid, strerror(errno));
}
_LOG(log, logtype::MAPS, "\n");
@@ -335,8 +346,8 @@
} else {
_LOG(log, logtype::MAPS, "memory map: (fault address prefixed with --->)\n");
if (map->begin() != map->end() && addr < map->begin()->start) {
- _LOG(log, logtype::MAPS, "--->Fault address falls at %" PRIPTR " before any mapped regions\n",
- addr);
+ _LOG(log, logtype::MAPS, "--->Fault address falls at %s before any mapped regions\n",
+ get_addr_string(addr).c_str());
print_fault_address_marker = false;
}
}
@@ -346,15 +357,15 @@
line = " ";
if (print_fault_address_marker) {
if (addr < it->start) {
- _LOG(log, logtype::MAPS, "--->Fault address falls at %" PRIPTR " between mapped regions\n",
- addr);
+ _LOG(log, logtype::MAPS, "--->Fault address falls at %s between mapped regions\n",
+ get_addr_string(addr).c_str());
print_fault_address_marker = false;
} else if (addr >= it->start && addr < it->end) {
line = "--->";
print_fault_address_marker = false;
}
}
- line += android::base::StringPrintf("%" PRIPTR "-%" PRIPTR " ", it->start, it->end - 1);
+ line += get_addr_string(it->start) + '-' + get_addr_string(it->end - 1) + ' ';
if (it->flags & PROT_READ) {
line += 'r';
} else {
@@ -372,7 +383,9 @@
}
line += android::base::StringPrintf(" %8" PRIxPTR " %8" PRIxPTR,
it->offset, it->end - it->start);
+ bool space_needed = true;
if (it->name.length() > 0) {
+ space_needed = false;
line += " " + it->name;
std::string build_id;
if ((it->flags & PROT_READ) && elf_get_build_id(backtrace, it->start, &build_id)) {
@@ -380,13 +393,16 @@
}
}
if (it->load_base != 0) {
+ if (space_needed) {
+ line += ' ';
+ }
line += android::base::StringPrintf(" (load base 0x%" PRIxPTR ")", it->load_base);
}
_LOG(log, logtype::MAPS, "%s\n", line.c_str());
}
if (print_fault_address_marker) {
- _LOG(log, logtype::MAPS, "--->Fault address falls at %" PRIPTR " after any mapped regions\n",
- addr);
+ _LOG(log, logtype::MAPS, "--->Fault address falls at %s after any mapped regions\n",
+ get_addr_string(addr).c_str());
}
}
@@ -431,7 +447,7 @@
// Skip this thread if cannot ptrace it
if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) {
- _LOG(log, logtype::ERROR, "ptrace attach to %d failed: %s\n", new_tid, strerror(errno));
+ ALOGE("ptrace attach to %d failed: %s\n", new_tid, strerror(errno));
continue;
}
@@ -454,7 +470,7 @@
log->current_tid = log->crashed_tid;
if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) {
- _LOG(log, logtype::ERROR, "ptrace detach from %d failed: %s\n", new_tid, strerror(errno));
+ ALOGE("ptrace detach from %d failed: %s\n", new_tid, strerror(errno));
detach_failed = true;
}
}
@@ -500,13 +516,11 @@
// non-blocking EOF; we're done
break;
} else {
- _LOG(log, logtype::ERROR, "Error while reading log: %s\n",
- strerror(-actual));
+ ALOGE("Error while reading log: %s\n", strerror(-actual));
break;
}
} else if (actual == 0) {
- _LOG(log, logtype::ERROR, "Got zero bytes while reading log: %s\n",
- strerror(errno));
+ ALOGE("Got zero bytes while reading log: %s\n", strerror(errno));
break;
}
@@ -772,11 +786,11 @@
log.crashed_tid = tid;
if ((mkdir(TOMBSTONE_DIR, 0755) == -1) && (errno != EEXIST)) {
- _LOG(&log, logtype::ERROR, "failed to create %s: %s\n", TOMBSTONE_DIR, strerror(errno));
+ ALOGE("failed to create %s: %s\n", TOMBSTONE_DIR, strerror(errno));
}
if (chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM) == -1) {
- _LOG(&log, logtype::ERROR, "failed to change ownership of %s: %s\n", TOMBSTONE_DIR, strerror(errno));
+ ALOGE("failed to change ownership of %s: %s\n", TOMBSTONE_DIR, strerror(errno));
}
int fd = -1;
@@ -784,11 +798,11 @@
if (selinux_android_restorecon(TOMBSTONE_DIR, 0) == 0) {
path = find_and_open_tombstone(&fd);
} else {
- _LOG(&log, logtype::ERROR, "Failed to restore security context, not writing tombstone.\n");
+ ALOGE("Failed to restore security context, not writing tombstone.\n");
}
if (fd < 0) {
- _LOG(&log, logtype::ERROR, "Skipping tombstone write, nothing to do.\n");
+ ALOGE("Skipping tombstone write, nothing to do.\n");
*detach_failed = false;
return NULL;
}
diff --git a/debuggerd/utility.cpp b/debuggerd/utility.cpp
index 9f340a8..f5d6ec1 100644
--- a/debuggerd/utility.cpp
+++ b/debuggerd/utility.cpp
@@ -35,8 +35,7 @@
// Whitelist output desired in the logcat output.
bool is_allowed_in_logcat(enum logtype ltype) {
- if ((ltype == ERROR)
- || (ltype == HEADER)
+ if ((ltype == HEADER)
|| (ltype == REGISTERS)
|| (ltype == BACKTRACE)) {
return true;
@@ -157,13 +156,28 @@
bytes &= ~(sizeof(uintptr_t) - 1);
}
- if (bytes < MEMORY_BYTES_TO_DUMP && bytes > 0) {
- // Try to do one more read. This could happen if a read crosses a map, but
- // the maps do not have any break between them. Only requires one extra
- // read because a map has to contain at least one page, and the total
- // number of bytes to dump is smaller than a page.
- size_t bytes2 = backtrace->Read(addr + bytes, reinterpret_cast<uint8_t*>(data) + bytes,
- sizeof(data) - bytes);
+ uintptr_t start = 0;
+ bool skip_2nd_read = false;
+ if (bytes == 0) {
+ // In this case, we might want to try another read at the beginning of
+ // the next page only if it's within the amount of memory we would have
+ // read.
+ size_t page_size = sysconf(_SC_PAGE_SIZE);
+ start = ((addr + (page_size - 1)) & ~(page_size - 1)) - addr;
+ if (start == 0 || start >= MEMORY_BYTES_TO_DUMP) {
+ skip_2nd_read = true;
+ }
+ }
+
+ if (bytes < MEMORY_BYTES_TO_DUMP && !skip_2nd_read) {
+ // Try to do one more read. This could happen if a read crosses a map,
+ // but the maps do not have any break between them. Or it could happen
+ // if reading from an unreadable map, but the read would cross back
+ // into a readable map. Only requires one extra read because a map has
+ // to contain at least one page, and the total number of bytes to dump
+ // is smaller than a page.
+ size_t bytes2 = backtrace->Read(addr + start + bytes, reinterpret_cast<uint8_t*>(data) + bytes,
+ sizeof(data) - bytes - start);
bytes += bytes2;
if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) {
// This should never happen, but we'll try and continue any way.
@@ -179,15 +193,16 @@
// On 32-bit machines, there are still 16 bytes per line but addresses and
// words are of course presented differently.
uintptr_t* data_ptr = data;
+ size_t current = 0;
+ size_t total_bytes = start + bytes;
for (size_t line = 0; line < MEMORY_BYTES_TO_DUMP / MEMORY_BYTES_PER_LINE; line++) {
std::string logline;
android::base::StringAppendF(&logline, " %" PRIPTR, addr);
addr += MEMORY_BYTES_PER_LINE;
std::string ascii;
- for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++, data_ptr++) {
- if (bytes >= sizeof(uintptr_t)) {
- bytes -= sizeof(uintptr_t);
+ for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++) {
+ if (current >= start && current + sizeof(uintptr_t) <= total_bytes) {
android::base::StringAppendF(&logline, " %" PRIPTR, *data_ptr);
// Fill out the ascii string from the data.
@@ -199,10 +214,12 @@
ascii += '.';
}
}
+ data_ptr++;
} else {
logline += ' ' + std::string(sizeof(uintptr_t) * 2, '-');
ascii += std::string(sizeof(uintptr_t), '.');
}
+ current += sizeof(uintptr_t);
}
_LOG(log, logtype::MEMORY, "%s %s\n", logline.c_str(), ascii.c_str());
}
diff --git a/debuggerd/utility.h b/debuggerd/utility.h
index 263374d..8bef192 100644
--- a/debuggerd/utility.h
+++ b/debuggerd/utility.h
@@ -59,7 +59,6 @@
// List of types of logs to simplify the logging decision in _LOG
enum logtype {
- ERROR,
HEADER,
THREAD,
REGISTERS,
diff --git a/debuggerd/x86/machine.cpp b/debuggerd/x86/machine.cpp
index c5f9259..af10817 100644
--- a/debuggerd/x86/machine.cpp
+++ b/debuggerd/x86/machine.cpp
@@ -14,12 +14,15 @@
* limitations under the License.
*/
+#define LOG_TAG "DEBUG"
+
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <sys/ptrace.h>
#include <backtrace/Backtrace.h>
+#include <log/log.h>
#include "machine.h"
#include "utility.h"
@@ -27,7 +30,7 @@
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
struct pt_regs r;
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r) == -1) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
@@ -44,7 +47,7 @@
void dump_registers(log_t* log, pid_t tid) {
struct pt_regs r;
if (ptrace(PTRACE_GETREGS, tid, 0, &r) == -1) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
diff --git a/debuggerd/x86_64/machine.cpp b/debuggerd/x86_64/machine.cpp
index 4f09a5d..fc86bc2 100644
--- a/debuggerd/x86_64/machine.cpp
+++ b/debuggerd/x86_64/machine.cpp
@@ -14,6 +14,8 @@
** limitations under the License.
*/
+#define LOG_TAG "DEBUG"
+
#include <errno.h>
#include <stdint.h>
#include <sys/ptrace.h>
@@ -21,6 +23,7 @@
#include <sys/user.h>
#include <backtrace/Backtrace.h>
+#include <log/log.h>
#include "machine.h"
#include "utility.h"
@@ -28,7 +31,7 @@
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
struct user_regs_struct r;
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r) == -1) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
@@ -45,7 +48,7 @@
void dump_registers(log_t* log, pid_t tid) {
struct user_regs_struct r;
if (ptrace(PTRACE_GETREGS, tid, 0, &r) == -1) {
- _LOG(log, logtype::ERROR, "cannot get registers: %s\n", strerror(errno));
+ ALOGE("cannot get registers: %s\n", strerror(errno));
return;
}
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index dee471a..ce8e15f 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -14,30 +14,33 @@
LOCAL_PATH:= $(call my-dir)
+fastboot_version := $(shell git -C $(LOCAL_PATH) rev-parse --short=12 HEAD 2>/dev/null)-android
+
include $(CLEAR_VARS)
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../mkbootimg \
$(LOCAL_PATH)/../../extras/ext4_utils \
$(LOCAL_PATH)/../../extras/f2fs_utils
-LOCAL_SRC_FILES := protocol.c engine.c bootimg_utils.cpp fastboot.cpp util.c fs.c
+LOCAL_SRC_FILES := protocol.cpp engine.cpp bootimg_utils.cpp fastboot.cpp util.cpp fs.cpp
LOCAL_MODULE := fastboot
LOCAL_MODULE_TAGS := debug
LOCAL_CONLYFLAGS += -std=gnu99
-LOCAL_CFLAGS += -Wall -Wextra -Werror
+LOCAL_CFLAGS += -Wall -Wextra -Werror -Wunreachable-code
+
+LOCAL_CFLAGS += -DFASTBOOT_REVISION='"$(fastboot_version)"'
ifeq ($(HOST_OS),linux)
- LOCAL_SRC_FILES += usb_linux.c util_linux.c
+ LOCAL_SRC_FILES += usb_linux.cpp util_linux.cpp
endif
ifeq ($(HOST_OS),darwin)
- LOCAL_SRC_FILES += usb_osx.c util_osx.c
- LOCAL_LDLIBS += -lpthread -framework CoreFoundation -framework IOKit \
- -framework Carbon
+ LOCAL_SRC_FILES += usb_osx.cpp util_osx.cpp
+ LOCAL_LDLIBS += -lpthread -framework CoreFoundation -framework IOKit -framework Carbon
LOCAL_CFLAGS += -Wno-unused-parameter
endif
ifeq ($(HOST_OS),windows)
- LOCAL_SRC_FILES += usb_windows.c util_windows.c
+ LOCAL_SRC_FILES += usb_windows.cpp util_windows.cpp
EXTRA_STATIC_LIBS := AdbWinApi
ifneq ($(strip $(USE_CYGWIN)),)
# Pure cygwin case
@@ -94,10 +97,9 @@
$(call dist-for-goals,dist_files sdk,$(my_dist_files))
my_dist_files :=
-
ifeq ($(HOST_OS),linux)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := usbtest.c usb_linux.c util.c
+LOCAL_SRC_FILES := usbtest.cpp usb_linux.cpp util.cpp
LOCAL_MODULE := usbtest
LOCAL_CFLAGS := -Werror
include $(BUILD_HOST_EXECUTABLE)
diff --git a/fastboot/engine.c b/fastboot/engine.cpp
similarity index 89%
rename from fastboot/engine.c
rename to fastboot/engine.cpp
index 2f90e41..66b8140 100644
--- a/fastboot/engine.c
+++ b/fastboot/engine.cpp
@@ -45,10 +45,6 @@
#include <sys/mman.h>
#endif
-#ifndef __unused
-#define __unused __attribute__((__unused__))
-#endif
-
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
#define OP_DOWNLOAD 1
@@ -73,7 +69,7 @@
unsigned size;
const char *msg;
- int (*func)(Action *a, int status, char *resp);
+ int (*func)(Action* a, int status, const char* resp);
double start;
};
@@ -121,8 +117,7 @@
return !!fs_get_generator(fs_type);
}
-static int cb_default(Action *a, int status, char *resp)
-{
+static int cb_default(Action* a, int status, const char* resp) {
if (status) {
fprintf(stderr,"FAILED (%s)\n", resp);
} else {
@@ -135,12 +130,11 @@
static Action *queue_action(unsigned op, const char *fmt, ...)
{
- Action *a;
va_list ap;
size_t cmdsize;
- a = calloc(1, sizeof(Action));
- if (a == 0) die("out of memory");
+ Action* a = reinterpret_cast<Action*>(calloc(1, sizeof(Action)));
+ if (a == nullptr) die("out of memory");
va_start(ap, fmt);
cmdsize = vsnprintf(a->cmd, sizeof(a->cmd), fmt, ap);
@@ -198,8 +192,7 @@
a->msg = mkmsg("writing '%s'", ptn);
}
-static int match(char *str, const char **value, unsigned count)
-{
+static int match(const char* str, const char** value, unsigned count) {
unsigned n;
for (n = 0; n < count; n++) {
@@ -222,9 +215,9 @@
-static int cb_check(Action *a, int status, char *resp, int invert)
+static int cb_check(Action* a, int status, const char* resp, int invert)
{
- const char **value = a->data;
+ const char** value = reinterpret_cast<const char**>(a->data);
unsigned count = a->size;
unsigned n;
int yes;
@@ -265,18 +258,16 @@
return -1;
}
-static int cb_require(Action *a, int status, char *resp)
-{
+static int cb_require(Action*a, int status, const char* resp) {
return cb_check(a, status, resp, 0);
}
-static int cb_reject(Action *a, int status, char *resp)
-{
+static int cb_reject(Action* a, int status, const char* resp) {
return cb_check(a, status, resp, 1);
}
void fb_queue_require(const char *prod, const char *var,
- int invert, unsigned nvalues, const char **value)
+ int invert, unsigned nvalues, const char **value)
{
Action *a;
a = queue_action(OP_QUERY, "getvar:%s", var);
@@ -285,11 +276,10 @@
a->size = nvalues;
a->msg = mkmsg("checking %s", var);
a->func = invert ? cb_reject : cb_require;
- if (a->data == 0) die("out of memory");
+ if (a->data == nullptr) die("out of memory");
}
-static int cb_display(Action *a, int status, char *resp)
-{
+static int cb_display(Action* a, int status, const char* resp) {
if (status) {
fprintf(stderr, "%s FAILED (%s)\n", a->cmd, resp);
return status;
@@ -303,17 +293,16 @@
Action *a;
a = queue_action(OP_QUERY, "getvar:%s", var);
a->data = strdup(prettyname);
- if (a->data == 0) die("out of memory");
+ if (a->data == nullptr) die("out of memory");
a->func = cb_display;
}
-static int cb_save(Action *a, int status, char *resp)
-{
+static int cb_save(Action* a, int status, const char* resp) {
if (status) {
fprintf(stderr, "%s FAILED (%s)\n", a->cmd, resp);
return status;
}
- strncpy(a->data, resp, a->size);
+ strncpy(reinterpret_cast<char*>(a->data), resp, a->size);
return 0;
}
@@ -326,8 +315,7 @@
a->func = cb_save;
}
-static int cb_do_nothing(Action *a __unused, int status __unused, char *resp __unused)
-{
+static int cb_do_nothing(Action*, int , const char*) {
fprintf(stderr,"\n");
return 0;
}
@@ -398,7 +386,7 @@
} else if (a->op == OP_NOTICE) {
fprintf(stderr,"%s\n",(char*)a->data);
} else if (a->op == OP_DOWNLOAD_SPARSE) {
- status = fb_download_data_sparse(usb, a->data);
+ status = fb_download_data_sparse(usb, reinterpret_cast<sparse_file*>(a->data));
status = a->func(a, status, status ? fb_get_error() : "");
if (status) break;
} else if (a->op == OP_WAIT_FOR_DISCONNECT) {
@@ -414,5 +402,5 @@
int fb_queue_is_empty(void)
{
- return (action_list == NULL);
+ return (action_list == nullptr);
}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 7acfd6e..4dbf32f 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -59,7 +59,6 @@
char cur_product[FB_RESPONSE_SZ + 1];
-static usb_handle *usb = 0;
static const char *serial = 0;
static const char *product = 0;
static const char *cmdline = 0;
@@ -190,25 +189,16 @@
return load_fd(fd, _sz);
}
-int match_fastboot_with_serial(usb_ifc_info *info, const char *local_serial)
-{
- if(!(vendor_id && (info->dev_vendor == vendor_id)) &&
- (info->dev_vendor != 0x18d1) && // Google
- (info->dev_vendor != 0x8087) && // Intel
- (info->dev_vendor != 0x0451) &&
- (info->dev_vendor != 0x0502) &&
- (info->dev_vendor != 0x0fce) && // Sony Ericsson
- (info->dev_vendor != 0x05c6) && // Qualcomm
- (info->dev_vendor != 0x22b8) && // Motorola
- (info->dev_vendor != 0x0955) && // Nvidia
- (info->dev_vendor != 0x413c) && // DELL
- (info->dev_vendor != 0x2314) && // INQ Mobile
- (info->dev_vendor != 0x0b05) && // Asus
- (info->dev_vendor != 0x0bb4)) // HTC
- return -1;
- if(info->ifc_class != 0xff) return -1;
- if(info->ifc_subclass != 0x42) return -1;
- if(info->ifc_protocol != 0x03) return -1;
+int match_fastboot_with_serial(usb_ifc_info* info, const char* local_serial) {
+ // Require a matching vendor id if the user specified one with -i.
+ if (vendor_id != 0 && info->dev_vendor != vendor_id) {
+ return -1;
+ }
+
+ if (info->ifc_class != 0xff || info->ifc_subclass != 0x42 || info->ifc_protocol != 0x03) {
+ return -1;
+ }
+
// require matching serial number or device path if requested
// at the command line with the -s option.
if (local_serial && (strcmp(local_serial, info->serial_number) != 0 &&
@@ -256,7 +246,7 @@
if(usb) return usb;
if(announce) {
announce = 0;
- fprintf(stderr,"< waiting for device >\n");
+ fprintf(stderr, "< waiting for %s >\n", serial ? serial : "any device");
}
usleep(1000);
}
@@ -276,52 +266,52 @@
"usage: fastboot [ <option> ] <command>\n"
"\n"
"commands:\n"
- " update <filename> reflash device from update.zip\n"
- " flashall flash boot, system, vendor and if found,\n"
- " recovery\n"
- " flash <partition> [ <filename> ] write a file to a flash partition\n"
- " flashing lock locks the device. Prevents flashing"
- " partitions\n"
- " flashing unlock unlocks the device. Allows user to"
- " flash any partition except the ones"
- " that are related to bootloader\n"
- " flashing lock_critical Prevents flashing bootloader related"
- " partitions\n"
- " flashing unlock_critical Enables flashing bootloader related"
- " partitions\n"
- " flashing get_unlock_ability Queries bootloader to see if the"
- " device is unlocked\n"
- " erase <partition> erase a flash partition\n"
- " format[:[<fs type>][:[<size>]] <partition> format a flash partition.\n"
- " Can override the fs type and/or\n"
- " size the bootloader reports.\n"
- " getvar <variable> display a bootloader variable\n"
- " boot <kernel> [ <ramdisk> [ <second> ] ] download and boot kernel\n"
- " flash:raw boot <kernel> [ <ramdisk> [ <second> ] ] create bootimage and \n"
- " flash it\n"
- " devices list all connected devices\n"
- " continue continue with autoboot\n"
- " reboot [bootloader] reboot device, optionally into bootloader\n"
- " reboot-bootloader reboot device into bootloader\n"
- " help show this help message\n"
+ " update <filename> Reflash device from update.zip.\n"
+ " flashall Flash boot, system, vendor, and --\n"
+ " if found -- recovery.\n"
+ " flash <partition> [ <filename> ] Write a file to a flash partition.\n"
+ " flashing lock Locks the device. Prevents flashing.\n"
+ " flashing unlock Unlocks the device. Allows flashing\n"
+ " any partition except\n"
+ " bootloader-related partitions.\n"
+ " flashing lock_critical Prevents flashing bootloader-related\n"
+ " partitions.\n"
+ " flashing unlock_critical Enables flashing bootloader-related\n"
+ " partitions.\n"
+ " flashing get_unlock_ability Queries bootloader to see if the\n"
+ " device is unlocked.\n"
+ " erase <partition> Erase a flash partition.\n"
+ " format[:[<fs type>][:[<size>]] <partition>\n"
+ " Format a flash partition. Can\n"
+ " override the fs type and/or size\n"
+ " the bootloader reports.\n"
+ " getvar <variable> Display a bootloader variable.\n"
+ " boot <kernel> [ <ramdisk> [ <second> ] ] Download and boot kernel.\n"
+ " flash:raw boot <kernel> [ <ramdisk> [ <second> ] ]\n"
+ " Create bootimage and flash it.\n"
+ " devices [-l] List all connected devices [with\n"
+ " device paths].\n"
+ " continue Continue with autoboot.\n"
+ " reboot [bootloader] Reboot device [into bootloader].\n"
+ " reboot-bootloader Reboot device into bootloader.\n"
+ " help Show this help message.\n"
"\n"
"options:\n"
- " -w erase userdata and cache (and format\n"
- " if supported by partition type)\n"
- " -u do not first erase partition before\n"
- " formatting\n"
- " -s <specific device> specify device serial number\n"
- " or path to device port\n"
- " -l with \"devices\", lists device paths\n"
- " -p <product> specify product name\n"
- " -c <cmdline> override kernel commandline\n"
- " -i <vendor id> specify a custom USB vendor id\n"
- " -b <base_addr> specify a custom kernel base address.\n"
- " default: 0x10000000\n"
- " -n <page size> specify the nand page size.\n"
- " default: 2048\n"
- " -S <size>[K|M|G] automatically sparse files greater\n"
- " than size. 0 to disable\n"
+ " -w Erase userdata and cache (and format\n"
+ " if supported by partition type).\n"
+ " -u Do not erase partition before\n"
+ " formatting.\n"
+ " -s <specific device> Specify device serial number\n"
+ " or path to device port.\n"
+ " -p <product> Specify product name.\n"
+ " -c <cmdline> Override kernel commandline.\n"
+ " -i <vendor id> Specify a custom USB vendor id.\n"
+ " -b <base_addr> Specify a custom kernel base\n"
+ " address (default: 0x10000000).\n"
+ " -n <page size> Specify the nand page size\n"
+ " (default: 2048).\n"
+ " -S <size>[K|M|G] Automatically sparse files greater\n"
+ " than 'size'. 0 to disable.\n"
);
}
@@ -392,7 +382,7 @@
static void* unzip_file(ZipArchiveHandle zip, const char* entry_name, unsigned* sz)
{
- ZipEntryName zip_entry_name(entry_name);
+ ZipString zip_entry_name(entry_name);
ZipEntry zip_entry;
if (FindEntry(zip, zip_entry_name, &zip_entry) != 0) {
fprintf(stderr, "archive does not contain '%s'\n", entry_name);
@@ -417,6 +407,35 @@
return data;
}
+#if defined(_WIN32)
+
+// TODO: move this to somewhere it can be shared.
+
+#include <windows.h>
+
+// Windows' tmpfile(3) requires administrator rights because
+// it creates temporary files in the root directory.
+static FILE* win32_tmpfile() {
+ char temp_path[PATH_MAX];
+ DWORD nchars = GetTempPath(sizeof(temp_path), temp_path);
+ if (nchars == 0 || nchars >= sizeof(temp_path)) {
+ fprintf(stderr, "GetTempPath failed, error %ld\n", GetLastError());
+ return nullptr;
+ }
+
+ char filename[PATH_MAX];
+ if (GetTempFileName(temp_path, "fastboot", 0, filename) == 0) {
+ fprintf(stderr, "GetTempFileName failed, error %ld\n", GetLastError());
+ return nullptr;
+ }
+
+ return fopen(filename, "w+bTD");
+}
+
+#define tmpfile win32_tmpfile
+
+#endif
+
static int unzip_to_file(ZipArchiveHandle zip, char* entry_name) {
FILE* fp = tmpfile();
if (fp == NULL) {
@@ -425,7 +444,7 @@
return -1;
}
- ZipEntryName zip_entry_name(entry_name);
+ ZipString zip_entry_name(entry_name);
ZipEntry zip_entry;
if (FindEntry(zip, zip_entry_name, &zip_entry) != 0) {
fprintf(stderr, "archive does not contain '%s'\n", entry_name);
@@ -619,7 +638,7 @@
* erase partitions of type ext4 before flashing a filesystem so no stale
* inodes are left lying around. Otherwise, e2fsck gets very upset.
*/
-static int needs_erase(const char *part)
+static int needs_erase(usb_handle* usb, const char *part)
{
/* The function fb_format_supported() currently returns the value
* we want, so just call it.
@@ -722,29 +741,33 @@
ZipArchiveHandle zip;
int error = OpenArchive(filename, &zip);
if (error != 0) {
+ CloseArchive(zip);
die("failed to open zip file '%s': %s", filename, ErrorCodeString(error));
}
unsigned sz;
void* data = unzip_file(zip, "android-info.txt", &sz);
if (data == 0) {
+ CloseArchive(zip);
die("update package '%s' has no android-info.txt", filename);
}
setup_requirements(reinterpret_cast<char*>(data), sz);
- for (size_t i = 0; i < ARRAY_SIZE(images); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(images); ++i) {
int fd = unzip_to_file(zip, images[i].img_name);
- if (fd < 0) {
- if (images[i].is_optional)
+ if (fd == -1) {
+ if (images[i].is_optional) {
continue;
- die("update package missing %s", images[i].img_name);
+ }
+ CloseArchive(zip);
+ exit(1); // unzip_to_file already explained why.
}
fastboot_buffer buf;
int rc = load_buf_fd(usb, fd, &buf);
if (rc) die("cannot load %s from flash", images[i].img_name);
do_update_signature(zip, images[i].sig_name);
- if (erase_first && needs_erase(images[i].part_name)) {
+ if (erase_first && needs_erase(usb, images[i].part_name)) {
fb_queue_erase(images[i].part_name);
}
flash_buf(images[i].part_name, &buf);
@@ -799,7 +822,7 @@
die("could not load %s\n", images[i].img_name);
}
do_send_signature(fname);
- if (erase_first && needs_erase(images[i].part_name)) {
+ if (erase_first && needs_erase(usb, images[i].part_name)) {
fb_queue_erase(images[i].part_name);
}
flash_buf(images[i].part_name, &buf);
@@ -867,7 +890,8 @@
return num;
}
-void fb_perform_format(const char *partition, int skip_if_not_supported,
+void fb_perform_format(usb_handle* usb,
+ const char *partition, int skip_if_not_supported,
const char *type_override, const char *size_override)
{
char pTypeBuff[FB_RESPONSE_SZ + 1], pSizeBuff[FB_RESPONSE_SZ + 1];
@@ -971,8 +995,9 @@
{"page_size", required_argument, 0, 'n'},
{"ramdisk_offset", required_argument, 0, 'r'},
{"tags_offset", required_argument, 0, 't'},
- {"help", 0, 0, 'h'},
- {"unbuffered", 0, 0, 0},
+ {"help", no_argument, 0, 'h'},
+ {"unbuffered", no_argument, 0, 0},
+ {"version", no_argument, 0, 0},
{0, 0, 0, 0}
};
@@ -1044,6 +1069,9 @@
if (strcmp("unbuffered", longopts[longindex].name) == 0) {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
+ } else if (strcmp("version", longopts[longindex].name) == 0) {
+ fprintf(stdout, "fastboot version %s\n", FASTBOOT_REVISION);
+ return 0;
}
break;
default:
@@ -1070,7 +1098,7 @@
return 0;
}
- usb = open_device();
+ usb_handle* usb = open_device();
while (argc > 0) {
if(!strcmp(*argv, "getvar")) {
@@ -1112,10 +1140,10 @@
}
if (type_override && !type_override[0]) type_override = NULL;
if (size_override && !size_override[0]) size_override = NULL;
- if (erase_first && needs_erase(argv[1])) {
+ if (erase_first && needs_erase(usb, argv[1])) {
fb_queue_erase(argv[1]);
}
- fb_perform_format(argv[1], 0, type_override, size_override);
+ fb_perform_format(usb, argv[1], 0, type_override, size_override);
skip(2);
} else if(!strcmp(*argv, "signature")) {
require(2);
@@ -1175,7 +1203,7 @@
skip(2);
}
if (fname == 0) die("cannot determine image filename for '%s'", pname);
- if (erase_first && needs_erase(pname)) {
+ if (erase_first && needs_erase(usb, pname)) {
fb_queue_erase(pname);
}
do_flash(usb, pname, fname);
@@ -1230,9 +1258,9 @@
if (wants_wipe) {
fb_queue_erase("userdata");
- fb_perform_format("userdata", 1, NULL, NULL);
+ fb_perform_format(usb, "userdata", 1, NULL, NULL);
fb_queue_erase("cache");
- fb_perform_format("cache", 1, NULL, NULL);
+ fb_perform_format(usb, "cache", 1, NULL, NULL);
}
if (wants_reboot) {
fb_queue_reboot();
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index 1786e49..481c501 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -69,7 +69,7 @@
/* util stuff */
double now();
char *mkmsg(const char *fmt, ...);
-void die(const char *fmt, ...);
+__attribute__((__noreturn__)) void die(const char *fmt, ...);
void get_my_path(char *path);
diff --git a/fastboot/fs.c b/fastboot/fs.cpp
similarity index 93%
rename from fastboot/fs.c
rename to fastboot/fs.cpp
index 8a15e6f..d8f9e16 100644
--- a/fastboot/fs.c
+++ b/fastboot/fs.cpp
@@ -38,7 +38,7 @@
static const struct fs_generator {
- char *fs_type; //must match what fastboot reports for partition type
+ const char* fs_type; //must match what fastboot reports for partition type
int (*generate)(int fd, long long partSize); //returns 0 or error value
} generators[] = {
diff --git a/fastboot/protocol.c b/fastboot/protocol.cpp
similarity index 97%
rename from fastboot/protocol.c
rename to fastboot/protocol.cpp
index 5b97600..00c8a03 100644
--- a/fastboot/protocol.c
+++ b/fastboot/protocol.cpp
@@ -223,9 +223,9 @@
static int fb_download_data_sparse_write(void *priv, const void *data, int len)
{
int r;
- usb_handle *usb = priv;
+ usb_handle* usb = reinterpret_cast<usb_handle*>(priv);
int to_write;
- const char *ptr = data;
+ const char* ptr = reinterpret_cast<const char*>(data);
if (usb_buf_len) {
to_write = min(USB_BUF_SIZE - usb_buf_len, len);
diff --git a/fastboot/usb_linux.c b/fastboot/usb_linux.cpp
similarity index 93%
rename from fastboot/usb_linux.c
rename to fastboot/usb_linux.cpp
index 022f364..7b87907 100644
--- a/fastboot/usb_linux.c
+++ b/fastboot/usb_linux.cpp
@@ -26,29 +26,22 @@
* SUCH DAMAGE.
*/
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
-#include <unistd.h>
#include <string.h>
-
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <pthread.h>
-#include <ctype.h>
+#include <unistd.h>
#include <linux/usbdevice_fs.h>
-#include <linux/usbdevice_fs.h>
#include <linux/version.h>
-#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
#include <linux/usb/ch9.h>
-#else
-#include <linux/usb_ch9.h>
-#endif
-#include <asm/byteorder.h>
#include "fastboot.h"
#include "usb.h"
@@ -69,9 +62,19 @@
#define DBG1(x...)
#endif
-/* The max bulk size for linux is 16384 which is defined
- * in drivers/usb/core/devio.c.
- */
+// Kernels before 3.3 have a 16KiB transfer limit. That limit was replaced
+// with a 16MiB global limit in 3.3, but each URB submitted required a
+// contiguous kernel allocation, so you would get ENOMEM if you tried to
+// send something larger than the biggest available contiguous kernel
+// memory region. 256KiB contiguous allocations are generally not reliable
+// on a device kernel that has been running for a while fragmenting its
+// memory, but that shouldn't be a problem for fastboot on the host.
+// In 3.6, the contiguous buffer limit was removed by allocating multiple
+// 16KiB chunks and having the USB driver stitch them back together while
+// transmitting using a scatter-gather list, so 256KiB bulk transfers should
+// be reliable.
+// 256KiB seems to work, but 1MiB bulk transfers lock up my z620 with a 3.13
+// kernel.
#define MAX_USBFS_BULK_SIZE (16 * 1024)
struct usb_handle
@@ -340,7 +343,7 @@
if(filter_usb_device(de->d_name, desc, n, writable, callback,
&in, &out, &ifc) == 0) {
- usb = calloc(1, sizeof(usb_handle));
+ usb = reinterpret_cast<usb_handle*>(calloc(1, sizeof(usb_handle)));
strcpy(usb->fname, devname);
usb->ep_in = in;
usb->ep_out = out;
diff --git a/fastboot/usb_osx.c b/fastboot/usb_osx.cpp
similarity index 96%
rename from fastboot/usb_osx.c
rename to fastboot/usb_osx.cpp
index 0b6c515..45ae833 100644
--- a/fastboot/usb_osx.c
+++ b/fastboot/usb_osx.cpp
@@ -26,6 +26,7 @@
* SUCH DAMAGE.
*/
+#include <inttypes.h>
#include <stdio.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
@@ -74,7 +75,6 @@
HRESULT result;
SInt32 score;
UInt8 interfaceNumEndpoints;
- UInt8 endpoint;
UInt8 configuration;
// Placing the constant KIOUSBFindInterfaceDontCare into the following
@@ -121,7 +121,7 @@
result = (*plugInInterface)->QueryInterface(
plugInInterface,
CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
- (LPVOID) &interface);
+ (LPVOID*) &interface);
// No longer need the intermediate plugin
(*plugInInterface)->Release(plugInInterface);
@@ -181,7 +181,7 @@
// Iterate over the endpoints for this interface and see if there
// are any that do bulk in/out.
- for (endpoint = 0; endpoint <= interfaceNumEndpoints; endpoint++) {
+ for (UInt8 endpoint = 1; endpoint <= interfaceNumEndpoints; endpoint++) {
UInt8 transferType;
UInt16 maxPacketSize;
UInt8 interval;
@@ -209,7 +209,7 @@
handle->zero_mask = maxPacketSize - 1;
}
} else {
- ERR("could not get pipe properties\n");
+ ERR("could not get pipe properties for endpoint %u (%08x)\n", endpoint, kr);
}
if (handle->info.has_bulk_in && handle->info.has_bulk_out) {
@@ -279,7 +279,7 @@
// Now create the device interface.
result = (*plugin)->QueryInterface(plugin,
- CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID) &dev);
+ CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &dev);
if ((result != 0) || (dev == NULL)) {
ERR("Couldn't create a device interface (%08x)\n", (int) result);
goto error;
@@ -293,6 +293,13 @@
// So, we have a device, finally. Grab its vitals.
+
+ kr = (*dev)->USBDeviceOpen(dev);
+ if (kr != 0) {
+ WARN("USBDeviceOpen");
+ goto out;
+ }
+
kr = (*dev)->GetDeviceVendor(dev, &handle->info.dev_vendor);
if (kr != 0) {
ERR("GetDeviceVendor");
@@ -365,12 +372,16 @@
goto error;
}
+ out:
+
+ (*dev)->USBDeviceClose(dev);
(*dev)->Release(dev);
return 0;
error:
if (dev != NULL) {
+ (*dev)->USBDeviceClose(dev);
(*dev)->Release(dev);
}
@@ -432,7 +443,7 @@
}
if (h.success) {
- *handle = calloc(1, sizeof(usb_handle));
+ *handle = reinterpret_cast<usb_handle*>(calloc(1, sizeof(usb_handle)));
memcpy(*handle, &h, sizeof(usb_handle));
ret = 0;
break;
diff --git a/fastboot/usb_windows.c b/fastboot/usb_windows.cpp
similarity index 100%
rename from fastboot/usb_windows.c
rename to fastboot/usb_windows.cpp
diff --git a/fastboot/usbtest.c b/fastboot/usbtest.cpp
similarity index 100%
rename from fastboot/usbtest.c
rename to fastboot/usbtest.cpp
diff --git a/fastboot/util.c b/fastboot/util.cpp
similarity index 100%
rename from fastboot/util.c
rename to fastboot/util.cpp
diff --git a/fastboot/util_linux.c b/fastboot/util_linux.cpp
similarity index 98%
rename from fastboot/util_linux.c
rename to fastboot/util_linux.cpp
index 91c3776..b788199 100644
--- a/fastboot/util_linux.c
+++ b/fastboot/util_linux.cpp
@@ -26,6 +26,8 @@
* SUCH DAMAGE.
*/
+#include "fastboot.h"
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/fastboot/util_osx.c b/fastboot/util_osx.cpp
similarity index 98%
rename from fastboot/util_osx.c
rename to fastboot/util_osx.cpp
index e718562..ae0b024 100644
--- a/fastboot/util_osx.c
+++ b/fastboot/util_osx.cpp
@@ -26,6 +26,8 @@
* SUCH DAMAGE.
*/
+#include "fastboot.h"
+
#import <Carbon/Carbon.h>
#include <unistd.h>
diff --git a/fastboot/util_windows.c b/fastboot/util_windows.cpp
similarity index 98%
rename from fastboot/util_windows.c
rename to fastboot/util_windows.cpp
index 74a5c27..ec52f39 100644
--- a/fastboot/util_windows.c
+++ b/fastboot/util_windows.cpp
@@ -26,6 +26,8 @@
* SUCH DAMAGE.
*/
+#include "fastboot.h"
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c
index bb2bed2..044c895 100644
--- a/fs_mgr/fs_mgr.c
+++ b/fs_mgr/fs_mgr.c
@@ -95,7 +95,7 @@
int status;
int ret;
long tmpmnt_flags = MS_NOATIME | MS_NOEXEC | MS_NOSUID;
- char *tmpmnt_opts = "nomblk_io_submit,errors=remount-ro";
+ char tmpmnt_opts[64] = "errors=remount-ro";
char *e2fsck_argv[] = {
E2FSCK_BIN,
"-y",
@@ -118,6 +118,10 @@
* fix the filesystem.
*/
errno = 0;
+ if (!strcmp(fs_type, "ext4")) {
+ // This option is only valid with ext4
+ strlcat(tmpmnt_opts, ",nomblk_io_submit", sizeof(tmpmnt_opts));
+ }
ret = mount(blk_device, target, fs_type, tmpmnt_flags, tmpmnt_opts);
INFO("%s(): mount(%s,%s,%s)=%d: %s\n",
__func__, blk_device, target, fs_type, ret, strerror(errno));
@@ -147,8 +151,8 @@
INFO("Running %s on %s\n", E2FSCK_BIN, blk_device);
ret = android_fork_execvp_ext(ARRAY_SIZE(e2fsck_argv), e2fsck_argv,
- &status, true, LOG_KLOG | LOG_FILE,
- true, FSCK_LOG_FILE);
+ &status, true, LOG_KLOG | LOG_FILE,
+ true, FSCK_LOG_FILE, NULL, 0);
if (ret < 0) {
/* No need to check for error in fork, we can't really handle it now */
@@ -158,14 +162,14 @@
} 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,
- true, FSCK_LOG_FILE);
+ true, FSCK_LOG_FILE, NULL, 0);
if (ret < 0) {
/* No need to check for error in fork, we can't really handle it now */
ERROR("Failed trying to run %s\n", F2FS_FSCK_BIN);
@@ -520,6 +524,14 @@
continue;
}
+ /* Skip mounting the root partition, as it will already have been mounted */
+ if (!strcmp(fstab->recs[i].mount_point, "/")) {
+ if ((fstab->recs[i].fs_mgr_flags & MS_RDONLY) != 0) {
+ fs_mgr_set_blk_ro(fstab->recs[i].blk_device);
+ }
+ continue;
+ }
+
/* Translate LABEL= file system labels into block devices */
if (!strcmp(fstab->recs[i].fs_type, "ext2") ||
!strcmp(fstab->recs[i].fs_type, "ext3") ||
@@ -783,7 +795,8 @@
/* Initialize the swap area */
mkswap_argv[1] = fstab->recs[i].blk_device;
err = android_fork_execvp_ext(ARRAY_SIZE(mkswap_argv), mkswap_argv,
- &status, true, LOG_KLOG, false, NULL);
+ &status, true, LOG_KLOG, false, NULL,
+ NULL, 0);
if (err) {
ERROR("mkswap failed for %s\n", fstab->recs[i].blk_device);
ret = -1;
diff --git a/fs_mgr/fs_mgr_verity.c b/fs_mgr/fs_mgr_verity.c
index 81c7c9a..a4a99c3 100644
--- a/fs_mgr/fs_mgr_verity.c
+++ b/fs_mgr/fs_mgr_verity.c
@@ -767,8 +767,24 @@
static int load_verity_state(struct fstab_rec *fstab, int *mode)
{
- off64_t offset = 0;
+ char propbuf[PROPERTY_VALUE_MAX];
int match = 0;
+ off64_t offset = 0;
+
+ /* use the kernel parameter if set */
+ property_get("ro.boot.veritymode", propbuf, "");
+
+ if (*propbuf != '\0') {
+ if (!strcmp(propbuf, "enforcing")) {
+ *mode = VERITY_MODE_DEFAULT;
+ return 0;
+ } else if (!strcmp(propbuf, "logging")) {
+ *mode = VERITY_MODE_LOGGING;
+ return 0;
+ } else {
+ INFO("Unknown value %s for veritymode; ignoring", propbuf);
+ }
+ }
if (get_verity_state_offset(fstab, &offset) < 0) {
/* fall back to stateless behavior */
@@ -843,6 +859,7 @@
int fs_mgr_update_verity_state(fs_mgr_verity_state_callback callback)
{
_Alignas(struct dm_ioctl) char buffer[DM_BUF_SIZE];
+ bool use_state = true;
char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)];
char *mount_point;
char propbuf[PROPERTY_VALUE_MAX];
@@ -855,6 +872,16 @@
struct dm_ioctl *io = (struct dm_ioctl *) buffer;
struct fstab *fstab = NULL;
+ /* check if we need to store the state */
+ property_get("ro.boot.veritymode", propbuf, "");
+
+ if (*propbuf != '\0') {
+ if (fs_mgr_load_verity_state(&mode) == -1) {
+ return -1;
+ }
+ use_state = false; /* state is kept by the bootloader */
+ }
+
fd = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC));
if (fd == -1) {
@@ -877,9 +904,11 @@
continue;
}
- if (get_verity_state_offset(&fstab->recs[i], &offset) < 0 ||
- read_verity_state(fstab->recs[i].verity_loc, offset, &mode) < 0) {
- continue;
+ if (use_state) {
+ if (get_verity_state_offset(&fstab->recs[i], &offset) < 0 ||
+ read_verity_state(fstab->recs[i].verity_loc, offset, &mode) < 0) {
+ continue;
+ }
}
mount_point = basename(fstab->recs[i].mount_point);
@@ -893,7 +922,7 @@
status = &buffer[io->data_start + sizeof(struct dm_target_spec)];
- if (*status == 'C') {
+ if (use_state && *status == 'C') {
if (write_verity_state(fstab->recs[i].verity_loc, offset,
VERITY_MODE_LOGGING) < 0) {
continue;
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/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 7ea8250..ed773d0 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -24,11 +24,13 @@
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/types.h>
#include <unistd.h>
+
#include <batteryservice/BatteryService.h>
#include <cutils/klog.h>
#include <cutils/properties.h>
-#include <sys/types.h>
+#include <log/log_read.h>
#include <utils/Errors.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -265,10 +267,32 @@
"battery none");
}
- KLOG_WARNING(LOG_TAG, "%s chg=%s%s%s\n", dmesgline,
- props.chargerAcOnline ? "a" : "",
- props.chargerUsbOnline ? "u" : "",
- props.chargerWirelessOnline ? "w" : "");
+ size_t len = strlen(dmesgline);
+ snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s",
+ props.chargerAcOnline ? "a" : "",
+ props.chargerUsbOnline ? "u" : "",
+ props.chargerWirelessOnline ? "w" : "");
+
+ log_time realtime(CLOCK_REALTIME);
+ time_t t = realtime.tv_sec;
+ struct tm *tmp = gmtime(&t);
+ if (tmp) {
+ static const char fmt[] = " %Y-%m-%d %H:%M:%S.XXXXXXXXX UTC";
+ len = strlen(dmesgline);
+ if ((len < (sizeof(dmesgline) - sizeof(fmt) - 8)) // margin
+ && strftime(dmesgline + len, sizeof(dmesgline) - len,
+ fmt, tmp)) {
+ char *usec = strchr(dmesgline + len, 'X');
+ if (usec) {
+ len = usec - dmesgline;
+ snprintf(dmesgline + len, sizeof(dmesgline) - len,
+ "%09u", realtime.tv_nsec);
+ usec[9] = ' ';
+ }
+ }
+ }
+
+ KLOG_WARNING(LOG_TAG, "%s\n", dmesgline);
}
healthd_mode_ops->battery_update(&props);
diff --git a/include/backtrace/BacktraceMap.h b/include/backtrace/BacktraceMap.h
index 784bc03..bb18aa2 100644
--- a/include/backtrace/BacktraceMap.h
+++ b/include/backtrace/BacktraceMap.h
@@ -33,13 +33,11 @@
#include <string>
struct backtrace_map_t {
- backtrace_map_t(): start(0), end(0), flags(0) {}
-
- uintptr_t start;
- uintptr_t end;
- uintptr_t offset;
- uintptr_t load_base;
- int flags;
+ uintptr_t start = 0;
+ uintptr_t end = 0;
+ uintptr_t offset = 0;
+ uintptr_t load_base = 0;
+ int flags = 0;
std::string name;
};
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..2d3c743 100644
--- a/include/cutils/sockets.h
+++ b/include/cutils/sockets.h
@@ -23,7 +23,7 @@
#include <string.h>
#include <stdbool.h>
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
#include <winsock2.h>
typedef int socklen_t;
#else
@@ -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/cutils/trace.h b/include/cutils/trace.h
index e4ed179..6d9b3bc 100644
--- a/include/cutils/trace.h
+++ b/include/cutils/trace.h
@@ -67,7 +67,8 @@
#define ATRACE_TAG_RS (1<<15)
#define ATRACE_TAG_BIONIC (1<<16)
#define ATRACE_TAG_POWER (1<<17)
-#define ATRACE_TAG_LAST ATRACE_TAG_POWER
+#define ATRACE_TAG_PACKAGE_MANAGER (1<<18)
+#define ATRACE_TAG_LAST ATRACE_TAG_PACKAGE_MANAGER
// Reserved for initialization.
#define ATRACE_TAG_NOT_READY (1LL<<63)
diff --git a/include/log/logprint.h b/include/log/logprint.h
index 96249e9..4b812cc 100644
--- a/include/log/logprint.h
+++ b/include/log/logprint.h
@@ -36,9 +36,10 @@
FORMAT_TIME,
FORMAT_THREADTIME,
FORMAT_LONG,
- /* The following two are modifiers to above formats */
+ /* The following three are modifiers to above formats */
FORMAT_MODIFIER_COLOR, /* converts priority to color */
FORMAT_MODIFIER_TIME_USEC, /* switches from msec to usec time precision */
+ FORMAT_MODIFIER_PRINTABLE, /* converts non-printable to printable escapes */
} AndroidLogPrintFormat;
typedef struct AndroidLogFormat_t AndroidLogFormat;
diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h
index 02fe2b5..7047e0f 100644
--- a/include/private/android_filesystem_config.h
+++ b/include/private/android_filesystem_config.h
@@ -26,7 +26,7 @@
#include <sys/types.h>
#include <stdint.h>
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
#include <linux/capability.h>
#else
#include "android_filesystem_capability.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 */
@@ -98,6 +99,10 @@
#define AID_NET_BW_ACCT 3007 /* change bandwidth statistics accounting */
#define AID_NET_BT_STACK 3008 /* bluetooth: access config files */
+/* The range 5000-5999 is also reserved for OEM, and must never be used here. */
+#define AID_OEM_RESERVED_2_START 5000
+#define AID_OEM_RESERVED_2_END 5999
+
#define AID_EVERYBODY 9997 /* shared between all apps in the same profile */
#define AID_MISC 9998 /* access to misc storage */
#define AID_NOBODY 9999
@@ -168,6 +173,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/include/utils/AndroidThreads.h b/include/utils/AndroidThreads.h
index aad1e82..4c2dd49 100644
--- a/include/utils/AndroidThreads.h
+++ b/include/utils/AndroidThreads.h
@@ -73,7 +73,7 @@
// ------------------------------------------------------------------
// Extra functions working with raw pids.
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
// Change the priority AND scheduling group of a particular thread. The priority
// should be one of the ANDROID_PRIORITY constants. Returns INVALID_OPERATION
// if the priority set failed, else another value if just the group set failed;
diff --git a/include/utils/ByteOrder.h b/include/utils/ByteOrder.h
index baa3a83..44ea13d 100644
--- a/include/utils/ByteOrder.h
+++ b/include/utils/ByteOrder.h
@@ -21,7 +21,7 @@
#include <stdint.h>
#include <sys/types.h>
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
#include <winsock2.h>
#else
#include <netinet/in.h>
diff --git a/include/utils/Compat.h b/include/utils/Compat.h
index 7d96310..c5f9d6a 100644
--- a/include/utils/Compat.h
+++ b/include/utils/Compat.h
@@ -75,4 +75,10 @@
_rc; })
#endif
+#if defined(_WIN32)
+#define OS_PATH_SEPARATOR '\\'
+#else
+#define OS_PATH_SEPARATOR '/'
+#endif
+
#endif /* __LIB_UTILS_COMPAT_H */
diff --git a/include/utils/Errors.h b/include/utils/Errors.h
index 46173db..08ddcd2 100644
--- a/include/utils/Errors.h
+++ b/include/utils/Errors.h
@@ -23,7 +23,7 @@
namespace android {
// use this type to return error codes
-#ifdef HAVE_MS_C_RUNTIME
+#ifdef _WIN32
typedef int status_t;
#else
typedef int32_t status_t;
@@ -58,8 +58,7 @@
ALREADY_EXISTS = -EEXIST,
DEAD_OBJECT = -EPIPE,
FAILED_TRANSACTION = (UNKNOWN_ERROR + 2),
- JPARKS_BROKE_IT = -EPIPE,
-#if !defined(HAVE_MS_C_RUNTIME)
+#if !defined(_WIN32)
BAD_INDEX = -EOVERFLOW,
NOT_ENOUGH_DATA = -ENODATA,
WOULD_BLOCK = -EWOULDBLOCK,
diff --git a/include/utils/FileMap.h b/include/utils/FileMap.h
index f70fc92..afd7bfd 100644
--- a/include/utils/FileMap.h
+++ b/include/utils/FileMap.h
@@ -26,7 +26,7 @@
#if defined(__MINGW32__)
// Ensure that we always pull in winsock2.h before windows.h
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
#include <winsock2.h>
#endif
#include <windows.h>
diff --git a/include/utils/JenkinsHash.h b/include/utils/JenkinsHash.h
index 7da5dbd..027c10c 100644
--- a/include/utils/JenkinsHash.h
+++ b/include/utils/JenkinsHash.h
@@ -29,6 +29,9 @@
/* The Jenkins hash of a sequence of 32 bit words A, B, C is:
* Whiten(Mix(Mix(Mix(0, A), B), C)) */
+#ifdef __clang__
+__attribute__((no_sanitize("integer")))
+#endif
inline uint32_t JenkinsHashMix(uint32_t hash, uint32_t data) {
hash += data;
hash += (hash << 10);
diff --git a/include/utils/Mutex.h b/include/utils/Mutex.h
index 757519b..f027c79 100644
--- a/include/utils/Mutex.h
+++ b/include/utils/Mutex.h
@@ -59,7 +59,7 @@
// lock if possible; returns 0 on success, error otherwise
status_t tryLock();
-#if HAVE_ANDROID_OS
+#if defined(__ANDROID__)
// lock the mutex, but don't wait longer than timeoutMilliseconds.
// Returns 0 on success, TIMED_OUT for failure due to timeout expiration.
//
@@ -128,7 +128,7 @@
inline status_t Mutex::tryLock() {
return -pthread_mutex_trylock(&mMutex);
}
-#if HAVE_ANDROID_OS
+#if defined(__ANDROID__)
inline status_t Mutex::timedLock(nsecs_t timeoutNs) {
const struct timespec ts = {
/* .tv_sec = */ static_cast<time_t>(timeoutNs / 1000000000),
diff --git a/include/utils/Thread.h b/include/utils/Thread.h
index 28839fd..1532b7e 100644
--- a/include/utils/Thread.h
+++ b/include/utils/Thread.h
@@ -70,7 +70,7 @@
// Indicates whether this thread is running or not.
bool isRunning() const;
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
// Return the thread's kernel ID, same as the thread itself calling gettid(),
// or -1 if the thread is not running.
pid_t getTid() const;
@@ -101,7 +101,7 @@
volatile bool mExitPending;
volatile bool mRunning;
sp<Thread> mHoldSelf;
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
// legacy for debugging, not used by getTid() as it is set by the child thread
// and so is not initialized until the child reaches that point
pid_t mTid;
diff --git a/include/utils/Trace.h b/include/utils/Trace.h
index 6ee343d..6ba68f6 100644
--- a/include/utils/Trace.h
+++ b/include/utils/Trace.h
@@ -17,7 +17,7 @@
#ifndef ANDROID_TRACE_H
#define ANDROID_TRACE_H
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
#include <fcntl.h>
#include <stdint.h>
@@ -59,11 +59,11 @@
}; // namespace android
-#else // HAVE_ANDROID_OS
+#else // !__ANDROID__
#define ATRACE_NAME(...)
#define ATRACE_CALL()
-#endif // HAVE_ANDROID_OS
+#endif // __ANDROID__
#endif // ANDROID_TRACE_H
diff --git a/include/utils/TypeHelpers.h b/include/utils/TypeHelpers.h
index 13c9081..61d618e 100644
--- a/include/utils/TypeHelpers.h
+++ b/include/utils/TypeHelpers.h
@@ -131,7 +131,8 @@
template<typename TYPE> inline
void construct_type(TYPE* p, size_t n) {
if (!traits<TYPE>::has_trivial_ctor) {
- while (n--) {
+ while (n > 0) {
+ n--;
new(p++) TYPE;
}
}
@@ -140,7 +141,8 @@
template<typename TYPE> inline
void destroy_type(TYPE* p, size_t n) {
if (!traits<TYPE>::has_trivial_dtor) {
- while (n--) {
+ while (n > 0) {
+ n--;
p->~TYPE();
p++;
}
@@ -150,7 +152,8 @@
template<typename TYPE> inline
void copy_type(TYPE* d, const TYPE* s, size_t n) {
if (!traits<TYPE>::has_trivial_copy) {
- while (n--) {
+ while (n > 0) {
+ n--;
new(d) TYPE(*s);
d++, s++;
}
@@ -162,12 +165,14 @@
template<typename TYPE> inline
void splat_type(TYPE* where, const TYPE* what, size_t n) {
if (!traits<TYPE>::has_trivial_copy) {
- while (n--) {
+ while (n > 0) {
+ n--;
new(where) TYPE(*what);
where++;
}
} else {
- while (n--) {
+ while (n > 0) {
+ n--;
*where++ = *what;
}
}
@@ -182,7 +187,8 @@
} else {
d += n;
s += n;
- while (n--) {
+ while (n > 0) {
+ n--;
--d, --s;
if (!traits<TYPE>::has_trivial_copy) {
new(d) TYPE(*s);
@@ -203,7 +209,8 @@
{
memmove(d,s,n*sizeof(TYPE));
} else {
- while (n--) {
+ while (n > 0) {
+ n--;
if (!traits<TYPE>::has_trivial_copy) {
new(d) TYPE(*s);
} else {
diff --git a/include/ziparchive/zip_archive.h b/include/ziparchive/zip_archive.h
index 386a390..3591a6b 100644
--- a/include/ziparchive/zip_archive.h
+++ b/include/ziparchive/zip_archive.h
@@ -22,6 +22,7 @@
#include <stdint.h>
#include <string.h>
+#include <sys/cdefs.h>
#include <sys/types.h>
#include <utils/Compat.h>
@@ -33,17 +34,33 @@
kCompressDeflated = 8, // standard deflate
};
-struct ZipEntryName {
+struct ZipString {
const uint8_t* name;
uint16_t name_length;
- ZipEntryName() {}
+ ZipString() {}
/*
* entry_name has to be an c-style string with only ASCII characters.
*/
- explicit ZipEntryName(const char* entry_name)
+ explicit ZipString(const char* entry_name)
: name(reinterpret_cast<const uint8_t*>(entry_name)), name_length(strlen(entry_name)) {}
+
+ bool operator==(const ZipString& rhs) const {
+ return name && (name_length == rhs.name_length) &&
+ (memcmp(name, rhs.name, name_length) == 0);
+ }
+
+ bool StartsWith(const ZipString& prefix) const {
+ return name && (name_length >= prefix.name_length) &&
+ (memcmp(name, prefix.name, prefix.name_length) == 0);
+ }
+
+ bool EndsWith(const ZipString& suffix) const {
+ return name && (name_length >= suffix.name_length) &&
+ (memcmp(name + name_length - suffix.name_length, suffix.name,
+ suffix.name_length) == 0);
+ }
};
/*
@@ -136,7 +153,7 @@
* and length, a call to VerifyCrcAndLengths must be made after entry data
* has been processed.
*/
-int32_t FindEntry(const ZipArchiveHandle handle, const ZipEntryName& entryName,
+int32_t FindEntry(const ZipArchiveHandle handle, const ZipString& entryName,
ZipEntry* data);
/*
@@ -147,13 +164,14 @@
* calls to Next. All calls to StartIteration must be matched by a call to
* EndIteration to free any allocated memory.
*
- * This method also accepts an optional prefix to restrict iteration to
- * entry names that start with |optional_prefix|.
+ * This method also accepts optional prefix and suffix to restrict iteration to
+ * entry names that start with |optional_prefix| or end with |optional_suffix|.
*
* Returns 0 on success and negative values on failure.
*/
int32_t StartIteration(ZipArchiveHandle handle, void** cookie_ptr,
- const ZipEntryName* optional_prefix);
+ const ZipString* optional_prefix,
+ const ZipString* optional_suffix);
/*
* Advance to the next element in the zipfile in iteration order.
@@ -161,7 +179,7 @@
* Returns 0 on success, -1 if there are no more elements in this
* archive and lower negative values on failure.
*/
-int32_t Next(void* cookie, ZipEntry* data, ZipEntryName *name);
+int32_t Next(void* cookie, ZipEntry* data, ZipString* name);
/*
* End iteration over all entries of a zip file and frees the memory allocated
diff --git a/init/Android.mk b/init/Android.mk
index b14f9b5..58bff58 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -18,21 +18,43 @@
-Wno-unused-parameter \
-Werror \
-init_clang := true
-
# --
+# If building on Linux, then build unit test for the host.
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CPPFLAGS := $(init_cflags)
+LOCAL_SRC_FILES:= \
+ parser/tokenizer.cpp \
+
+LOCAL_MODULE := libinit_parser
+LOCAL_CLANG := true
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := init_parser_tests
+LOCAL_SRC_FILES := \
+ parser/tokenizer_test.cpp \
+
+LOCAL_STATIC_LIBRARIES := libinit_parser
+LOCAL_CLANG := true
+include $(BUILD_HOST_NATIVE_TEST)
+endif
+
include $(CLEAR_VARS)
LOCAL_CPPFLAGS := $(init_cflags)
LOCAL_SRC_FILES:= \
+ action.cpp \
init_parser.cpp \
log.cpp \
parser.cpp \
+ service.cpp \
util.cpp \
LOCAL_STATIC_LIBRARIES := libbase
LOCAL_MODULE := libinit
-LOCAL_CLANG := $(init_clang)
+LOCAL_SANITIZE := integer
+LOCAL_CLANG := true
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
@@ -79,7 +101,8 @@
ln -sf ../init $(TARGET_ROOT_OUT)/sbin/ueventd; \
ln -sf ../init $(TARGET_ROOT_OUT)/sbin/watchdogd
-LOCAL_CLANG := $(init_clang)
+LOCAL_SANITIZE := integer
+LOCAL_CLANG := true
include $(BUILD_EXECUTABLE)
@@ -96,5 +119,6 @@
libbase \
LOCAL_STATIC_LIBRARIES := libinit
-LOCAL_CLANG := $(init_clang)
+LOCAL_SANITIZE := integer
+LOCAL_CLANG := true
include $(BUILD_NATIVE_TEST)
diff --git a/init/action.cpp b/init/action.cpp
new file mode 100644
index 0000000..dd366d3
--- /dev/null
+++ b/init/action.cpp
@@ -0,0 +1,412 @@
+/*
+ * 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.
+ */
+
+#include "action.h"
+
+#include <errno.h>
+
+#include <base/strings.h>
+#include <base/stringprintf.h>
+
+#include "error.h"
+#include "init_parser.h"
+#include "log.h"
+#include "property_service.h"
+#include "util.h"
+
+class Action::Command
+{
+public:
+ Command(int (*f)(const std::vector<std::string>& args),
+ const std::vector<std::string>& args,
+ const std::string& filename,
+ int line);
+
+ int InvokeFunc() const;
+ std::string BuildCommandString() const;
+ std::string BuildSourceString() const;
+
+private:
+ int (*func_)(const std::vector<std::string>& args);
+ const std::vector<std::string> args_;
+ const std::string filename_;
+ int line_;
+};
+
+Action::Command::Command(int (*f)(const std::vector<std::string>& args),
+ const std::vector<std::string>& args,
+ const std::string& filename,
+ int line) :
+ func_(f), args_(args), filename_(filename), line_(line)
+{
+}
+
+int Action::Command::InvokeFunc() const
+{
+ std::vector<std::string> expanded_args;
+ expanded_args.resize(args_.size());
+ expanded_args[0] = args_[0];
+ for (std::size_t i = 1; i < args_.size(); ++i) {
+ if (expand_props(args_[i], &expanded_args[i]) == -1) {
+ ERROR("%s: cannot expand '%s'\n", args_[0].c_str(), args_[i].c_str());
+ return -EINVAL;
+ }
+ }
+
+ return func_(expanded_args);
+}
+
+std::string Action::Command::BuildCommandString() const
+{
+ return android::base::Join(args_, ' ');
+}
+
+std::string Action::Command::BuildSourceString() const
+{
+ if (!filename_.empty()) {
+ return android::base::StringPrintf(" (%s:%d)", filename_.c_str(), line_);
+ } else {
+ return std::string();
+ }
+}
+
+Action::Action()
+{
+}
+
+void Action::AddCommand(int (*f)(const std::vector<std::string>& args),
+ const std::vector<std::string>& args,
+ const std::string& filename, int line)
+{
+ Action::Command* cmd = new Action::Command(f, args, filename, line);
+ commands_.push_back(cmd);
+}
+
+std::size_t Action::NumCommands() const
+{
+ return commands_.size();
+}
+
+void Action::ExecuteOneCommand(std::size_t command) const
+{
+ ExecuteCommand(*commands_[command]);
+}
+
+void Action::ExecuteAllCommands() const
+{
+ for (const auto& c : commands_) {
+ ExecuteCommand(*c);
+ }
+}
+
+void Action::ExecuteCommand(const Command& command) const
+{
+ Timer t;
+ int result = command.InvokeFunc();
+
+ if (klog_get_level() >= KLOG_INFO_LEVEL) {
+ std::string trigger_name = BuildTriggersString();
+ std::string cmd_str = command.BuildCommandString();
+ std::string source = command.BuildSourceString();
+
+ INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
+ cmd_str.c_str(), trigger_name.c_str(), source.c_str(),
+ result, t.duration());
+ }
+}
+
+bool Action::ParsePropertyTrigger(const std::string& trigger, std::string* err)
+{
+ const static std::string prop_str("property:");
+ std::string prop_name(trigger.substr(prop_str.length()));
+ size_t equal_pos = prop_name.find('=');
+ if (equal_pos == std::string::npos) {
+ *err = "property trigger found without matching '='";
+ return false;
+ }
+
+ std::string prop_value(prop_name.substr(equal_pos + 1));
+ prop_name.erase(equal_pos);
+
+ auto res = property_triggers_.emplace(prop_name, prop_value);
+ if (res.second == false) {
+ *err = "multiple property triggers found for same property";
+ return false;
+ }
+ return true;
+}
+
+bool Action::InitTriggers(const std::vector<std::string>& args, std::string* err)
+{
+ const static std::string prop_str("property:");
+ for (std::size_t i = 0; i < args.size(); ++i) {
+ if (i % 2) {
+ if (args[i] != "&&") {
+ *err = "&& is the only symbol allowed to concatenate actions";
+ return false;
+ } else {
+ continue;
+ }
+ }
+
+ if (!args[i].compare(0, prop_str.length(), prop_str)) {
+ if (!ParsePropertyTrigger(args[i], err)) {
+ return false;
+ }
+ } else {
+ if (!event_trigger_.empty()) {
+ *err = "multiple event triggers are not allowed";
+ return false;
+ }
+
+ event_trigger_ = args[i];
+ }
+ }
+
+ return true;
+}
+
+bool Action::InitSingleTrigger(const std::string& trigger)
+{
+ std::vector<std::string> name_vector{trigger};
+ std::string err;
+ return InitTriggers(name_vector, &err);
+}
+
+bool Action::CheckPropertyTriggers(const std::string& name,
+ const std::string& value) const
+{
+ bool found = name.empty();
+ if (property_triggers_.empty()) {
+ return true;
+ }
+
+ for (const auto& t : property_triggers_) {
+ const auto& trigger_name = t.first;
+ const auto& trigger_value = t.second;
+ if (trigger_name == name) {
+ if (trigger_value != "*" && trigger_value != value) {
+ return false;
+ } else {
+ found = true;
+ }
+ } else {
+ std::string prop_val = property_get(trigger_name.c_str());
+ if (prop_val.empty() || (trigger_value != "*" &&
+ trigger_value != prop_val)) {
+ return false;
+ }
+ }
+ }
+ return found;
+}
+
+bool Action::CheckEventTrigger(const std::string& trigger) const
+{
+ return !event_trigger_.empty() &&
+ trigger == event_trigger_ &&
+ CheckPropertyTriggers();
+}
+
+bool Action::CheckPropertyTrigger(const std::string& name,
+ const std::string& value) const
+{
+ return event_trigger_.empty() && CheckPropertyTriggers(name, value);
+}
+
+bool Action::TriggersEqual(const class Action& other) const
+{
+ return property_triggers_ == other.property_triggers_ &&
+ event_trigger_ == other.event_trigger_;
+}
+
+std::string Action::BuildTriggersString() const
+{
+ std::string result;
+
+ for (const auto& t : property_triggers_) {
+ result += t.first;
+ result += '=';
+ result += t.second;
+ result += ' ';
+ }
+ if (!event_trigger_.empty()) {
+ result += event_trigger_;
+ result += ' ';
+ }
+ result.pop_back();
+ return result;
+}
+
+void Action::DumpState() const
+{
+ std::string trigger_name = BuildTriggersString();
+ INFO("on %s\n", trigger_name.c_str());
+
+ for (const auto& c : commands_) {
+ std::string cmd_str = c->BuildCommandString();
+ INFO(" %s\n", cmd_str.c_str());
+ }
+ INFO("\n");
+}
+
+
+class EventTrigger : public Trigger {
+public:
+ EventTrigger(const std::string& trigger) : trigger_(trigger) {
+ }
+ bool CheckTriggers(const Action* action) override {
+ return action->CheckEventTrigger(trigger_);
+ }
+private:
+ std::string trigger_;
+};
+
+class PropertyTrigger : public Trigger {
+public:
+ PropertyTrigger(const std::string& name, const std::string& value)
+ : name_(name), value_(value) {
+ }
+ bool CheckTriggers(const Action* action) override {
+ return action->CheckPropertyTrigger(name_, value_);
+ }
+private:
+ std::string name_;
+ std::string value_;
+};
+
+class BuiltinTrigger : public Trigger {
+public:
+ BuiltinTrigger(Action* action) : action_(action) {
+ }
+ bool CheckTriggers(const Action* action) override {
+ return action == action_;
+ }
+private:
+ Action* action_;
+};
+
+ActionManager::ActionManager() : current_command_(0)
+{
+}
+
+ActionManager& ActionManager::GetInstance() {
+ static ActionManager instance;
+ return instance;
+}
+
+void ActionManager::QueueEventTrigger(const std::string& trigger)
+{
+ trigger_queue_.push(std::make_unique<EventTrigger>(trigger));
+}
+
+void ActionManager::QueuePropertyTrigger(const std::string& name,
+ const std::string& value)
+{
+ trigger_queue_.push(std::make_unique<PropertyTrigger>(name, value));
+}
+
+void ActionManager::QueueAllPropertyTriggers()
+{
+ QueuePropertyTrigger("", "");
+}
+
+void ActionManager::QueueBuiltinAction(int (*func)(const std::vector<std::string>& args),
+ const std::string& name)
+{
+ Action* act = new Action();
+ std::vector<std::string> name_vector{name};
+
+ if (!act->InitSingleTrigger(name)) {
+ return;
+ }
+
+ act->AddCommand(func, name_vector);
+
+ actions_.push_back(act);
+ trigger_queue_.push(std::make_unique<BuiltinTrigger>(act));
+}
+
+void ActionManager::ExecuteOneCommand() {
+ while (current_executing_actions_.empty() && !trigger_queue_.empty()) {
+ std::copy_if(actions_.begin(), actions_.end(),
+ std::back_inserter(current_executing_actions_),
+ [this] (Action* act) {
+ return trigger_queue_.front()->CheckTriggers(act);
+ });
+ trigger_queue_.pop();
+ }
+
+ if (current_executing_actions_.empty()) {
+ return;
+ }
+
+ Action* action = current_executing_actions_.back();
+ if (!action->NumCommands()) {
+ current_executing_actions_.pop_back();
+ return;
+ }
+
+ if (current_command_ == 0) {
+ std::string trigger_name = action->BuildTriggersString();
+ INFO("processing action %p (%s)\n", action, trigger_name.c_str());
+ }
+
+ action->ExecuteOneCommand(current_command_++);
+ if (current_command_ == action->NumCommands()) {
+ current_command_ = 0;
+ current_executing_actions_.pop_back();
+ }
+}
+
+bool ActionManager::HasMoreCommands() const
+{
+ return !current_executing_actions_.empty() || !trigger_queue_.empty();
+}
+
+Action* ActionManager::AddNewAction(const std::vector<std::string>& triggers,
+ std::string* err)
+{
+ if (triggers.size() < 1) {
+ *err = "actions must have a trigger\n";
+ return nullptr;
+ }
+
+ Action* act = new Action();
+ if (!act->InitTriggers(triggers, err)) {
+ return nullptr;
+ }
+
+ auto old_act_it =
+ std::find_if(actions_.begin(), actions_.end(),
+ [&act] (Action* a) { return act->TriggersEqual(*a); });
+
+ if (old_act_it != actions_.end()) {
+ delete act;
+ return *old_act_it;
+ }
+
+ actions_.push_back(act);
+ return act;
+}
+
+void ActionManager::DumpState() const
+{
+ for (const auto& a : actions_) {
+ a->DumpState();
+ }
+ INFO("\n");
+}
diff --git a/init/action.h b/init/action.h
new file mode 100644
index 0000000..5088c71
--- /dev/null
+++ b/init/action.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#ifndef _INIT_ACTION_H
+#define _INIT_ACTION_H
+
+#include <map>
+#include <queue>
+#include <string>
+#include <vector>
+
+class Action {
+public:
+ Action();
+
+ void AddCommand(int (*f)(const std::vector<std::string>& args),
+ const std::vector<std::string>& args,
+ const std::string& filename = "", int line = 0);
+ bool InitTriggers(const std::vector<std::string>& args, std::string* err);
+ bool InitSingleTrigger(const std::string& trigger);
+ std::size_t NumCommands() const;
+ void ExecuteOneCommand(std::size_t command) const;
+ void ExecuteAllCommands() const;
+ bool CheckEventTrigger(const std::string& trigger) const;
+ bool CheckPropertyTrigger(const std::string& name,
+ const std::string& value) const;
+ bool TriggersEqual(const class Action& other) const;
+ std::string BuildTriggersString() const;
+ void DumpState() const;
+
+private:
+ class Command;
+
+ void ExecuteCommand(const Command& command) const;
+ bool CheckPropertyTriggers(const std::string& name = "",
+ const std::string& value = "") const;
+ bool ParsePropertyTrigger(const std::string& trigger, std::string* err);
+
+ std::map<std::string, std::string> property_triggers_;
+ std::string event_trigger_;
+ std::vector<Command*> commands_;
+};
+
+class Trigger {
+public:
+ virtual ~Trigger() { }
+ virtual bool CheckTriggers(const Action* action) = 0;
+};
+
+class ActionManager {
+public:
+ static ActionManager& GetInstance();
+ void QueueEventTrigger(const std::string& trigger);
+ void QueuePropertyTrigger(const std::string& name, const std::string& value);
+ void QueueAllPropertyTriggers();
+ void QueueBuiltinAction(int (*func)(const std::vector<std::string>& args),
+ const std::string& name);
+ void ExecuteOneCommand();
+ bool HasMoreCommands() const;
+ Action* AddNewAction(const std::vector<std::string>& triggers,
+ std::string* err);
+ void DumpState() const;
+
+private:
+ ActionManager();
+
+ ActionManager(ActionManager const&) = delete;
+ void operator=(ActionManager const&) = delete;
+
+ std::vector<Action*> actions_;
+ std::queue<std::unique_ptr<Trigger>> trigger_queue_;
+ std::vector<Action*> current_executing_actions_;
+ std::size_t current_command_;
+};
+
+#endif
diff --git a/init/bootchart.cpp b/init/bootchart.cpp
index 95687cb..efaee1c 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());
@@ -164,10 +164,11 @@
// timeout. this is useful when using -wipe-data since the /data
// partition is fresh.
std::string cmdline;
+ const char* s;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
#define KERNEL_OPTION "androidboot.bootchart="
- if (strstr(cmdline.c_str(), KERNEL_OPTION) != NULL) {
- timeout = atoi(cmdline.c_str() + sizeof(KERNEL_OPTION) - 1);
+ if ((s = strstr(cmdline.c_str(), KERNEL_OPTION)) != NULL) {
+ timeout = atoi(s + sizeof(KERNEL_OPTION) - 1);
}
}
if (timeout == 0)
@@ -202,7 +203,7 @@
return count;
}
-int do_bootchart_init(int nargs, char** args) {
+int do_bootchart_init(const std::vector<std::string>& args) {
g_remaining_samples = bootchart_init();
if (g_remaining_samples < 0) {
ERROR("Bootcharting init failure: %s\n", strerror(errno));
diff --git a/init/builtins.cpp b/init/builtins.cpp
index ca31c50..97151c0 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,33 +40,30 @@
#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"
-#include "keywords.h"
-#include "property_service.h"
+#include "action.h"
#include "devices.h"
+#include "init.h"
#include "init_parser.h"
-#include "util.h"
+#include "keywords.h"
#include "log.h"
+#include "property_service.h"
+#include "service.h"
+#include "util.h"
#define chmod DO_NOT_USE_CHMOD_USE_FCHMODAT_SYMLINK_NOFOLLOW
-
-int add_environment(const char *name, const char *value);
+#define UNMOUNT_CHECK_MS 5000
+#define UNMOUNT_CHECK_TIMES 10
// System call provided by bionic but not in any header file.
extern "C" int init_module(void *, unsigned long, const char *);
-static int insmod(const char *filename, char *options)
+static int insmod(const char *filename, const 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;
}
@@ -100,154 +99,190 @@
return ret;
}
-static void service_start_if_not_disabled(struct service *svc)
+static void unmount_and_fsck(const struct mntent *entry)
{
- if (!(svc->flags & SVC_DISABLED)) {
- service_start(svc, NULL);
- } else {
- svc->flags |= SVC_DISABLED_START;
+ 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.
+ */
+ ServiceManager::GetInstance().ForEachService([] (Service* s) { s->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, NULL, 0);
+ } 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, NULL, 0);
}
}
-int do_class_start(int nargs, char **args)
+int do_class_start(const std::vector<std::string>& args)
{
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
- service_for_each_class(args[1], service_start_if_not_disabled);
+ ServiceManager::GetInstance().
+ ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
return 0;
}
-int do_class_stop(int nargs, char **args)
+int do_class_stop(const std::vector<std::string>& args)
{
- service_for_each_class(args[1], service_stop);
+ ServiceManager::GetInstance().
+ ForEachServiceInClass(args[1], [] (Service* s) { s->Stop(); });
return 0;
}
-int do_class_reset(int nargs, char **args)
+int do_class_reset(const std::vector<std::string>& args)
{
- service_for_each_class(args[1], service_reset);
+ ServiceManager::GetInstance().
+ ForEachServiceInClass(args[1], [] (Service* s) { s->Reset(); });
return 0;
}
-int do_domainname(int nargs, char **args)
+int do_domainname(const std::vector<std::string>& args)
{
- return write_file("/proc/sys/kernel/domainname", args[1]);
+ return write_file("/proc/sys/kernel/domainname", args[1].c_str());
}
-int do_enable(int nargs, char **args)
+int do_enable(const std::vector<std::string>& args)
{
- struct service *svc;
- svc = service_find_by_name(args[1]);
- if (svc) {
- svc->flags &= ~(SVC_DISABLED | SVC_RC_DISABLED);
- if (svc->flags & SVC_DISABLED_START) {
- service_start(svc, NULL);
- }
- } else {
+ Service* svc = ServiceManager::GetInstance().FindServiceByName(args[1]);
+ if (!svc) {
return -1;
}
- return 0;
+ return svc->Enable();
}
-int do_exec(int nargs, char** args) {
- service* svc = make_exec_oneshot_service(nargs, args);
- if (svc == NULL) {
+int do_exec(const std::vector<std::string>& args) {
+ Service* svc = ServiceManager::GetInstance().MakeExecOneshotService(args);
+ if (!svc) {
return -1;
}
- service_start(svc, NULL);
+ if (!svc->Start()) {
+ return -1;
+ }
+ waiting_for_exec = true;
return 0;
}
-int do_export(int nargs, char **args)
+int do_export(const std::vector<std::string>& args)
{
- return add_environment(args[1], args[2]);
+ return add_environment(args[1].c_str(), args[2].c_str());
}
-int do_hostname(int nargs, char **args)
+int do_hostname(const std::vector<std::string>& args)
{
- return write_file("/proc/sys/kernel/hostname", args[1]);
+ return write_file("/proc/sys/kernel/hostname", args[1].c_str());
}
-int do_ifup(int nargs, char **args)
+int do_ifup(const std::vector<std::string>& args)
{
- return __ifupdown(args[1], 1);
+ return __ifupdown(args[1].c_str(), 1);
}
-
-static int do_insmod_inner(int nargs, char **args, int opt_len)
+int do_insmod(const std::vector<std::string>& args)
{
- char options[opt_len + 1];
- int i;
+ std::string options;
- options[0] = '\0';
- if (nargs > 2) {
- strcpy(options, args[2]);
- for (i = 3; i < nargs; ++i) {
- strcat(options, " ");
- strcat(options, args[i]);
+ if (args.size() > 2) {
+ options += args[2];
+ for (std::size_t i = 3; i < args.size(); ++i) {
+ options += ' ';
+ options += args[i];
}
}
- return insmod(args[1], options);
+ return insmod(args[1].c_str(), options.c_str());
}
-int do_insmod(int nargs, char **args)
-{
- int i;
- int size = 0;
-
- if (nargs > 2) {
- for (i = 2; i < nargs; ++i)
- size += strlen(args[i]) + 1;
- }
-
- return do_insmod_inner(nargs, args, size);
-}
-
-int do_mkdir(int nargs, char **args)
+int do_mkdir(const std::vector<std::string>& args)
{
mode_t mode = 0755;
int ret;
/* mkdir <path> [mode] [owner] [group] */
- if (nargs >= 3) {
- mode = strtoul(args[2], 0, 8);
+ if (args.size() >= 3) {
+ mode = std::stoul(args[2], 0, 8);
}
- ret = make_dir(args[1], mode);
+ ret = make_dir(args[1].c_str(), mode);
/* chmod in case the directory already exists */
if (ret == -1 && errno == EEXIST) {
- ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
+ ret = fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW);
}
if (ret == -1) {
return -errno;
}
- if (nargs >= 4) {
- uid_t uid = decode_uid(args[3]);
+ if (args.size() >= 4) {
+ uid_t uid = decode_uid(args[3].c_str());
gid_t gid = -1;
- if (nargs == 5) {
- gid = decode_uid(args[4]);
+ if (args.size() == 5) {
+ gid = decode_uid(args[4].c_str());
}
- if (lchown(args[1], uid, gid) == -1) {
+ if (lchown(args[1].c_str(), uid, gid) == -1) {
return -errno;
}
/* chown may have cleared S_ISUID and S_ISGID, chmod again */
if (mode & (S_ISUID | S_ISGID)) {
- ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
+ ret = fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW);
if (ret == -1) {
return -errno;
}
}
}
- return e4crypt_set_directory_policy(args[1]);
+ return e4crypt_set_directory_policy(args[1].c_str());
}
static struct {
@@ -275,35 +310,36 @@
#define DATA_MNT_POINT "/data"
/* mount <type> <device> <path> <flags ...> <options> */
-int do_mount(int nargs, char **args)
+int do_mount(const std::vector<std::string>& args)
{
char tmp[64];
- char *source, *target, *system;
- char *options = NULL;
+ const char *source, *target, *system;
+ const char *options = NULL;
unsigned flags = 0;
+ std::size_t na = 0;
int n, i;
int wait = 0;
- for (n = 4; n < nargs; n++) {
+ for (na = 4; na < args.size(); na++) {
for (i = 0; mount_flags[i].name; i++) {
- if (!strcmp(args[n], mount_flags[i].name)) {
+ if (!args[na].compare(mount_flags[i].name)) {
flags |= mount_flags[i].flag;
break;
}
}
if (!mount_flags[i].name) {
- if (!strcmp(args[n], "wait"))
+ if (!args[na].compare("wait"))
wait = 1;
/* if our last argument isn't a flag, wolf it up as an option string */
- else if (n + 1 == nargs)
- options = args[n];
+ else if (na + 1 == args.size())
+ options = args[na].c_str();
}
}
- system = args[1];
- source = args[2];
- target = args[3];
+ system = args[1].c_str();
+ source = args[2].c_str();
+ target = args[3].c_str();
if (!strncmp(source, "mtd@", 4)) {
n = mtd_name_to_number(source + 4);
@@ -391,11 +427,24 @@
while (1) { pause(); } // never reached
}
+void import_late()
+{
+ static const std::vector<std::string> init_directories = {
+ "/system/etc/init",
+ "/vendor/etc/init",
+ "/odm/etc/init"
+ };
+
+ for (const auto& dir : init_directories) {
+ init_parse_config(dir.c_str());
+ }
+}
+
/*
* This function might request a reboot, in which case it will
* not return.
*/
-int do_mount_all(int nargs, char **args)
+int do_mount_all(const std::vector<std::string>& args)
{
pid_t pid;
int ret = -1;
@@ -403,10 +452,10 @@
int status;
struct fstab *fstab;
- if (nargs != 2) {
+ if (args.size() != 2) {
return -1;
}
-
+ const char* fstabfile = args[1].c_str();
/*
* 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 +479,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) {
@@ -442,6 +491,8 @@
return -1;
}
+ import_late();
+
if (ret == FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION) {
property_set("vold.decrypt", "trigger_encryption");
} else if (ret == FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED) {
@@ -453,7 +504,7 @@
/* If fs_mgr determined this is an unencrypted device, then trigger
* that action.
*/
- action_for_each_trigger("nonencrypted", action_add_queue_tail);
+ ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
} else if (ret == FS_MGR_MNTALL_DEV_NEEDS_RECOVERY) {
/* Setup a wipe via recovery, and reboot into recovery */
ERROR("fs_mgr_mount_all suggested recovery, so wiping data via recovery.\n");
@@ -468,7 +519,7 @@
// Although encrypted, we have device key, so we do not need to
// do anything different from the nonencrypted case.
- action_for_each_trigger("nonencrypted", action_add_queue_tail);
+ ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
} else if (ret == FS_MGR_MNTALL_DEV_NON_DEFAULT_FILE_ENCRYPTED) {
if (e4crypt_install_keyring()) {
return -1;
@@ -484,87 +535,77 @@
return ret;
}
-int do_swapon_all(int nargs, char **args)
+int do_swapon_all(const std::vector<std::string>& args)
{
struct fstab *fstab;
int ret;
- fstab = fs_mgr_read_fstab(args[1]);
+ fstab = fs_mgr_read_fstab(args[1].c_str());
ret = fs_mgr_swapon_all(fstab);
fs_mgr_free_fstab(fstab);
return ret;
}
-int do_setprop(int nargs, char **args)
+int do_setprop(const std::vector<std::string>& args)
{
- 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);
+ const char* name = args[1].c_str();
+ const char* value = args[2].c_str();
+ property_set(name, value);
return 0;
}
-int do_setrlimit(int nargs, char **args)
+int do_setrlimit(const std::vector<std::string>& args)
{
struct rlimit limit;
int resource;
- resource = atoi(args[1]);
- limit.rlim_cur = atoi(args[2]);
- limit.rlim_max = atoi(args[3]);
+ resource = std::stoi(args[1]);
+ limit.rlim_cur = std::stoi(args[2]);
+ limit.rlim_max = std::stoi(args[3]);
return setrlimit(resource, &limit);
}
-int do_start(int nargs, char **args)
+int do_start(const std::vector<std::string>& args)
{
- struct service *svc;
- svc = service_find_by_name(args[1]);
- if (svc) {
- service_start(svc, NULL);
+ Service* svc = ServiceManager::GetInstance().FindServiceByName(args[1]);
+ if (!svc) {
+ ERROR("do_start: Service %s not found\n", args[1].c_str());
+ return -1;
}
+ if (!svc->Start())
+ return -1;
return 0;
}
-int do_stop(int nargs, char **args)
+int do_stop(const std::vector<std::string>& args)
{
- struct service *svc;
- svc = service_find_by_name(args[1]);
- if (svc) {
- service_stop(svc);
+ Service* svc = ServiceManager::GetInstance().FindServiceByName(args[1]);
+ if (!svc) {
+ ERROR("do_stop: Service %s not found\n", args[1].c_str());
+ return -1;
}
+ svc->Stop();
return 0;
}
-int do_restart(int nargs, char **args)
+int do_restart(const std::vector<std::string>& args)
{
- struct service *svc;
- svc = service_find_by_name(args[1]);
- if (svc) {
- service_restart(svc);
+ Service* svc = ServiceManager::GetInstance().FindServiceByName(args[1]);
+ if (!svc) {
+ ERROR("do_restart: Service %s not found\n", args[1].c_str());
+ return -1;
}
+ svc->Restart();
return 0;
}
-int do_powerctl(int nargs, char **args)
+int do_powerctl(const std::vector<std::string>& args)
{
- char command[PROP_VALUE_MAX];
- int res;
+ const char* command = args[1].c_str();
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;
- }
+ unsigned int cmd = 0;
+ const char *reboot_target = "";
+ void (*callback_on_ro_remount)(const struct mntent*) = NULL;
if (strncmp(command, "shutdown", 8) == 0) {
cmd = ANDROID_RB_POWEROFF;
@@ -578,63 +619,63 @@
}
if (command[len] == ',') {
- reboot_target = &command[len + 1];
- } else if (command[len] == '\0') {
- reboot_target = "";
- } else {
+ if (cmd == ANDROID_RB_POWEROFF &&
+ !strcmp(&command[len + 1], "userrequested")) {
+ // The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED.
+ // Run fsck once the file system is remounted in read-only mode.
+ callback_on_ro_remount = unmount_and_fsck;
+ } else if (cmd == ANDROID_RB_RESTART2) {
+ reboot_target = &command[len + 1];
+ }
+ } else if (command[len] != '\0') {
ERROR("powerctl: unrecognized reboot target '%s'\n", &command[len]);
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)
+int do_trigger(const std::vector<std::string>& 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);
+ ActionManager::GetInstance().QueueEventTrigger(args[1]);
return 0;
}
-int do_symlink(int nargs, char **args)
+int do_symlink(const std::vector<std::string>& args)
{
- return symlink(args[1], args[2]);
+ return symlink(args[1].c_str(), args[2].c_str());
}
-int do_rm(int nargs, char **args)
+int do_rm(const std::vector<std::string>& args)
{
- return unlink(args[1]);
+ return unlink(args[1].c_str());
}
-int do_rmdir(int nargs, char **args)
+int do_rmdir(const std::vector<std::string>& args)
{
- return rmdir(args[1]);
+ return rmdir(args[1].c_str());
}
-int do_sysclktz(int nargs, char **args)
+int do_sysclktz(const std::vector<std::string>& args)
{
struct timezone tz;
- if (nargs != 2)
+ if (args.size() != 2)
return -1;
memset(&tz, 0, sizeof(tz));
- tz.tz_minuteswest = atoi(args[1]);
+ tz.tz_minuteswest = std::stoi(args[1]);
if (settimeofday(NULL, &tz))
return -1;
return 0;
}
-int do_verity_load_state(int nargs, char **args) {
+int do_verity_load_state(const std::vector<std::string>& args) {
int mode = -1;
int rc = fs_mgr_load_verity_state(&mode);
if (rc == 0 && mode == VERITY_MODE_LOGGING) {
- action_for_each_trigger("verity-logging", action_add_queue_tail);
+ ActionManager::GetInstance().QueueEventTrigger("verity-logging");
}
return rc;
}
@@ -644,24 +685,18 @@
android::base::StringPrintf("%d", mode).c_str());
}
-int do_verity_update_state(int nargs, char** args) {
+int do_verity_update_state(const std::vector<std::string>& args) {
return fs_mgr_update_verity_state(verity_update_property);
}
-int do_write(int nargs, char **args)
+int do_write(const std::vector<std::string>& args)
{
- 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);
+ const char* path = args[1].c_str();
+ const char* value = args[2].c_str();
+ return write_file(path, value);
}
-int do_copy(int nargs, char **args)
+int do_copy(const std::vector<std::string>& args)
{
char *buffer = NULL;
int rc = 0;
@@ -670,16 +705,16 @@
int brtw, brtr;
char *p;
- if (nargs != 3)
+ if (args.size() != 3)
return -1;
- if (stat(args[1], &info) < 0)
+ if (stat(args[1].c_str(), &info) < 0)
return -1;
- if ((fd1 = open(args[1], O_RDONLY|O_CLOEXEC)) < 0)
+ if ((fd1 = open(args[1].c_str(), O_RDONLY|O_CLOEXEC)) < 0)
goto out_err;
- if ((fd2 = open(args[2], O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0660)) < 0)
+ if ((fd2 = open(args[2].c_str(), O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0660)) < 0)
goto out_err;
if (!(buffer = (char*) malloc(info.st_size)))
@@ -723,13 +758,14 @@
return rc;
}
-int do_chown(int nargs, char **args) {
+int do_chown(const std::vector<std::string>& args) {
/* GID is optional. */
- if (nargs == 3) {
- if (lchown(args[2], decode_uid(args[1]), -1) == -1)
+ if (args.size() == 3) {
+ if (lchown(args[2].c_str(), decode_uid(args[1].c_str()), -1) == -1)
return -errno;
- } else if (nargs == 4) {
- if (lchown(args[3], decode_uid(args[1]), decode_uid(args[2])) == -1)
+ } else if (args.size() == 4) {
+ if (lchown(args[3].c_str(), decode_uid(args[1].c_str()),
+ decode_uid(args[2].c_str())) == -1)
return -errno;
} else {
return -1;
@@ -750,49 +786,41 @@
return mode;
}
-int do_chmod(int nargs, char **args) {
- mode_t mode = get_mode(args[1]);
- if (fchmodat(AT_FDCWD, args[2], mode, AT_SYMLINK_NOFOLLOW) < 0) {
+int do_chmod(const std::vector<std::string>& args) {
+ mode_t mode = get_mode(args[1].c_str());
+ if (fchmodat(AT_FDCWD, args[2].c_str(), mode, AT_SYMLINK_NOFOLLOW) < 0) {
return -errno;
}
return 0;
}
-int do_restorecon(int nargs, char **args) {
- int i;
+int do_restorecon(const std::vector<std::string>& args) {
int ret = 0;
- for (i = 1; i < nargs; i++) {
- if (restorecon(args[i]) < 0)
+ for (auto it = std::next(args.begin()); it != args.end(); ++it) {
+ if (restorecon(it->c_str()) < 0)
ret = -errno;
}
return ret;
}
-int do_restorecon_recursive(int nargs, char **args) {
- int i;
+int do_restorecon_recursive(const std::vector<std::string>& args) {
int ret = 0;
- for (i = 1; i < nargs; i++) {
- if (restorecon_recursive(args[i]) < 0)
+ for (auto it = std::next(args.begin()); it != args.end(); ++it) {
+ if (restorecon_recursive(it->c_str()) < 0)
ret = -errno;
}
return ret;
}
-int do_loglevel(int nargs, char **args) {
- int log_level;
- char log_level_str[PROP_VALUE_MAX] = "";
- if (nargs != 2) {
+int do_loglevel(const std::vector<std::string>& args) {
+ if (args.size() != 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 = std::stoi(args[1]);
if (log_level < KLOG_ERROR_LEVEL || log_level > KLOG_DEBUG_LEVEL) {
ERROR("loglevel: invalid log level'%d'\n", log_level);
return -EINVAL;
@@ -801,28 +829,28 @@
return 0;
}
-int do_load_persist_props(int nargs, char **args) {
- if (nargs == 1) {
+int do_load_persist_props(const std::vector<std::string>& args) {
+ if (args.size() == 1) {
load_persist_props();
return 0;
}
return -1;
}
-int do_load_all_props(int nargs, char **args) {
- if (nargs == 1) {
+int do_load_all_props(const std::vector<std::string>& args) {
+ if (args.size() == 1) {
load_all_props();
return 0;
}
return -1;
}
-int do_wait(int nargs, char **args)
+int do_wait(const std::vector<std::string>& args)
{
- if (nargs == 2) {
- return wait_for_file(args[1], COMMAND_RETRY_TIMEOUT);
- } else if (nargs == 3) {
- return wait_for_file(args[1], atoi(args[2]));
+ if (args.size() == 2) {
+ return wait_for_file(args[1].c_str(), COMMAND_RETRY_TIMEOUT);
+ } else if (args.size() == 3) {
+ return wait_for_file(args[1].c_str(), std::stoi(args[2]));
} else
return -1;
}
@@ -839,18 +867,17 @@
return 0;
}
-int do_installkey(int nargs, char **args)
+int do_installkey(const std::vector<std::string>& args)
{
- if (nargs != 2) {
+ if (args.size() != 2) {
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;
}
- return e4crypt_create_device_key(args[1],
+ return e4crypt_create_device_key(args[1].c_str(),
do_installkeys_ensure_dir_exists);
}
diff --git a/init/compare-bootcharts.py b/init/compare-bootcharts.py
new file mode 100755
index 0000000..2057b55
--- /dev/null
+++ b/init/compare-bootcharts.py
@@ -0,0 +1,146 @@
+#!/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.
+
+"""Compare two bootcharts and list start/end timestamps on key processes.
+
+This script extracts two bootchart.tgz files and compares the timestamps
+in proc_ps.log for selected processes. The proc_ps.log file consists of
+repetitive blocks of the following format:
+
+timestamp1 (jiffies)
+dumps of /proc/<pid>/stat
+
+timestamp2
+dumps of /proc/<pid>/stat
+
+The timestamps are 200ms apart, and the creation time of selected processes
+are listed. The termination time of the boot animation process is also listed
+as a coarse indication about when the boot process is complete as perceived by
+the user.
+"""
+
+import sys
+import tarfile
+
+# The bootchart timestamps are 200ms apart, but the USER_HZ value is not
+# reported in the bootchart, so we use the first two timestamps to calculate
+# the wall clock time of a jiffy.
+jiffy_to_wallclock = {
+ '1st_timestamp': -1,
+ '2nd_timestamp': -1,
+ 'jiffy_to_wallclock': -1
+}
+
+def analyze_process_maps(process_map1, process_map2, jiffy_record):
+ # List interesting processes here
+ processes_of_interest = [
+ '/init',
+ '/system/bin/surfaceflinger',
+ '/system/bin/bootanimation',
+ 'zygote64',
+ 'zygote',
+ 'system_server'
+ ]
+
+ jw = jiffy_record['jiffy_to_wallclock']
+ print "process: baseline experiment (delta)"
+ print " - Unit is ms (a jiffy is %d ms on the system)" % jw
+ print "------------------------------------"
+ for p in processes_of_interest:
+ # e.g., 32-bit system doesn't have zygote64
+ if p in process_map1 and p in process_map2:
+ print "%s: %d %d (%+d)" % (
+ p, process_map1[p]['start_time'] * jw,
+ process_map2[p]['start_time'] * jw,
+ (process_map2[p]['start_time'] -
+ process_map1[p]['start_time']) * jw)
+
+ # Print the last tick for the bootanimation process
+ print "bootanimation ends at: %d %d (%+d)" % (
+ process_map1['/system/bin/bootanimation']['last_tick'] * jw,
+ process_map2['/system/bin/bootanimation']['last_tick'] * jw,
+ (process_map2['/system/bin/bootanimation']['last_tick'] -
+ process_map1['/system/bin/bootanimation']['last_tick']) * jw)
+
+def parse_proc_file(pathname, process_map, jiffy_record=None):
+ # Uncompress bootchart.tgz
+ with tarfile.open(pathname + '/bootchart.tgz', 'r:*') as tf:
+ try:
+ # Read proc_ps.log
+ f = tf.extractfile('proc_ps.log')
+
+ # Break proc_ps into chunks based on timestamps
+ blocks = f.read().split('\n\n')
+ for b in blocks:
+ lines = b.split('\n')
+ if not lines[0]:
+ break
+
+ # 200ms apart in jiffies
+ timestamp = int(lines[0]);
+
+ # Figure out the wall clock time of a jiffy
+ if jiffy_record is not None:
+ if jiffy_record['1st_timestamp'] == -1:
+ jiffy_record['1st_timestamp'] = timestamp
+ elif jiffy_record['jiffy_to_wallclock'] == -1:
+ # Not really needed but for debugging purposes
+ jiffy_record['2nd_timestamp'] = timestamp
+ value = 200 / (timestamp -
+ jiffy_record['1st_timestamp'])
+ # Fix the rounding error
+ # e.g., 201 jiffies in 200ms when USER_HZ is 1000
+ if value == 0:
+ value = 1
+ # e.g., 21 jiffies in 200ms when USER_HZ is 100
+ elif value == 9:
+ value = 10
+ jiffy_record['jiffy_to_wallclock'] = value
+
+ # Populate the process_map table
+ for line in lines[1:]:
+ segs = line.split(' ')
+
+ # 0: pid
+ # 1: process name
+ # 17: priority
+ # 18: nice
+ # 21: creation time
+
+ proc_name = segs[1].strip('()')
+ if proc_name in process_map:
+ process = process_map[proc_name]
+ else:
+ process = {'start_time': int(segs[21])}
+ process_map[proc_name] = process
+
+ process['last_tick'] = timestamp
+ finally:
+ f.close()
+
+def main():
+ if len(sys.argv) != 3:
+ print "Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0]
+ sys.exit(1)
+
+ process_map1 = {}
+ process_map2 = {}
+ parse_proc_file(sys.argv[1], process_map1, jiffy_to_wallclock)
+ parse_proc_file(sys.argv[2], process_map2)
+ analyze_process_maps(process_map1, process_map2, jiffy_to_wallclock)
+
+if __name__ == "__main__":
+ main()
diff --git a/init/devices.cpp b/init/devices.cpp
index 2c7f5a9..3652c57 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -241,10 +241,8 @@
mode = get_device_perm(path, links, &uid, &gid) | (block ? S_IFBLK : S_IFCHR);
- if (sehandle) {
- selabel_lookup_best_match(sehandle, &secontext, path, links, mode);
- setfscreatecon(secontext);
- }
+ selabel_lookup_best_match(sehandle, &secontext, path, links, mode);
+ setfscreatecon(secontext);
dev = makedev(major, minor);
/* Temporarily change egid to avoid race condition setting the gid of the
@@ -907,7 +905,7 @@
struct uevent uevent;
parse_event(msg, &uevent);
- if (sehandle && selinux_status_updated() > 0) {
+ if (selinux_status_updated() > 0) {
struct selabel_handle *sehandle2;
sehandle2 = selinux_android_file_context_handle();
if (sehandle2) {
@@ -974,11 +972,8 @@
}
void device_init() {
- sehandle = NULL;
- if (is_selinux_enabled() > 0) {
- sehandle = selinux_android_file_context_handle();
- selinux_status_open(true);
- }
+ sehandle = selinux_android_file_context_handle();
+ selinux_status_open(true);
/* is 256K enough? udev uses 16MB! */
device_fd = uevent_open_socket(256*1024, true);
diff --git a/init/init.cpp b/init/init.cpp
index 60fcf64..c94a6fe 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -32,7 +32,6 @@
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
-#include <termios.h>
#include <unistd.h>
#include <mtd/mtd-user.h>
@@ -53,16 +52,18 @@
#include <memory>
+#include "action.h"
+#include "bootchart.h"
#include "devices.h"
#include "init.h"
+#include "init_parser.h"
+#include "keychords.h"
#include "log.h"
#include "property_service.h"
-#include "bootchart.h"
+#include "service.h"
#include "signal_handler.h"
-#include "keychords.h"
-#include "init_parser.h"
-#include "util.h"
#include "ueventd.h"
+#include "util.h"
#include "watchdogd.h"
struct selabel_handle *sehandle;
@@ -72,14 +73,11 @@
static char qemu[32];
-static struct action *cur_action = NULL;
-static struct command *cur_command = NULL;
-
-static int have_console;
-static char console_name[PROP_VALUE_MAX] = "/dev/console";
+int have_console;
+std::string console_name = "/dev/console";
static time_t process_needs_restart;
-static const char *ENV[32];
+const char *ENV[32];
bool waiting_for_exec = false;
@@ -94,27 +92,6 @@
}
}
-void service::NotifyStateChange(const char* new_state) {
- if (!properties_initialized()) {
- // If properties aren't available yet, we can't set them.
- return;
- }
-
- if ((flags & SVC_EXEC) != 0) {
- // 'exec' commands don't have properties tracking their state.
- return;
- }
-
- char prop_name[PROP_NAME_MAX];
- if (snprintf(prop_name, sizeof(prop_name), "init.svc.%s", name) >= PROP_NAME_MAX) {
- // If the property name would be too long, we can't set it.
- ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n", name, new_state);
- return;
- }
-
- property_set(prop_name, new_state);
-}
-
/* add_environment - add "key=value" to the current environment */
int add_environment(const char *key, const char *val)
{
@@ -147,474 +124,80 @@
return -1;
}
-void zap_stdio(void)
-{
- int fd;
- fd = open("/dev/null", O_RDWR);
- dup2(fd, 0);
- dup2(fd, 1);
- dup2(fd, 2);
- close(fd);
-}
-
-static void open_console()
-{
- int fd;
- if ((fd = open(console_name, O_RDWR)) < 0) {
- fd = open("/dev/null", O_RDWR);
- }
- ioctl(fd, TIOCSCTTY, 0);
- dup2(fd, 0);
- dup2(fd, 1);
- dup2(fd, 2);
- close(fd);
-}
-
-static void publish_socket(const char *name, int fd)
-{
- char key[64] = ANDROID_SOCKET_ENV_PREFIX;
- char val[64];
-
- strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1,
- name,
- sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX));
- snprintf(val, sizeof(val), "%d", fd);
- add_environment(key, val);
-
- /* make sure we don't close-on-exec */
- fcntl(fd, F_SETFD, 0);
-}
-
-void service_start(struct service *svc, const char *dynamic_args)
-{
- // Starting a service removes it from the disabled or reset state and
- // immediately takes it out of the restarting state if it was in there.
- svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
- svc->time_started = 0;
-
- // Running processes require no additional work --- if they're in the
- // process of exiting, we've ensured that they will immediately restart
- // on exit, unless they are ONESHOT.
- if (svc->flags & SVC_RUNNING) {
- return;
- }
-
- bool needs_console = (svc->flags & SVC_CONSOLE);
- if (needs_console && !have_console) {
- ERROR("service '%s' requires console\n", svc->name);
- svc->flags |= SVC_DISABLED;
- return;
- }
-
- struct stat sb;
- if (stat(svc->args[0], &sb) == -1) {
- ERROR("cannot find '%s' (%s), disabling '%s'\n", svc->args[0], strerror(errno), svc->name);
- svc->flags |= SVC_DISABLED;
- return;
- }
-
- if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) {
- ERROR("service '%s' must be one-shot to use dynamic args, disabling\n", svc->args[0]);
- svc->flags |= SVC_DISABLED;
- return;
- }
-
- char* scon = NULL;
- if (is_selinux_enabled() > 0) {
- if (svc->seclabel) {
- scon = strdup(svc->seclabel);
- if (!scon) {
- ERROR("Out of memory while starting '%s'\n", svc->name);
- return;
- }
- } else {
- char *mycon = NULL, *fcon = NULL;
-
- INFO("computing context for service '%s'\n", svc->args[0]);
- int rc = getcon(&mycon);
- if (rc < 0) {
- ERROR("could not get context while starting '%s'\n", svc->name);
- return;
- }
-
- rc = getfilecon(svc->args[0], &fcon);
- if (rc < 0) {
- ERROR("could not get context while starting '%s'\n", svc->name);
- freecon(mycon);
- return;
- }
-
- rc = security_compute_create(mycon, fcon, string_to_security_class("process"), &scon);
- if (rc == 0 && !strcmp(scon, mycon)) {
- ERROR("Warning! Service %s needs a SELinux domain defined; please fix!\n", svc->name);
- }
- freecon(mycon);
- freecon(fcon);
- if (rc < 0) {
- ERROR("could not get context while starting '%s'\n", svc->name);
- return;
- }
- }
- }
-
- NOTICE("Starting service '%s'...\n", svc->name);
-
- pid_t pid = fork();
- if (pid == 0) {
- struct socketinfo *si;
- struct svcenvinfo *ei;
- char tmp[32];
- int fd, sz;
-
- umask(077);
- if (properties_initialized()) {
- get_property_workspace(&fd, &sz);
- snprintf(tmp, sizeof(tmp), "%d,%d", dup(fd), sz);
- add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
- }
-
- for (ei = svc->envvars; ei; ei = ei->next)
- add_environment(ei->name, ei->value);
-
- for (si = svc->sockets; si; si = si->next) {
- int socket_type = (
- !strcmp(si->type, "stream") ? SOCK_STREAM :
- (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
- int s = create_socket(si->name, socket_type,
- si->perm, si->uid, si->gid, si->socketcon ?: scon);
- if (s >= 0) {
- publish_socket(si->name, s);
- }
- }
-
- freecon(scon);
- scon = NULL;
-
- if (svc->ioprio_class != IoSchedClass_NONE) {
- if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) {
- ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
- getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno));
- }
- }
-
- if (needs_console) {
- setsid();
- open_console();
- } else {
- zap_stdio();
- }
-
- if (false) {
- for (size_t n = 0; svc->args[n]; n++) {
- INFO("args[%zu] = '%s'\n", n, svc->args[n]);
- }
- for (size_t n = 0; ENV[n]; n++) {
- INFO("env[%zu] = '%s'\n", n, ENV[n]);
- }
- }
-
- setpgid(0, getpid());
-
- // As requested, set our gid, supplemental gids, and uid.
- if (svc->gid) {
- if (setgid(svc->gid) != 0) {
- ERROR("setgid failed: %s\n", strerror(errno));
- _exit(127);
- }
- }
- if (svc->nr_supp_gids) {
- if (setgroups(svc->nr_supp_gids, svc->supp_gids) != 0) {
- ERROR("setgroups failed: %s\n", strerror(errno));
- _exit(127);
- }
- }
- if (svc->uid) {
- if (setuid(svc->uid) != 0) {
- ERROR("setuid failed: %s\n", strerror(errno));
- _exit(127);
- }
- }
- if (svc->seclabel) {
- if (is_selinux_enabled() > 0 && setexeccon(svc->seclabel) < 0) {
- ERROR("cannot setexeccon('%s'): %s\n", svc->seclabel, strerror(errno));
- _exit(127);
- }
- }
-
- if (!dynamic_args) {
- if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
- ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
- }
- } else {
- char *arg_ptrs[INIT_PARSER_MAXARGS+1];
- int arg_idx = svc->nargs;
- char *tmp = strdup(dynamic_args);
- char *next = tmp;
- char *bword;
-
- /* Copy the static arguments */
- memcpy(arg_ptrs, svc->args, (svc->nargs * sizeof(char *)));
-
- while((bword = strsep(&next, " "))) {
- arg_ptrs[arg_idx++] = bword;
- if (arg_idx == INIT_PARSER_MAXARGS)
- break;
- }
- arg_ptrs[arg_idx] = NULL;
- execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);
- }
- _exit(127);
- }
-
- freecon(scon);
-
- if (pid < 0) {
- ERROR("failed to start '%s'\n", svc->name);
- svc->pid = 0;
- return;
- }
-
- svc->time_started = gettime();
- svc->pid = pid;
- svc->flags |= SVC_RUNNING;
-
- if ((svc->flags & SVC_EXEC) != 0) {
- INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n",
- svc->pid, svc->uid, svc->gid, svc->nr_supp_gids, svc->seclabel);
- waiting_for_exec = true;
- }
-
- svc->NotifyStateChange("running");
-}
-
-/* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */
-static void service_stop_or_reset(struct service *svc, int how)
-{
- /* The service is still SVC_RUNNING until its process exits, but if it has
- * already exited it shoudn't attempt a restart yet. */
- svc->flags &= ~(SVC_RESTARTING | SVC_DISABLED_START);
-
- if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) {
- /* Hrm, an illegal flag. Default to SVC_DISABLED */
- how = SVC_DISABLED;
- }
- /* if the service has not yet started, prevent
- * it from auto-starting with its class
- */
- if (how == SVC_RESET) {
- svc->flags |= (svc->flags & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET;
- } else {
- svc->flags |= how;
- }
-
- if (svc->pid) {
- NOTICE("Service '%s' is being killed...\n", svc->name);
- kill(-svc->pid, SIGKILL);
- svc->NotifyStateChange("stopping");
- } else {
- svc->NotifyStateChange("stopped");
- }
-}
-
-void service_reset(struct service *svc)
-{
- service_stop_or_reset(svc, SVC_RESET);
-}
-
-void service_stop(struct service *svc)
-{
- service_stop_or_reset(svc, SVC_DISABLED);
-}
-
-void service_restart(struct service *svc)
-{
- if (svc->flags & SVC_RUNNING) {
- /* Stop, wait, then start the service. */
- service_stop_or_reset(svc, SVC_RESTART);
- } else if (!(svc->flags & SVC_RESTARTING)) {
- /* Just start the service since it's not running. */
- service_start(svc, NULL);
- } /* else: Service is restarting anyways. */
-}
-
void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled)
- queue_property_triggers(name, value);
-}
-
-static void restart_service_if_needed(struct service *svc)
-{
- time_t next_start_time = svc->time_started + 5;
-
- if (next_start_time <= gettime()) {
- svc->flags &= (~SVC_RESTARTING);
- service_start(svc, NULL);
- return;
- }
-
- if ((next_start_time < process_needs_restart) ||
- (process_needs_restart == 0)) {
- process_needs_restart = next_start_time;
- }
+ ActionManager::GetInstance().QueuePropertyTrigger(name, value);
}
static void restart_processes()
{
process_needs_restart = 0;
- service_for_each_flags(SVC_RESTARTING,
- restart_service_if_needed);
+ ServiceManager::GetInstance().
+ ForEachServiceWithFlags(SVC_RESTARTING, [] (Service* s) {
+ s->RestartIfNeeded(process_needs_restart);
+ });
}
-static void msg_start(const char *name)
+static void msg_start(const std::string& name)
{
- struct service *svc = NULL;
- char *tmp = NULL;
- char *args = NULL;
+ Service* svc = nullptr;
+ std::vector<std::string> vargs;
- if (!strchr(name, ':'))
- svc = service_find_by_name(name);
- else {
- tmp = strdup(name);
- if (tmp) {
- args = strchr(tmp, ':');
- *args = '\0';
- args++;
+ size_t colon_pos = name.find(':');
+ if (colon_pos == std::string::npos) {
+ svc = ServiceManager::GetInstance().FindServiceByName(name);
+ } else {
+ std::string service_name(name.substr(0, colon_pos));
+ std::string args(name.substr(colon_pos + 1));
+ vargs = android::base::Split(args, " ");
- svc = service_find_by_name(tmp);
- }
+ svc = ServiceManager::GetInstance().FindServiceByName(service_name);
}
if (svc) {
- service_start(svc, args);
+ svc->Start(vargs);
} else {
- ERROR("no such service '%s'\n", name);
+ ERROR("no such service '%s'\n", name.c_str());
}
- if (tmp)
- free(tmp);
}
-static void msg_stop(const char *name)
+static void msg_stop(const std::string& name)
{
- struct service *svc = service_find_by_name(name);
+ Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
if (svc) {
- service_stop(svc);
+ svc->Stop();
} else {
- ERROR("no such service '%s'\n", name);
+ ERROR("no such service '%s'\n", name.c_str());
}
}
-static void msg_restart(const char *name)
+static void msg_restart(const std::string& name)
{
- struct service *svc = service_find_by_name(name);
+ Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
if (svc) {
- service_restart(svc);
+ svc->Restart();
} else {
- ERROR("no such service '%s'\n", name);
+ ERROR("no such service '%s'\n", name.c_str());
}
}
-void handle_control_message(const char *msg, const char *arg)
+void handle_control_message(const std::string& msg, const std::string& arg)
{
- if (!strcmp(msg,"start")) {
+ if (msg == "start") {
msg_start(arg);
- } else if (!strcmp(msg,"stop")) {
+ } else if (msg == "stop") {
msg_stop(arg);
- } else if (!strcmp(msg,"restart")) {
+ } else if (msg == "restart") {
msg_restart(arg);
} else {
- ERROR("unknown control msg '%s'\n", msg);
+ ERROR("unknown control msg '%s'\n", msg.c_str());
}
}
-static struct command *get_first_command(struct action *act)
-{
- struct listnode *node;
- node = list_head(&act->commands);
- if (!node || list_empty(&act->commands))
- return NULL;
-
- return node_to_item(node, struct command, clist);
-}
-
-static struct command *get_next_command(struct action *act, struct command *cmd)
-{
- struct listnode *node;
- node = cmd->clist.next;
- if (!node)
- return NULL;
- if (node == &act->commands)
- return NULL;
-
- return node_to_item(node, struct command, clist);
-}
-
-static int is_last_command(struct action *act, struct command *cmd)
-{
- return (list_tail(&act->commands) == &cmd->clist);
-}
-
-
-void build_triggers_string(char *name_str, int length, struct action *cur_action) {
- 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);
- }
- strlcat(name_str, cur_trigger->name , length);
- }
-}
-
-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;
- if (!cur_action) {
- return;
- }
-
- build_triggers_string(name_str, sizeof(name_str), cur_action);
-
- INFO("processing action %p (%s)\n", cur_action, name_str);
- cur_command = get_first_command(cur_action);
- } else {
- cur_command = get_next_command(cur_action, cur_command);
- }
-
- if (!cur_command) {
- return;
- }
-
- int result = cur_command->func(cur_command->nargs, cur_command->args);
- 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));
- }
- }
- char source[256];
- if (cur_command->filename) {
- snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line);
- } else {
- *source = '\0';
- }
- INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
- cmd_str, cur_action ? name_str : "", source, result, t.duration());
- }
-}
-
-static int wait_for_coldboot_done_action(int nargs, char **args) {
+static int wait_for_coldboot_done_action(const std::vector<std::string>& args) {
Timer t;
NOTICE("Waiting for %s...\n", COLDBOOT_DONE);
@@ -644,7 +227,7 @@
* time. We do not reboot or halt on failures, as this is a best-effort
* attempt.
*/
-static int mix_hwrng_into_linux_rng_action(int nargs, char **args)
+static int mix_hwrng_into_linux_rng_action(const std::vector<std::string>& args)
{
int result = -1;
int hwrandom_fd = -1;
@@ -706,20 +289,20 @@
return result;
}
-static int keychord_init_action(int nargs, char **args)
+static int keychord_init_action(const std::vector<std::string>& args)
{
keychord_init();
return 0;
}
-static int console_init_action(int nargs, char **args)
+static int console_init_action(const std::vector<std::string>& 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);
@@ -780,9 +363,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);
}
}
@@ -828,9 +410,9 @@
if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}
-static int queue_property_triggers_action(int nargs, char **args)
+static int queue_property_triggers_action(const std::vector<std::string>& args)
{
- queue_all_property_triggers();
+ ActionManager::GetInstance().QueueAllPropertyTriggers();
/* enable property triggers */
property_triggers_enabled = 1;
return 0;
@@ -1022,38 +604,40 @@
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);
+ ActionManager& am = ActionManager::GetInstance();
+
+ am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
- queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
+ am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
- queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
- queue_builtin_action(keychord_init_action, "keychord_init");
- queue_builtin_action(console_init_action, "console_init");
+ am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
+ am.QueueBuiltinAction(keychord_init_action, "keychord_init");
+ am.QueueBuiltinAction(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
- action_for_each_trigger("init", action_add_queue_tail);
+ am.QueueEventTrigger("init");
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
- queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
+ am.QueueBuiltinAction(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) {
- action_for_each_trigger("charger", action_add_queue_tail);
+ std::string bootmode = property_get("ro.bootmode");
+ if (bootmode == "charger") {
+ am.QueueEventTrigger("charger");
} else {
- action_for_each_trigger("late-init", action_add_queue_tail);
+ am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
- queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
+ am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
while (true) {
if (!waiting_for_exec) {
- execute_one_command();
+ am.ExecuteOneCommand();
restart_processes();
}
@@ -1064,7 +648,7 @@
timeout = 0;
}
- if (!action_queue_empty() || cur_action) {
+ if (am.HasMoreCommands()) {
timeout = 0;
}
diff --git a/init/init.h b/init/init.h
index 1cabb14..345d442 100644
--- a/init/init.h
+++ b/init/init.h
@@ -17,144 +17,28 @@
#ifndef _INIT_INIT_H
#define _INIT_INIT_H
-#include <sys/types.h>
+#include <string>
-#include <cutils/list.h>
-#include <cutils/iosched_policy.h>
-
-struct command
-{
- /* list of commands in an action */
- struct listnode clist;
-
- int (*func)(int nargs, char **args);
-
- int line;
- const char *filename;
-
- int nargs;
- char *args[1];
-};
-
-struct trigger {
- struct listnode nlist;
- const char *name;
-};
-
-struct action {
- /* node in list of all actions */
- struct listnode alist;
- /* node in the queue of pending actions */
- struct listnode qlist;
- /* node in list of actions for a trigger */
- struct listnode tlist;
-
- unsigned hash;
-
- /* list of actions which triggers the commands*/
- struct listnode triggers;
- struct listnode commands;
- struct command *current;
-};
-
-struct socketinfo {
- struct socketinfo *next;
- const char *name;
- const char *type;
- uid_t uid;
- gid_t gid;
- int perm;
- const char *socketcon;
-};
-
-struct svcenvinfo {
- struct svcenvinfo *next;
- const char *name;
- const char *value;
-};
-
-#define SVC_DISABLED 0x001 // do not autostart with class
-#define SVC_ONESHOT 0x002 // do not restart on exit
-#define SVC_RUNNING 0x004 // currently active
-#define SVC_RESTARTING 0x008 // waiting to restart
-#define SVC_CONSOLE 0x010 // requires console
-#define SVC_CRITICAL 0x020 // will reboot into recovery if keeps crashing
-#define SVC_RESET 0x040 // Use when stopping a process, but not disabling so it can be restarted with its class.
-#define SVC_RC_DISABLED 0x080 // Remember if the disabled flag was set in the rc script.
-#define SVC_RESTART 0x100 // Use to safely restart (stop, wait, start) a service.
-#define SVC_DISABLED_START 0x200 // A start was requested but it was disabled at the time.
-#define SVC_EXEC 0x400 // This synthetic service corresponds to an 'exec'.
-
-#define NR_SVC_SUPP_GIDS 12 /* twelve supplementary groups */
+class Action;
+class Service;
#define COMMAND_RETRY_TIMEOUT 5
-struct service {
- void NotifyStateChange(const char* new_state);
-
- /* list of all services */
- struct listnode slist;
-
- char *name;
- const char *classname;
-
- unsigned flags;
- pid_t pid;
- time_t time_started; /* time of last start */
- time_t time_crashed; /* first crash within inspection window */
- int nr_crashed; /* number of times crashed within window */
-
- uid_t uid;
- gid_t gid;
- gid_t supp_gids[NR_SVC_SUPP_GIDS];
- size_t nr_supp_gids;
-
- const char* seclabel;
-
- struct socketinfo *sockets;
- struct svcenvinfo *envvars;
-
- struct action onrestart; /* Actions to execute on restart. */
-
- /* keycodes for triggering this service via /dev/keychord */
- int *keycodes;
- int nkeycodes;
- int keychord_id;
-
- IoSchedClass ioprio_class;
- int ioprio_pri;
-
- int nargs;
- /* "MUST BE AT THE END OF THE STRUCT" */
- char *args[1];
-}; /* ^-------'args' MUST be at the end of this struct! */
-
+extern const char *ENV[32];
extern bool waiting_for_exec;
+extern int have_console;
+extern std::string console_name;
extern struct selabel_handle *sehandle;
extern struct selabel_handle *sehandle_prop;
-void build_triggers_string(char *name_str, int length, struct action *cur_action);
+void handle_control_message(const std::string& msg, const std::string& arg);
-void handle_control_message(const char *msg, const char *arg);
-
-struct service *service_find_by_name(const char *name);
-struct service *service_find_by_pid(pid_t pid);
-struct service *service_find_by_keychord(int keychord_id);
-void service_for_each(void (*func)(struct service *svc));
-void service_for_each_class(const char *classname,
- void (*func)(struct service *svc));
-void service_for_each_flags(unsigned matchflags,
- void (*func)(struct service *svc));
-void service_stop(struct service *svc);
-void service_reset(struct service *svc);
-void service_restart(struct service *svc);
-void service_start(struct service *svc, const char *dynamic_args);
void property_changed(const char *name, const char *value);
int selinux_reload_policy(void);
-void zap_stdio(void);
-
void register_epoll_handler(int fd, void (*fn)());
-#endif /* _INIT_INIT_H */
+int add_environment(const char* key, const char* val);
+
+#endif /* _INIT_INIT_H */
diff --git a/init/init_parser.cpp b/init/init_parser.cpp
index df049ed..12f44f7 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>
@@ -25,19 +26,20 @@
#include <string.h>
#include <unistd.h>
+#include "action.h"
#include "init.h"
-#include "parser.h"
#include "init_parser.h"
#include "log.h"
+#include "parser.h"
#include "property_service.h"
+#include "service.h"
#include "util.h"
+#include <base/stringprintf.h>
#include <cutils/iosched_policy.h>
#include <cutils/list.h>
static list_declare(service_list);
-static list_declare(action_list);
-static list_declare(action_queue);
struct import {
struct listnode list;
@@ -61,8 +63,8 @@
static struct {
const char *name;
- int (*func)(int nargs, char **args);
- unsigned char nargs;
+ int (*func)(const std::vector<std::string>& args);
+ size_t nargs;
unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
@@ -76,42 +78,8 @@
#define kw_nargs(kw) (keyword_info[kw].nargs)
void dump_parser_state() {
- if (false) {
- struct listnode* node;
- list_for_each(node, &service_list) {
- service* svc = node_to_item(node, struct service, slist);
- INFO("service %s\n", svc->name);
- INFO(" class '%s'\n", svc->classname);
- INFO(" exec");
- for (int n = 0; n < svc->nargs; n++) {
- INFO(" '%s'", svc->args[n]);
- }
- INFO("\n");
- for (socketinfo* si = svc->sockets; si; si = si->next) {
- INFO(" socket %s %s 0%o\n", si->name, si->type, si->perm);
- }
- }
-
- 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);
- INFO("\n");
-
- struct listnode* node2;
- list_for_each(node2, &act->commands) {
- command* cmd = node_to_item(node2, struct command, clist);
- INFO(" %p", cmd->func);
- for (int n = 0; n < cmd->nargs; n++) {
- INFO(" %s", cmd->args[n]);
- }
- INFO("\n");
- }
- INFO("\n");
- }
- }
+ ServiceManager::GetInstance().DumpState();
+ ActionManager::GetInstance().DumpState();
}
static int lookup_keyword(const char *s)
@@ -206,6 +174,7 @@
break;
case 'w':
if (!strcmp(s, "rite")) return K_write;
+ if (!strcmp(s, "ritepid")) return K_writepid;
if (!strcmp(s, "ait")) return K_wait;
break;
}
@@ -215,27 +184,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)
+int expand_props(const std::string& src, std::string* dst) {
+ const char *src_ptr = src.c_str();
+
+ if (!dst) {
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;
- const char *src_ptr = src;
- int ret = 0;
- int left = dst_size - 1;
-
- if (!src || !dst || dst_size == 0)
- return -1;
+ }
/* - variables can either be $x.y or ${x.y}, in case they are only part
* of the string.
@@ -243,104 +197,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.c_str());
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) {
- ERROR("invalid zero-length prop name in '%s'\n", src);
+ if (prop_name.empty()) {
+ ERROR("invalid zero-length prop name in '%s'\n", src.c_str());
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.c_str());
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);
@@ -348,7 +273,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);
}
@@ -388,10 +315,14 @@
int nargs = 0;
+ //TODO: Use a parser with const input and remove this copy
+ std::vector<char> data_copy(data.begin(), data.end());
+ data_copy.push_back('\0');
+
parse_state state;
state.filename = fn;
state.line = 0;
- state.ptr = strdup(data.c_str()); // TODO: fix this code!
+ state.ptr = &data_copy[0];
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
@@ -427,15 +358,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)) {
@@ -450,550 +381,118 @@
return true;
}
-static int valid_name(const char *name)
-{
- if (strlen(name) > 16) {
- return 0;
+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;
}
- while (*name) {
- if (!isalnum(*name) && (*name != '_') && (*name != '-')) {
- return 0;
- }
- name++;
- }
- return 1;
-}
-
-struct service *service_find_by_name(const char *name)
-{
- struct listnode *node;
- struct service *svc;
- list_for_each(node, &service_list) {
- svc = node_to_item(node, struct service, slist);
- if (!strcmp(svc->name, name)) {
- return svc;
- }
- }
- return 0;
-}
-
-struct service *service_find_by_pid(pid_t pid)
-{
- struct listnode *node;
- struct service *svc;
- list_for_each(node, &service_list) {
- svc = node_to_item(node, struct service, slist);
- if (svc->pid == pid) {
- return svc;
- }
- }
- return 0;
-}
-
-struct service *service_find_by_keychord(int keychord_id)
-{
- struct listnode *node;
- struct service *svc;
- list_for_each(node, &service_list) {
- svc = node_to_item(node, struct service, slist);
- if (svc->keychord_id == keychord_id) {
- return svc;
- }
- }
- return 0;
-}
-
-void service_for_each(void (*func)(struct service *svc))
-{
- struct listnode *node;
- struct service *svc;
- list_for_each(node, &service_list) {
- svc = node_to_item(node, struct service, slist);
- func(svc);
- }
-}
-
-void service_for_each_class(const char *classname,
- void (*func)(struct service *svc))
-{
- struct listnode *node;
- struct service *svc;
- list_for_each(node, &service_list) {
- svc = node_to_item(node, struct service, slist);
- if (!strcmp(svc->classname, classname)) {
- func(svc);
- }
- }
-}
-
-void service_for_each_flags(unsigned matchflags,
- void (*func)(struct service *svc))
-{
- struct listnode *node;
- struct service *svc;
- list_for_each(node, &service_list) {
- svc = node_to_item(node, struct service, slist);
- if (svc->flags & matchflags) {
- func(svc);
- }
- }
-}
-
-void action_for_each_trigger(const char *trigger,
- void (*func)(struct action *act))
-{
- struct listnode *node, *node2;
- struct action *act;
- struct trigger *cur_trigger;
-
- list_for_each(node, &action_list) {
- act = node_to_item(node, struct action, alist);
- list_for_each(node2, &act->triggers) {
- cur_trigger = node_to_item(node2, struct trigger, nlist);
- if (!strcmp(cur_trigger->name, trigger)) {
- func(act);
+ 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;
}
-
-void queue_property_triggers(const char *name, const char *value)
-{
- struct listnode *node, *node2;
- struct action *act;
- struct trigger *cur_trigger;
- bool match;
- int name_length;
-
- list_for_each(node, &action_list) {
- act = node_to_item(node, struct action, alist);
- match = !name;
- list_for_each(node2, &act->triggers) {
- cur_trigger = node_to_item(node2, struct trigger, nlist);
- if (!strncmp(cur_trigger->name, "property:", strlen("property:"))) {
- const char *test = cur_trigger->name + strlen("property:");
- if (!match) {
- name_length = strlen(name);
- if (!strncmp(name, test, name_length) &&
- test[name_length] == '=' &&
- (!strcmp(test + name_length + 1, value) ||
- !strcmp(test + name_length + 1, "*"))) {
- match = true;
- continue;
- }
- } 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;
-
- /* does the property exist, and match the trigger value? */
- ret = property_get(prop_name, value);
- if (ret > 0 && (!strcmp(equals + 1, value) ||
- !strcmp(equals + 1, "*"))) {
- continue;
- }
- }
- }
- }
- }
- match = false;
- break;
- }
- if (match) {
- action_add_queue_tail(act);
- }
+bool init_parse_config(const char* path) {
+ if (is_dir(path)) {
+ return init_parse_config_dir(path);
}
-}
-
-void queue_all_property_triggers()
-{
- queue_property_triggers(NULL, NULL);
-}
-
-void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
-{
- action* act = (action*) calloc(1, sizeof(*act));
- trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
- cur_trigger->name = name;
- list_init(&act->triggers);
- list_add_tail(&act->triggers, &cur_trigger->nlist);
- list_init(&act->commands);
- list_init(&act->qlist);
-
- command* cmd = (command*) calloc(1, sizeof(*cmd));
- cmd->func = func;
- cmd->args[0] = const_cast<char*>(name);
- cmd->nargs = 1;
- list_add_tail(&act->commands, &cmd->clist);
-
- list_add_tail(&action_list, &act->alist);
- action_add_queue_tail(act);
-}
-
-void action_add_queue_tail(struct action *act)
-{
- if (list_empty(&act->qlist)) {
- list_add_tail(&action_queue, &act->qlist);
- }
-}
-
-struct action *action_remove_queue_head(void)
-{
- if (list_empty(&action_queue)) {
- return 0;
- } else {
- struct listnode *node = list_head(&action_queue);
- struct action *act = node_to_item(node, struct action, qlist);
- list_remove(node);
- list_init(node);
- return act;
- }
-}
-
-int action_queue_empty()
-{
- return list_empty(&action_queue);
-}
-
-service* make_exec_oneshot_service(int nargs, char** args) {
- // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
- int command_arg = 1;
- for (int i = 1; i < nargs; ++i) {
- if (strcmp(args[i], "--") == 0) {
- command_arg = i + 1;
- break;
- }
- }
- if (command_arg > 4 + NR_SVC_SUPP_GIDS) {
- ERROR("exec called with too many supplementary group ids\n");
- return NULL;
- }
-
- int argc = nargs - command_arg;
- char** argv = (args + command_arg);
- if (argc < 1) {
- ERROR("exec called without command\n");
- return NULL;
- }
-
- service* svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * argc);
- if (svc == NULL) {
- ERROR("Couldn't allocate service for exec of '%s': %s", argv[0], strerror(errno));
- return NULL;
- }
-
- if (command_arg > 2) {
- svc->seclabel = args[1];
- }
- if (command_arg > 3) {
- svc->uid = decode_uid(args[2]);
- }
- if (command_arg > 4) {
- svc->gid = decode_uid(args[3]);
- svc->nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */;
- for (size_t i = 0; i < svc->nr_supp_gids; ++i) {
- svc->supp_gids[i] = decode_uid(args[4 + i]);
- }
- }
-
- static int exec_count; // Every service needs a unique name.
- char* name = NULL;
- asprintf(&name, "exec %d (%s)", exec_count++, argv[0]);
- if (name == NULL) {
- ERROR("Couldn't allocate name for exec service '%s'\n", argv[0]);
- free(svc);
- return NULL;
- }
- svc->name = name;
- svc->classname = "default";
- svc->flags = SVC_EXEC | SVC_ONESHOT;
- svc->nargs = argc;
- memcpy(svc->args, argv, sizeof(char*) * svc->nargs);
- svc->args[argc] = NULL;
- list_add_tail(&service_list, &svc->slist);
- return svc;
+ return init_parse_config_file(path);
}
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
if (nargs < 3) {
parse_error(state, "services must have a name and a program\n");
- return 0;
+ return nullptr;
}
- if (!valid_name(args[1])) {
- parse_error(state, "invalid service name '%s'\n", args[1]);
- return 0;
- }
+ std::vector<std::string> str_args(args + 2, args + nargs);
+ std::string ret_err;
+ Service* svc = ServiceManager::GetInstance().AddNewService(args[1], "default",
+ str_args, &ret_err);
- service* svc = (service*) service_find_by_name(args[1]);
- if (svc) {
- parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
- return 0;
- }
-
- nargs -= 2;
- svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
if (!svc) {
- parse_error(state, "out of memory\n");
- return 0;
+ parse_error(state, "%s\n", ret_err.c_str());
}
- svc->name = strdup(args[1]);
- svc->classname = "default";
- memcpy(svc->args, args + 2, sizeof(char*) * nargs);
- trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
- svc->args[nargs] = 0;
- svc->nargs = nargs;
- list_init(&svc->onrestart.triggers);
- cur_trigger->name = "onrestart";
- list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist);
- list_init(&svc->onrestart.commands);
- list_add_tail(&service_list, &svc->slist);
+
return svc;
}
static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
- struct service *svc = (service*) state->context;
- struct command *cmd;
- int i, kw, kw_nargs;
-
if (nargs == 0) {
return;
}
- svc->ioprio_class = IoSchedClass_NONE;
+ Service* svc = static_cast<Service*>(state->context);
+ int kw = lookup_keyword(args[0]);
+ std::vector<std::string> str_args(args, args + nargs);
+ std::string ret_err;
+ bool ret = svc->HandleLine(kw, str_args, &ret_err);
- kw = lookup_keyword(args[0]);
- switch (kw) {
- case K_class:
- if (nargs != 2) {
- parse_error(state, "class option requires a classname\n");
- } else {
- svc->classname = args[1];
- }
- break;
- case K_console:
- svc->flags |= SVC_CONSOLE;
- break;
- case K_disabled:
- svc->flags |= SVC_DISABLED;
- svc->flags |= SVC_RC_DISABLED;
- break;
- case K_ioprio:
- if (nargs != 3) {
- parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");
- } else {
- svc->ioprio_pri = strtoul(args[2], 0, 8);
-
- if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {
- parse_error(state, "priority value must be range 0 - 7\n");
- break;
- }
-
- if (!strcmp(args[1], "rt")) {
- svc->ioprio_class = IoSchedClass_RT;
- } else if (!strcmp(args[1], "be")) {
- svc->ioprio_class = IoSchedClass_BE;
- } else if (!strcmp(args[1], "idle")) {
- svc->ioprio_class = IoSchedClass_IDLE;
- } else {
- parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");
- }
- }
- break;
- case K_group:
- if (nargs < 2) {
- parse_error(state, "group option requires a group id\n");
- } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
- parse_error(state, "group option accepts at most %d supp. groups\n",
- NR_SVC_SUPP_GIDS);
- } else {
- int n;
- svc->gid = decode_uid(args[1]);
- for (n = 2; n < nargs; n++) {
- svc->supp_gids[n-2] = decode_uid(args[n]);
- }
- svc->nr_supp_gids = n - 2;
- }
- break;
- case K_keycodes:
- if (nargs < 2) {
- parse_error(state, "keycodes option requires atleast one keycode\n");
- } else {
- svc->keycodes = (int*) malloc((nargs - 1) * sizeof(svc->keycodes[0]));
- if (!svc->keycodes) {
- parse_error(state, "could not allocate keycodes\n");
- } else {
- svc->nkeycodes = nargs - 1;
- for (i = 1; i < nargs; i++) {
- svc->keycodes[i - 1] = atoi(args[i]);
- }
- }
- }
- break;
- case K_oneshot:
- svc->flags |= SVC_ONESHOT;
- break;
- case K_onrestart:
- nargs--;
- args++;
- kw = lookup_keyword(args[0]);
- if (!kw_is(kw, COMMAND)) {
- parse_error(state, "invalid command '%s'\n", args[0]);
- break;
- }
- kw_nargs = kw_nargs(kw);
- if (nargs < kw_nargs) {
- parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,
- kw_nargs > 2 ? "arguments" : "argument");
- break;
- }
-
- cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs);
- cmd->func = kw_func(kw);
- cmd->nargs = nargs;
- memcpy(cmd->args, args, sizeof(char*) * nargs);
- list_add_tail(&svc->onrestart.commands, &cmd->clist);
- break;
- case K_critical:
- svc->flags |= SVC_CRITICAL;
- break;
- case K_setenv: { /* name value */
- if (nargs < 3) {
- parse_error(state, "setenv option requires name and value arguments\n");
- break;
- }
- svcenvinfo* ei = (svcenvinfo*) calloc(1, sizeof(*ei));
- if (!ei) {
- parse_error(state, "out of memory\n");
- break;
- }
- ei->name = args[1];
- ei->value = args[2];
- ei->next = svc->envvars;
- svc->envvars = ei;
- break;
- }
- case K_socket: {/* name type perm [ uid gid context ] */
- if (nargs < 4) {
- parse_error(state, "socket option requires name, type, perm arguments\n");
- break;
- }
- if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")
- && strcmp(args[2],"seqpacket")) {
- parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");
- break;
- }
- socketinfo* si = (socketinfo*) calloc(1, sizeof(*si));
- if (!si) {
- parse_error(state, "out of memory\n");
- break;
- }
- si->name = args[1];
- si->type = args[2];
- si->perm = strtoul(args[3], 0, 8);
- if (nargs > 4)
- si->uid = decode_uid(args[4]);
- if (nargs > 5)
- si->gid = decode_uid(args[5]);
- if (nargs > 6)
- si->socketcon = args[6];
- si->next = svc->sockets;
- svc->sockets = si;
- break;
- }
- case K_user:
- if (nargs != 2) {
- parse_error(state, "user option requires a user id\n");
- } else {
- svc->uid = decode_uid(args[1]);
- }
- break;
- case K_seclabel:
- if (nargs != 2) {
- parse_error(state, "seclabel option requires a label string\n");
- } else {
- svc->seclabel = args[1];
- }
- break;
-
- default:
- parse_error(state, "invalid option '%s'\n", args[0]);
+ if (!ret) {
+ parse_error(state, "%s\n", ret_err.c_str());
}
}
-static void *parse_action(struct parse_state *state, int nargs, char **args)
+static void *parse_action(struct parse_state* state, int nargs, char **args)
{
- struct trigger *cur_trigger;
- int i;
- if (nargs < 2) {
- parse_error(state, "actions must have a trigger\n");
- return 0;
+ std::string ret_err;
+ std::vector<std::string> triggers(args + 1, args + nargs);
+ Action* ret = ActionManager::GetInstance().AddNewAction(triggers, &ret_err);
+
+ if (!ret) {
+ parse_error(state, "%s\n", ret_err.c_str());
}
- action* act = (action*) calloc(1, sizeof(*act));
- list_init(&act->triggers);
+ return ret;
+}
- for (i = 1; i < nargs; i++) {
- if (!(i % 2)) {
- if (strcmp(args[i], "&&")) {
- struct listnode *node;
- struct listnode *node2;
- parse_error(state, "& is the only symbol allowed to concatenate actions\n");
- list_for_each_safe(node, node2, &act->triggers) {
- struct trigger *trigger = node_to_item(node, struct trigger, nlist);
- free(trigger);
- }
- free(act);
- return 0;
- } else
- continue;
- }
- cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
- cur_trigger->name = args[i];
- list_add_tail(&act->triggers, &cur_trigger->nlist);
+bool add_command_to_action(Action* action, const std::vector<std::string>& args,
+ const std::string& filename, int line, std::string* err)
+{
+ int kw;
+ size_t n;
+
+ kw = lookup_keyword(args[0].c_str());
+ if (!kw_is(kw, COMMAND)) {
+ *err = android::base::StringPrintf("invalid command '%s'\n", args[0].c_str());
+ return false;
}
- list_init(&act->commands);
- list_init(&act->qlist);
- list_add_tail(&action_list, &act->alist);
- /* XXX add to hash */
- return act;
+ n = kw_nargs(kw);
+ if (args.size() < n) {
+ *err = android::base::StringPrintf("%s requires %zu %s\n",
+ args[0].c_str(), n - 1,
+ n > 2 ? "arguments" : "argument");
+ return false;
+ }
+
+ action->AddCommand(kw_func(kw), args, filename, line);
+ return true;
}
static void parse_line_action(struct parse_state* state, int nargs, char **args)
{
- struct action *act = (action*) state->context;
- int kw, n;
-
if (nargs == 0) {
return;
}
- kw = lookup_keyword(args[0]);
- if (!kw_is(kw, COMMAND)) {
- parse_error(state, "invalid command '%s'\n", args[0]);
- return;
+ Action* action = static_cast<Action*>(state->context);
+ std::vector<std::string> str_args(args, args + nargs);
+ std::string ret_err;
+ bool ret = add_command_to_action(action, str_args, state->filename,
+ state->line, &ret_err);
+ if (!ret) {
+ parse_error(state, "%s\n", ret_err.c_str());
}
-
- n = kw_nargs(kw);
- if (nargs < n) {
- parse_error(state, "%s requires %d %s\n", args[0], n - 1,
- n > 2 ? "arguments" : "argument");
- return;
- }
- command* cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs);
- cmd->func = kw_func(kw);
- cmd->line = state->line;
- cmd->filename = state->filename;
- cmd->nargs = nargs;
- memcpy(cmd->args, args, sizeof(char*) * nargs);
- list_add_tail(&act->commands, &cmd->clist);
}
diff --git a/init/init_parser.h b/init/init_parser.h
index 90f880f..709dca8 100644
--- a/init/init_parser.h
+++ b/init/init_parser.h
@@ -17,23 +17,16 @@
#ifndef _INIT_INIT_PARSER_H_
#define _INIT_INIT_PARSER_H_
+#include <string>
+#include <vector>
+
#define INIT_PARSER_MAXARGS 64
-struct action;
-struct service;
+class Action;
-struct action *action_remove_queue_head(void);
-void action_add_queue_tail(struct action *act);
-void action_for_each_trigger(const char *trigger,
- void (*func)(struct action *act));
-int action_queue_empty(void);
-void queue_property_triggers(const char *name, const char *value);
-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);
-
-service* make_exec_oneshot_service(int argc, char** argv);
+bool init_parse_config(const char* path);
+int expand_props(const std::string& src, std::string* dst);
+bool add_command_to_action(Action* action, const std::vector<std::string>& args,
+ const std::string& filename, int line, std::string* err);
#endif
diff --git a/init/init_parser_test.cpp b/init/init_parser_test.cpp
index 170a73a..52aaa37 100644
--- a/init/init_parser_test.cpp
+++ b/init/init_parser_test.cpp
@@ -17,96 +17,97 @@
#include "init_parser.h"
#include "init.h"
+#include "service.h"
#include "util.h"
#include <errno.h>
#include <gtest/gtest.h>
-TEST(init_parser, make_exec_oneshot_service_invalid_syntax) {
- char* argv[10];
- memset(argv, 0, sizeof(argv));
+#include <string>
+#include <vector>
+TEST(init_parser, make_exec_oneshot_service_invalid_syntax) {
+ ServiceManager& sm = ServiceManager::GetInstance();
+ std::vector<std::string> args;
// Nothing.
- ASSERT_EQ(nullptr, make_exec_oneshot_service(0, argv));
+ ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args));
// No arguments to 'exec'.
- argv[0] = const_cast<char*>("exec");
- ASSERT_EQ(nullptr, make_exec_oneshot_service(1, argv));
+ args.push_back("exec");
+ ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args));
// No command in "exec --".
- argv[1] = const_cast<char*>("--");
- ASSERT_EQ(nullptr, make_exec_oneshot_service(2, argv));
+ args.push_back("--");
+ ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args));
}
TEST(init_parser, make_exec_oneshot_service_too_many_supplementary_gids) {
- int argc = 0;
- char* argv[4 + NR_SVC_SUPP_GIDS + 3];
- argv[argc++] = const_cast<char*>("exec");
- argv[argc++] = const_cast<char*>("seclabel");
- argv[argc++] = const_cast<char*>("root"); // uid.
- argv[argc++] = const_cast<char*>("root"); // gid.
+ ServiceManager& sm = ServiceManager::GetInstance();
+ std::vector<std::string> args;
+ args.push_back("exec");
+ args.push_back("seclabel");
+ args.push_back("root"); // uid.
+ args.push_back("root"); // gid.
for (int i = 0; i < NR_SVC_SUPP_GIDS; ++i) {
- argv[argc++] = const_cast<char*>("root"); // Supplementary gid.
+ args.push_back("root"); // Supplementary gid.
}
- argv[argc++] = const_cast<char*>("--");
- argv[argc++] = const_cast<char*>("/system/bin/id");
- argv[argc] = nullptr;
- ASSERT_EQ(nullptr, make_exec_oneshot_service(argc, argv));
+ args.push_back("--");
+ args.push_back("/system/bin/id");
+ ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args));
}
-static void Test_make_exec_oneshot_service(bool dash_dash, bool seclabel, bool uid, bool gid, bool supplementary_gids) {
- int argc = 0;
- char* argv[10];
- argv[argc++] = const_cast<char*>("exec");
+static void Test_make_exec_oneshot_service(bool dash_dash, bool seclabel, bool uid,
+ bool gid, bool supplementary_gids) {
+ ServiceManager& sm = ServiceManager::GetInstance();
+ std::vector<std::string> args;
+ args.push_back("exec");
if (seclabel) {
- argv[argc++] = const_cast<char*>("u:r:su:s0"); // seclabel
+ args.push_back("u:r:su:s0"); // seclabel
if (uid) {
- argv[argc++] = const_cast<char*>("log"); // uid
+ args.push_back("log"); // uid
if (gid) {
- argv[argc++] = const_cast<char*>("shell"); // gid
+ args.push_back("shell"); // gid
if (supplementary_gids) {
- argv[argc++] = const_cast<char*>("system"); // supplementary gid 0
- argv[argc++] = const_cast<char*>("adb"); // supplementary gid 1
+ args.push_back("system"); // supplementary gid 0
+ args.push_back("adb"); // supplementary gid 1
}
}
}
}
if (dash_dash) {
- argv[argc++] = const_cast<char*>("--");
+ args.push_back("--");
}
- argv[argc++] = const_cast<char*>("/system/bin/toybox");
- argv[argc++] = const_cast<char*>("id");
- argv[argc] = nullptr;
- service* svc = make_exec_oneshot_service(argc, argv);
+ args.push_back("/system/bin/toybox");
+ args.push_back("id");
+ Service* svc = sm.MakeExecOneshotService(args);
ASSERT_NE(nullptr, svc);
if (seclabel) {
- ASSERT_STREQ("u:r:su:s0", svc->seclabel);
+ ASSERT_EQ("u:r:su:s0", svc->seclabel());
} else {
- ASSERT_EQ(nullptr, svc->seclabel);
+ ASSERT_EQ("", svc->seclabel());
}
if (uid) {
- ASSERT_EQ(decode_uid("log"), svc->uid);
+ ASSERT_EQ(decode_uid("log"), svc->uid());
} else {
- ASSERT_EQ(0U, svc->uid);
+ ASSERT_EQ(0U, svc->uid());
}
if (gid) {
- ASSERT_EQ(decode_uid("shell"), svc->gid);
+ ASSERT_EQ(decode_uid("shell"), svc->gid());
} else {
- ASSERT_EQ(0U, svc->gid);
+ ASSERT_EQ(0U, svc->gid());
}
if (supplementary_gids) {
- ASSERT_EQ(2U, svc->nr_supp_gids);
- ASSERT_EQ(decode_uid("system"), svc->supp_gids[0]);
- ASSERT_EQ(decode_uid("adb"), svc->supp_gids[1]);
+ ASSERT_EQ(2U, svc->supp_gids().size());
+ ASSERT_EQ(decode_uid("system"), svc->supp_gids()[0]);
+ ASSERT_EQ(decode_uid("adb"), svc->supp_gids()[1]);
} else {
- ASSERT_EQ(0U, svc->nr_supp_gids);
+ ASSERT_EQ(0U, svc->supp_gids().size());
}
- ASSERT_EQ(2, svc->nargs);
- ASSERT_EQ("/system/bin/toybox", svc->args[0]);
- ASSERT_EQ("id", svc->args[1]);
- ASSERT_EQ(nullptr, svc->args[2]);
+ ASSERT_EQ(static_cast<std::size_t>(2), svc->args().size());
+ ASSERT_EQ("/system/bin/toybox", svc->args()[0]);
+ ASSERT_EQ("id", svc->args()[1]);
}
TEST(init_parser, make_exec_oneshot_service_with_everything) {
diff --git a/init/keychords.cpp b/init/keychords.cpp
index 10d9573..7a7838d 100644
--- a/init/keychords.cpp
+++ b/init/keychords.cpp
@@ -26,20 +26,21 @@
#include "init.h"
#include "log.h"
#include "property_service.h"
+#include "service.h"
static struct input_keychord *keychords = 0;
static int keychords_count = 0;
static int keychords_length = 0;
static int keychord_fd = -1;
-void add_service_keycodes(struct service *svc)
+void add_service_keycodes(Service* svc)
{
struct input_keychord *keychord;
- int i, size;
+ size_t i, size;
- if (svc->keycodes) {
+ if (!svc->keycodes().empty()) {
/* add a new keychord to the list */
- size = sizeof(*keychord) + svc->nkeycodes * sizeof(keychord->keycodes[0]);
+ size = sizeof(*keychord) + svc->keycodes().size() * sizeof(keychord->keycodes[0]);
keychords = (input_keychord*) realloc(keychords, keychords_length + size);
if (!keychords) {
ERROR("could not allocate keychords\n");
@@ -51,11 +52,11 @@
keychord = (struct input_keychord *)((char *)keychords + keychords_length);
keychord->version = KEYCHORD_VERSION;
keychord->id = keychords_count + 1;
- keychord->count = svc->nkeycodes;
- svc->keychord_id = keychord->id;
+ keychord->count = svc->keycodes().size();
+ svc->set_keychord_id(keychord->id);
- for (i = 0; i < svc->nkeycodes; i++) {
- keychord->keycodes[i] = svc->keycodes[i];
+ for (i = 0; i < svc->keycodes().size(); i++) {
+ keychord->keycodes[i] = svc->keycodes()[i];
}
keychords_count++;
keychords_length += size;
@@ -63,24 +64,22 @@
}
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")) {
- svc = service_find_by_keychord(id);
+ // Only handle keychords if adb is enabled.
+ std::string adb_enabled = property_get("init.svc.adbd");
+ if (adb_enabled == "running") {
+ Service* svc = ServiceManager::GetInstance().FindServiceByKeychord(id);
if (svc) {
- INFO("Starting service %s from keychord\n", svc->name);
- service_start(svc, NULL);
+ INFO("Starting service %s from keychord\n", svc->name().c_str());
+ svc->Start();
} else {
ERROR("service for keychord %d not found\n", id);
}
@@ -88,7 +87,7 @@
}
void keychord_init() {
- service_for_each(add_service_keycodes);
+ ServiceManager::GetInstance().ForEachService(add_service_keycodes);
// Nothing to do if no services require keychords.
if (!keychords) {
diff --git a/init/keywords.h b/init/keywords.h
index 37f01b8..922feee 100644
--- a/init/keywords.h
+++ b/init/keywords.h
@@ -1,53 +1,59 @@
#ifndef KEYWORD
-int do_bootchart_init(int nargs, char **args);
-int do_class_start(int nargs, char **args);
-int do_class_stop(int nargs, char **args);
-int do_class_reset(int nargs, char **args);
-int do_domainname(int nargs, char **args);
-int do_enable(int nargs, char **args);
-int do_exec(int nargs, char **args);
-int do_export(int nargs, char **args);
-int do_hostname(int nargs, char **args);
-int do_ifup(int nargs, char **args);
-int do_insmod(int nargs, char **args);
-int do_installkey(int nargs, char **args);
-int do_mkdir(int nargs, char **args);
-int do_mount_all(int nargs, char **args);
-int do_mount(int nargs, char **args);
-int do_powerctl(int nargs, char **args);
-int do_restart(int nargs, char **args);
-int do_restorecon(int nargs, char **args);
-int do_restorecon_recursive(int nargs, char **args);
-int do_rm(int nargs, char **args);
-int do_rmdir(int nargs, char **args);
-int do_setprop(int nargs, char **args);
-int do_setrlimit(int nargs, char **args);
-int do_start(int nargs, char **args);
-int do_stop(int nargs, char **args);
-int do_swapon_all(int nargs, char **args);
-int do_trigger(int nargs, char **args);
-int do_symlink(int nargs, char **args);
-int do_sysclktz(int nargs, char **args);
-int do_write(int nargs, char **args);
-int do_copy(int nargs, char **args);
-int do_chown(int nargs, char **args);
-int do_chmod(int nargs, char **args);
-int do_loglevel(int nargs, char **args);
-int do_load_persist_props(int nargs, char **args);
-int do_load_all_props(int nargs, char **args);
-int do_verity_load_state(int nargs, char **args);
-int do_verity_update_state(int nargs, char **args);
-int do_wait(int nargs, char **args);
+#include <string>
+#include <vector>
+int do_bootchart_init(const std::vector<std::string>& args);
+int do_class_start(const std::vector<std::string>& args);
+int do_class_stop(const std::vector<std::string>& args);
+int do_class_reset(const std::vector<std::string>& args);
+int do_domainname(const std::vector<std::string>& args);
+int do_enable(const std::vector<std::string>& args);
+int do_exec(const std::vector<std::string>& args);
+int do_export(const std::vector<std::string>& args);
+int do_hostname(const std::vector<std::string>& args);
+int do_ifup(const std::vector<std::string>& args);
+int do_insmod(const std::vector<std::string>& args);
+int do_installkey(const std::vector<std::string>& args);
+int do_mkdir(const std::vector<std::string>& args);
+int do_mount_all(const std::vector<std::string>& args);
+int do_mount(const std::vector<std::string>& args);
+int do_powerctl(const std::vector<std::string>& args);
+int do_restart(const std::vector<std::string>& args);
+int do_restorecon(const std::vector<std::string>& args);
+int do_restorecon_recursive(const std::vector<std::string>& args);
+int do_rm(const std::vector<std::string>& args);
+int do_rmdir(const std::vector<std::string>& args);
+int do_setprop(const std::vector<std::string>& args);
+int do_setrlimit(const std::vector<std::string>& args);
+int do_start(const std::vector<std::string>& args);
+int do_stop(const std::vector<std::string>& args);
+int do_swapon_all(const std::vector<std::string>& args);
+int do_trigger(const std::vector<std::string>& args);
+int do_symlink(const std::vector<std::string>& args);
+int do_sysclktz(const std::vector<std::string>& args);
+int do_write(const std::vector<std::string>& args);
+int do_copy(const std::vector<std::string>& args);
+int do_chown(const std::vector<std::string>& args);
+int do_chmod(const std::vector<std::string>& args);
+int do_loglevel(const std::vector<std::string>& args);
+int do_load_persist_props(const std::vector<std::string>& args);
+int do_load_all_props(const std::vector<std::string>& args);
+int do_verity_load_state(const std::vector<std::string>& args);
+int do_verity_update_state(const std::vector<std::string>& args);
+int do_wait(const std::vector<std::string>& args);
#define __MAKE_KEYWORD_ENUM__
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
K_UNKNOWN,
#endif
+ KEYWORD(bootchart_init, COMMAND, 0, do_bootchart_init)
+ KEYWORD(chmod, COMMAND, 2, do_chmod)
+ KEYWORD(chown, COMMAND, 2, do_chown)
KEYWORD(class, OPTION, 0, 0)
+ KEYWORD(class_reset, COMMAND, 1, do_class_reset)
KEYWORD(class_start, COMMAND, 1, do_class_start)
KEYWORD(class_stop, COMMAND, 1, do_class_stop)
- KEYWORD(class_reset, COMMAND, 1, do_class_reset)
KEYWORD(console, OPTION, 0, 0)
+ KEYWORD(copy, COMMAND, 2, do_copy)
KEYWORD(critical, OPTION, 0, 0)
KEYWORD(disabled, OPTION, 0, 0)
KEYWORD(domainname, COMMAND, 1, do_domainname)
@@ -57,16 +63,20 @@
KEYWORD(group, OPTION, 0, 0)
KEYWORD(hostname, COMMAND, 1, do_hostname)
KEYWORD(ifup, COMMAND, 1, do_ifup)
+ KEYWORD(import, SECTION, 1, 0)
KEYWORD(insmod, COMMAND, 1, do_insmod)
KEYWORD(installkey, COMMAND, 1, do_installkey)
- KEYWORD(import, SECTION, 1, 0)
+ KEYWORD(ioprio, OPTION, 0, 0)
KEYWORD(keycodes, OPTION, 0, 0)
+ KEYWORD(load_all_props, COMMAND, 0, do_load_all_props)
+ KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props)
+ KEYWORD(loglevel, COMMAND, 1, do_loglevel)
KEYWORD(mkdir, COMMAND, 1, do_mkdir)
KEYWORD(mount_all, COMMAND, 1, do_mount_all)
KEYWORD(mount, COMMAND, 3, do_mount)
- KEYWORD(on, SECTION, 0, 0)
KEYWORD(oneshot, OPTION, 0, 0)
KEYWORD(onrestart, OPTION, 0, 0)
+ KEYWORD(on, SECTION, 0, 0)
KEYWORD(powerctl, COMMAND, 1, do_powerctl)
KEYWORD(restart, COMMAND, 1, do_restart)
KEYWORD(restorecon, COMMAND, 1, do_restorecon)
@@ -82,22 +92,15 @@
KEYWORD(start, COMMAND, 1, do_start)
KEYWORD(stop, COMMAND, 1, do_stop)
KEYWORD(swapon_all, COMMAND, 1, do_swapon_all)
- KEYWORD(trigger, COMMAND, 1, do_trigger)
KEYWORD(symlink, COMMAND, 1, do_symlink)
KEYWORD(sysclktz, COMMAND, 1, do_sysclktz)
+ KEYWORD(trigger, COMMAND, 1, do_trigger)
KEYWORD(user, OPTION, 0, 0)
KEYWORD(verity_load_state, COMMAND, 0, do_verity_load_state)
KEYWORD(verity_update_state, COMMAND, 0, do_verity_update_state)
KEYWORD(wait, COMMAND, 1, do_wait)
KEYWORD(write, COMMAND, 2, do_write)
- KEYWORD(copy, COMMAND, 2, do_copy)
- KEYWORD(chown, COMMAND, 2, do_chown)
- KEYWORD(chmod, COMMAND, 2, do_chmod)
- KEYWORD(loglevel, COMMAND, 1, do_loglevel)
- KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props)
- KEYWORD(load_all_props, COMMAND, 0, do_load_all_props)
- KEYWORD(ioprio, OPTION, 0, 0)
- KEYWORD(bootchart_init, COMMAND, 0, do_bootchart_init)
+ KEYWORD(writepid, OPTION, 0, 0)
#ifdef __MAKE_KEYWORD_ENUM__
KEYWORD_COUNT,
};
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/parser/tokenizer.cpp b/init/parser/tokenizer.cpp
new file mode 100644
index 0000000..340e0d9
--- /dev/null
+++ b/init/parser/tokenizer.cpp
@@ -0,0 +1,129 @@
+// 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.
+
+#include "tokenizer.h"
+
+namespace init {
+
+Tokenizer::Tokenizer(const std::string& data)
+ : data_(data), eof_(false), pos_(0), tok_start_(0) {
+ current_.type = TOK_START;
+
+ if (data.size() > 0) {
+ cur_char_ = data[0];
+ } else {
+ eof_ = true;
+ cur_char_ = '\0';
+ }
+}
+
+Tokenizer::~Tokenizer() {}
+
+const Tokenizer::Token& Tokenizer::current() {
+ return current_;
+}
+
+bool Tokenizer::Next() {
+ while (!eof_) {
+ AdvWhiteSpace();
+
+ // Check for comments.
+ if (cur_char_ == '#') {
+ AdvChar();
+ // Skip rest of line
+ while (!eof_ && cur_char_ != '\n') {
+ AdvChar();
+ }
+ }
+
+ if (eof_) {
+ break;
+ }
+
+ if (cur_char_ == '\0') {
+ AdvChar();
+ } else if (cur_char_ == '\n') {
+ current_.type = TOK_NEWLINE;
+ current_.text.clear();
+ AdvChar();
+ return true;
+ } else if (cur_char_ == '\\') {
+ AdvChar(); // skip backslash
+ // This is line continuation so
+ // do not generated TOK_NEWLINE at
+ // the next \n.
+ AdvUntil('\n');
+ AdvChar(); // skip \n
+ } else if (cur_char_ == '\"') {
+ AdvChar();
+ StartText();
+ // Grab everything until the next quote.
+ AdvUntil('\"');
+ EndText();
+ AdvChar(); // skip quote.
+ return true;
+ } else {
+ StartText();
+ AdvText();
+ EndText();
+ return true;
+ }
+ }
+ current_.type = TOK_END;
+ current_.text.clear();
+ return false;
+}
+
+void Tokenizer::AdvChar() {
+ pos_++;
+ if (pos_ < data_.size()) {
+ cur_char_ = data_[pos_];
+ } else {
+ eof_ = true;
+ cur_char_ = '\0';
+ }
+}
+
+void Tokenizer::AdvWhiteSpace() {
+ while (cur_char_ == '\t' || cur_char_ == '\r' || cur_char_ == ' ') {
+ AdvChar();
+ }
+}
+
+void Tokenizer::AdvUntil(char x) {
+ while (!eof_ && cur_char_ != x) {
+ AdvChar();
+ }
+}
+
+void Tokenizer::AdvText() {
+ while (cur_char_ != '\t' && cur_char_ != '\r' && cur_char_ != '\0' &&
+ cur_char_ != ' ' && cur_char_ != '\n' && cur_char_ != '#') {
+ AdvChar();
+ }
+}
+
+void Tokenizer::StartText() {
+ current_.text.clear();
+ tok_start_ = pos_;
+ current_.type = TOK_TEXT;
+}
+
+void Tokenizer::EndText() {
+ if (pos_ != tok_start_) {
+ current_.text.append(data_, tok_start_, pos_ - tok_start_);
+ }
+}
+
+} // namespace init
\ No newline at end of file
diff --git a/init/parser/tokenizer.h b/init/parser/tokenizer.h
new file mode 100644
index 0000000..8312a08
--- /dev/null
+++ b/init/parser/tokenizer.h
@@ -0,0 +1,74 @@
+// 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.
+
+#ifndef _INIT_PARSER_TOKENIZER_H
+#define _INIT_PARSER_TOKENIZER_H
+
+#include <string>
+
+namespace init {
+
+// Used to tokenize a std::string.
+// Call Next() to advance through each token until it returns false,
+// indicating there are no more tokens left in the string.
+// The current token can be accessed with current(), which returns
+// a Token.
+// Supported tokens are:
+// TOK_START - Next() has yet to be called
+// TOK_END - At the end of string
+// TOK_NEWLINE - The end of a line denoted by \n.
+// TOK_TEXT - A word.
+// Comments are denoted with '#' and the tokenizer will ignore
+// the rest of the line.
+// Double quotes can be used to insert whitespace into words.
+// A backslash at the end of a line denotes continuation and
+// a TOK_NEWLINE will not be generated for that line.
+class Tokenizer {
+ public:
+ Tokenizer(const std::string& data);
+ ~Tokenizer();
+
+ enum TokenType { TOK_START, TOK_END, TOK_NEWLINE, TOK_TEXT };
+ struct Token {
+ TokenType type;
+ std::string text;
+ };
+
+ // Returns the curret token.
+ const Token& current();
+
+ // Move to the next token, returns false at the end of input.
+ bool Next();
+
+ private:
+ void GetData();
+ void AdvChar();
+ void AdvText();
+ void AdvUntil(char x);
+ void AdvWhiteSpace();
+ void StartText();
+ void EndText();
+
+ const std::string& data_;
+ Token current_;
+
+ bool eof_;
+ size_t pos_;
+ char cur_char_;
+ size_t tok_start_;
+};
+
+} // namespace init
+
+#endif
diff --git a/init/parser/tokenizer_test.cpp b/init/parser/tokenizer_test.cpp
new file mode 100644
index 0000000..c4a48df
--- /dev/null
+++ b/init/parser/tokenizer_test.cpp
@@ -0,0 +1,230 @@
+// 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.
+
+#include "tokenizer.h"
+
+#include <errno.h>
+#include <gtest/gtest.h>
+
+#include <string>
+
+namespace init {
+
+#define SETUP_TEST(test_data) \
+ std::string data(test_data); \
+ Tokenizer tokenizer(data); \
+ ASSERT_EQ(Tokenizer::TOK_START, tokenizer.current().type)
+
+#define ASSERT_TEXT_TOKEN(test_text) \
+ ASSERT_TRUE(tokenizer.Next()); \
+ ASSERT_EQ(test_text, tokenizer.current().text); \
+ ASSERT_EQ(Tokenizer::TOK_TEXT, tokenizer.current().type)
+
+#define ASSERT_NEWLINE_TOKEN() \
+ ASSERT_TRUE(tokenizer.Next()); \
+ ASSERT_EQ(Tokenizer::TOK_NEWLINE, tokenizer.current().type)
+
+TEST(Tokenizer, Empty) {
+ SETUP_TEST("");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, Simple) {
+ SETUP_TEST("test");
+ ASSERT_TEXT_TOKEN("test");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, LeadingWhiteSpace) {
+ SETUP_TEST(" \t \r test");
+ ASSERT_TEXT_TOKEN("test");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, TrailingWhiteSpace) {
+ SETUP_TEST("test \t \r ");
+ ASSERT_TEXT_TOKEN("test");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, WhiteSpace) {
+ SETUP_TEST(" \t \r test \t \r ");
+ ASSERT_TEXT_TOKEN("test");
+
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, TwoTokens) {
+ SETUP_TEST(" foo bar ");
+ ASSERT_TEXT_TOKEN("foo");
+ ASSERT_TEXT_TOKEN("bar");
+
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, MultiToken) {
+ SETUP_TEST("one two three four five");
+ ASSERT_TEXT_TOKEN("one");
+ ASSERT_TEXT_TOKEN("two");
+ ASSERT_TEXT_TOKEN("three");
+ ASSERT_TEXT_TOKEN("four");
+ ASSERT_TEXT_TOKEN("five");
+
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, NewLine) {
+ SETUP_TEST("\n");
+ ASSERT_NEWLINE_TOKEN();
+
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, TextNewLine) {
+ SETUP_TEST("test\n");
+ ASSERT_TEXT_TOKEN("test");
+ ASSERT_NEWLINE_TOKEN();
+
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, MultiTextNewLine) {
+ SETUP_TEST("one\ntwo\nthree\n");
+ ASSERT_TEXT_TOKEN("one");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_TEXT_TOKEN("two");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_TEXT_TOKEN("three");
+ ASSERT_NEWLINE_TOKEN();
+
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, MultiTextNewLineNoLineEnding) {
+ SETUP_TEST("one\ntwo\nthree");
+ ASSERT_TEXT_TOKEN("one");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_TEXT_TOKEN("two");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_TEXT_TOKEN("three");
+
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, Comment) {
+ SETUP_TEST("#test");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, CommentWhiteSpace) {
+ SETUP_TEST(" \t \r #test \t \r ");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, CommentNewLine) {
+ SETUP_TEST(" #test \n");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, CommentTwoNewLine) {
+ SETUP_TEST(" #test \n#test");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, CommentWithText) {
+ SETUP_TEST("foo bar #test");
+ ASSERT_TEXT_TOKEN("foo");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, CommentWithTextNoSpace) {
+ SETUP_TEST("foo bar#test");
+ ASSERT_TEXT_TOKEN("foo");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, CommentWithTextLineFeed) {
+ SETUP_TEST("foo bar #test\n");
+ ASSERT_TEXT_TOKEN("foo");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, CommentWithMultiTextLineFeed) {
+ SETUP_TEST("#blah\nfoo bar #test\n#blah");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_TEXT_TOKEN("foo");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_NEWLINE_TOKEN();
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, SimpleEscaped) {
+ SETUP_TEST("fo\\no bar");
+ ASSERT_TEXT_TOKEN("fo\\no");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, EscapedLineContNoLineFeed) {
+ SETUP_TEST("fo\\no bar \\ hello");
+ ASSERT_TEXT_TOKEN("fo\\no");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, EscapedLineContLineFeed) {
+ SETUP_TEST("fo\\no bar \\ hello\n");
+ ASSERT_TEXT_TOKEN("fo\\no");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, EscapedLineCont) {
+ SETUP_TEST("fo\\no bar \\\ntest");
+ ASSERT_TEXT_TOKEN("fo\\no");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_TEXT_TOKEN("test");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, EscapedLineContWithBadChars) {
+ SETUP_TEST("fo\\no bar \\bad bad bad\ntest");
+ ASSERT_TEXT_TOKEN("fo\\no");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_TEXT_TOKEN("test");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, SimpleQuotes) {
+ SETUP_TEST("foo \"single token\" bar");
+ ASSERT_TEXT_TOKEN("foo");
+ ASSERT_TEXT_TOKEN("single token");
+ ASSERT_TEXT_TOKEN("bar");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+TEST(Tokenizer, BadQuotes) {
+ SETUP_TEST("foo \"single token");
+ ASSERT_TEXT_TOKEN("foo");
+ ASSERT_TEXT_TOKEN("single token");
+ ASSERT_FALSE(tokenizer.Next());
+}
+
+} // namespace init
diff --git a/init/perfboot.py b/init/perfboot.py
new file mode 100755
index 0000000..2a17ab6
--- /dev/null
+++ b/init/perfboot.py
@@ -0,0 +1,461 @@
+#!/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.
+"""Record the event logs during boot and output them to a file.
+
+This script repeats the record of each event log during Android boot specified
+times. By default, interval between measurements is adjusted in such a way that
+CPUs are cooled down sufficiently to avoid boot time slowdown caused by CPU
+thermal throttling. The result is output in a tab-separated value format.
+
+Examples:
+
+Repeat measurements 10 times. Interval between iterations is adjusted based on
+CPU temperature of the device.
+
+$ ./perfboot.py --iterations=10
+
+Repeat measurements 20 times. 60 seconds interval is taken between each
+iteration.
+
+$ ./perfboot.py --iterations=20 --interval=60
+
+Repeat measurements 20 times, show verbose output, output the result to
+data.tsv, and read event tags from eventtags.txt.
+
+$ ./perfboot.py --iterations=30 -v --output=data.tsv --tags=eventtags.txt
+"""
+
+import argparse
+import atexit
+import cStringIO
+import glob
+import inspect
+import logging
+import math
+import os
+import re
+import subprocess
+import sys
+import threading
+import time
+
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+import adb
+
+# The default event tags to record.
+_DEFAULT_EVENT_TAGS = [
+ 'boot_progress_start',
+ 'boot_progress_preload_start',
+ 'boot_progress_preload_end',
+ 'boot_progress_system_run',
+ 'boot_progress_pms_start',
+ 'boot_progress_pms_system_scan_start',
+ 'boot_progress_pms_data_scan_start',
+ 'boot_progress_pms_scan_end',
+ 'boot_progress_pms_ready',
+ 'boot_progress_ams_ready',
+ 'boot_progress_enable_screen',
+ 'sf_stop_bootanim',
+ 'wm_boot_animation_done',
+]
+
+
+class IntervalAdjuster(object):
+ """A helper class to take suffficient interval between iterations."""
+
+ # CPU temperature values per product used to decide interval
+ _CPU_COOL_DOWN_THRESHOLDS = {
+ 'flo': 40,
+ 'flounder': 40000,
+ 'razor': 40,
+ 'volantis': 40000,
+ }
+ # The interval between CPU temperature checks
+ _CPU_COOL_DOWN_WAIT_INTERVAL = 10
+ # The wait time used when the value of _CPU_COOL_DOWN_THRESHOLDS for
+ # the product is not defined.
+ _CPU_COOL_DOWN_WAIT_TIME_DEFAULT = 120
+
+ def __init__(self, interval, device):
+ self._interval = interval
+ self._device = device
+ self._temp_paths = device.shell(
+ ['ls', '/sys/class/thermal/thermal_zone*/temp']).splitlines()
+ self._product = device.get_prop('ro.build.product')
+ self._waited = False
+
+ def wait(self):
+ """Waits certain amount of time for CPUs cool-down."""
+ if self._interval is None:
+ self._wait_cpu_cool_down(self._product, self._temp_paths)
+ else:
+ if self._waited:
+ print 'Waiting for %d seconds' % self._interval
+ time.sleep(self._interval)
+ self._waited = True
+
+ def _get_cpu_temp(self, threshold):
+ max_temp = 0
+ for temp_path in self._temp_paths:
+ temp = int(self._device.shell(['cat', temp_path]).rstrip())
+ max_temp = max(max_temp, temp)
+ if temp >= threshold:
+ return temp
+ return max_temp
+
+ def _wait_cpu_cool_down(self, product, temp_paths):
+ threshold = IntervalAdjuster._CPU_COOL_DOWN_THRESHOLDS.get(
+ self._product)
+ if threshold is None:
+ print 'No CPU temperature threshold is set for ' + self._product
+ print ('Just wait %d seconds' %
+ IntervalAdjuster._CPU_COOL_DOWN_WAIT_TIME_DEFAULT)
+ time.sleep(IntervalAdjuster._CPU_COOL_DOWN_WAIT_TIME_DEFAULT)
+ return
+ while True:
+ temp = self._get_cpu_temp(threshold)
+ if temp < threshold:
+ logging.info('Current CPU temperature %s' % temp)
+ return
+ print 'Waiting until CPU temperature (%d) falls below %d' % (
+ temp, threshold)
+ time.sleep(IntervalAdjuster._CPU_COOL_DOWN_WAIT_INTERVAL)
+
+
+class WatchdogTimer(object):
+ """A timer that makes is_timedout() return true in |timeout| seconds."""
+ def __init__(self, timeout):
+ self._timedout = False
+
+ def notify_timeout():
+ self._timedout = True
+ self._timer = threading.Timer(timeout, notify_timeout)
+ self._timer.daemon = True
+ self._timer.start()
+
+ def is_timedout(self):
+ return self._timedout
+
+ def cancel(self):
+ self._timer.cancel()
+
+
+def readlines_unbuffered(proc):
+ """Read lines from |proc|'s standard out without buffering."""
+ while True:
+ buf = []
+ c = proc.stdout.read(1)
+ if c == '' and proc.poll() is not None:
+ break
+ while c != '\n':
+ if c == '' and proc.poll() is not None:
+ break
+ buf.append(c)
+ c = proc.stdout.read(1)
+ yield ''.join(buf)
+
+
+def disable_dropbox(device):
+ """Removes the files created by Dropbox and avoids creating the files."""
+ device.root()
+ device.wait()
+ device.shell(['rm', '-rf', '/system/data/dropbox'])
+ original_dropbox_max_files = device.shell(
+ ['settings', 'get', 'global', 'dropbox_max_files']).rstrip()
+ device.shell(['settings', 'put', 'global', 'dropbox_max_files', '0'])
+ return original_dropbox_max_files
+
+
+def restore_dropbox(device, original_dropbox_max_files):
+ """Restores the dropbox_max_files setting."""
+ device.root()
+ device.wait()
+ if original_dropbox_max_files == 'null':
+ device.shell(['settings', 'delete', 'global', 'dropbox_max_files'])
+ else:
+ device.shell(['settings', 'put', 'global', 'dropbox_max_files',
+ original_dropbox_max_files])
+
+
+def init_perf(device, output, record_list, tags):
+ device.wait()
+ build_type = device.get_prop('ro.build.type')
+ original_dropbox_max_files = None
+ if build_type != 'user':
+ # Workaround for Dropbox issue (http://b/20890386).
+ original_dropbox_max_files = disable_dropbox(device)
+
+ def cleanup():
+ try:
+ if record_list:
+ print_summary(record_list, tags[-1])
+ output_results(output, record_list, tags)
+ if original_dropbox_max_files is not None:
+ restore_dropbox(device, original_dropbox_max_files)
+ except (subprocess.CalledProcessError, RuntimeError):
+ pass
+ atexit.register(cleanup)
+
+
+def check_dm_verity_settings(device):
+ device.wait()
+ for partition in ['system', 'vendor']:
+ verity_mode = device.get_prop('partition.%s.verified' % partition)
+ if verity_mode is None:
+ logging.warning('dm-verity is not enabled for /%s. Did you run '
+ 'adb disable-verity? That may skew the result.',
+ partition)
+
+
+def read_event_tags(tags_file):
+ """Reads event tags from |tags_file|."""
+ if not tags_file:
+ return _DEFAULT_EVENT_TAGS
+ tags = []
+ with open(tags_file) as f:
+ for line in f:
+ if '#' in line:
+ line = line[:line.find('#')]
+ line = line.strip()
+ if line:
+ tags.append(line)
+ return tags
+
+
+def make_event_tags_re(tags):
+ """Makes a regular expression object that matches event logs of |tags|."""
+ return re.compile(r'(?P<pid>[0-9]+) +[0-9]+ I (?P<tag>%s): (?P<time>\d+)' %
+ '|'.join(tags))
+
+
+def filter_event_tags(tags, device):
+ """Drop unknown tags not listed in device's event-log-tags file."""
+ device.wait()
+ supported_tags = set()
+ for l in device.shell(['cat', '/system/etc/event-log-tags']).splitlines():
+ tokens = l.split(' ')
+ if len(tokens) >= 2:
+ supported_tags.add(tokens[1])
+ filtered = []
+ for tag in tags:
+ if tag in supported_tags:
+ filtered.append(tag)
+ else:
+ logging.warning('Unknown tag \'%s\'. Ignoring...', tag)
+ return filtered
+
+
+def get_values(record, tag):
+ """Gets values that matches |tag| from |record|."""
+ keys = [key for key in record.keys() if key[0] == tag]
+ return [record[k] for k in sorted(keys)]
+
+
+def get_last_value(record, tag):
+ """Gets the last value that matches |tag| from |record|."""
+ values = get_values(record, tag)
+ if not values:
+ return 0
+ return values[-1]
+
+
+def output_results(filename, record_list, tags):
+ """Outputs |record_list| into |filename| in a TSV format."""
+ # First, count the number of the values of each tag.
+ # This is for dealing with events that occur multiple times.
+ # For instance, boot_progress_preload_start and boot_progress_preload_end
+ # are recorded twice on 64-bit system. One is for 64-bit zygote process
+ # and the other is for 32-bit zygote process.
+ values_counter = {}
+ for record in record_list:
+ for tag in tags:
+ # Some record might lack values for some tags due to unanticipated
+ # problems (e.g. timeout), so take the maximum count among all the
+ # record.
+ values_counter[tag] = max(values_counter.get(tag, 1),
+ len(get_values(record, tag)))
+
+ # Then creates labels for the data. If there are multiple values for one
+ # tag, labels for these values are numbered except the first one as
+ # follows:
+ #
+ # event_tag event_tag2 event_tag3
+ #
+ # The corresponding values are sorted in an ascending order of PID.
+ labels = []
+ for tag in tags:
+ for i in range(1, values_counter[tag] + 1):
+ labels.append('%s%s' % (tag, '' if i == 1 else str(i)))
+
+ # Finally write the data into the file.
+ with open(filename, 'w') as f:
+ f.write('\t'.join(labels) + '\n')
+ for record in record_list:
+ line = cStringIO.StringIO()
+ invalid_line = False
+ for i, tag in enumerate(tags):
+ if i != 0:
+ line.write('\t')
+ values = get_values(record, tag)
+ if len(values) < values_counter[tag]:
+ invalid_line = True
+ # Fill invalid record with 0
+ values += [0] * (values_counter[tag] - len(values))
+ line.write('\t'.join(str(t) for t in values))
+ if invalid_line:
+ logging.error('Invalid record found: ' + line.getvalue())
+ line.write('\n')
+ f.write(line.getvalue())
+ print 'Wrote: ' + filename
+
+
+def median(data):
+ """Calculates the median value from |data|."""
+ data = sorted(data)
+ n = len(data)
+ if n % 2 == 1:
+ return data[n / 2]
+ else:
+ n2 = n / 2
+ return (data[n2 - 1] + data[n2]) / 2.0
+
+
+def mean(data):
+ """Calculates the mean value from |data|."""
+ return float(sum(data)) / len(data)
+
+
+def stddev(data):
+ """Calculates the standard deviation value from |value|."""
+ m = mean(data)
+ return math.sqrt(sum((x - m) ** 2 for x in data) / len(data))
+
+
+def print_summary(record_list, end_tag):
+ """Prints the summary of |record_list|."""
+ # Filter out invalid data.
+ end_times = [get_last_value(record, end_tag) for record in record_list
+ if get_last_value(record, end_tag) != 0]
+ print 'mean: ', mean(end_times)
+ print 'median:', median(end_times)
+ print 'standard deviation:', stddev(end_times)
+
+
+def do_iteration(device, interval_adjuster, event_tags_re, end_tag):
+ """Measures the boot time once."""
+ device.wait()
+ interval_adjuster.wait()
+ device.reboot()
+ print 'Rebooted the device'
+ record = {}
+ booted = False
+ while not booted:
+ device.wait()
+ # Stop the iteration if it does not finish within 120 seconds.
+ timeout = 120
+ t = WatchdogTimer(timeout)
+ p = subprocess.Popen(
+ ['adb', 'logcat', '-b', 'events', '-v', 'threadtime'],
+ stdout=subprocess.PIPE)
+ for line in readlines_unbuffered(p):
+ if t.is_timedout():
+ print '*** Timed out ***'
+ return record
+ m = event_tags_re.search(line)
+ if not m:
+ continue
+ tag = m.group('tag')
+ event_time = int(m.group('time'))
+ pid = m.group('pid')
+ record[(tag, pid)] = event_time
+ print 'Event log recorded: %s (%s) - %d ms' % (
+ tag, pid, event_time)
+ if tag == end_tag:
+ booted = True
+ t.cancel()
+ break
+ return record
+
+
+def parse_args():
+ """Parses the command line arguments."""
+ parser = argparse.ArgumentParser(
+ description=inspect.getdoc(sys.modules[__name__]),
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('--iterations', type=int, default=5,
+ help='Number of times to repeat boot measurements.')
+ parser.add_argument('--interval', type=int,
+ help=('Duration between iterations. If this is not '
+ 'set explicitly, durations are determined '
+ 'adaptively based on CPUs temperature.'))
+ parser.add_argument('-o', '--output', help='File name of output data.')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='Show verbose output.')
+ parser.add_argument('-s', '--serial', default=os.getenv('ANDROID_SERIAL'),
+ help='Adb device serial number.')
+ parser.add_argument('-t', '--tags', help='Specify the filename from which '
+ 'event tags are read. Every line contains one event '
+ 'tag and the last event tag is used to detect that '
+ 'the device has finished booting unless --end-tag is '
+ 'specified.')
+ parser.add_argument('--end-tag', help='An event tag on which the script '
+ 'stops measuring the boot time.')
+ parser.add_argument('--apk-dir', help='Specify the directory which contains '
+ 'APK files to be installed before measuring boot time.')
+ return parser.parse_args()
+
+
+def install_apks(device, apk_dir):
+ for apk in glob.glob(os.path.join(apk_dir, '*.apk')):
+ print 'Installing: ' + apk
+ device.install(apk, replace=True)
+
+
+def main():
+ args = parse_args()
+ if args.verbose:
+ logging.getLogger().setLevel(logging.INFO)
+
+ device = adb.get_device(args.serial)
+
+ if not args.output:
+ device.wait()
+ args.output = 'perf-%s-%s.tsv' % (
+ device.get_prop('ro.build.flavor'),
+ device.get_prop('ro.build.version.incremental'))
+ check_dm_verity_settings(device)
+
+ if args.apk_dir:
+ install_apks(device, args.apk_dir)
+
+ record_list = []
+ event_tags = filter_event_tags(read_event_tags(args.tags), device)
+ end_tag = args.end_tag or event_tags[-1]
+ if end_tag not in event_tags:
+ sys.exit('%s is not a valid tag.' % end_tag)
+ event_tags = event_tags[0 : event_tags.index(end_tag) + 1]
+ init_perf(device, args.output, record_list, event_tags)
+ interval_adjuster = IntervalAdjuster(args.interval, device)
+ event_tags_re = make_event_tags_re(event_tags)
+
+ for i in range(args.iterations):
+ print 'Run #%d ' % i
+ record = do_iteration(
+ device, interval_adjuster, event_tags_re, end_tag)
+ record_list.append(record)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 0ee0351..aa939a5 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -92,9 +92,6 @@
static int check_mac_perms(const char *name, char *sctx)
{
- if (is_selinux_enabled() <= 0)
- return 1;
-
char *tctx = NULL;
int result = 0;
@@ -144,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)
@@ -494,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);
}
}
@@ -514,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 6b9c42d..d70c6f3 100644
--- a/init/readme.txt
+++ b/init/readme.txt
@@ -60,36 +60,36 @@
runs the service.
critical
- This is a device-critical service. If it exits more than four times in
- four minutes, the device will reboot into recovery mode.
+ This is a device-critical service. If it exits more than four times in
+ four minutes, the device will reboot into recovery mode.
disabled
- This service will not automatically start with its class.
- It must be explicitly started by name.
+ This service will not automatically start with its class.
+ It must be explicitly started by name.
setenv <name> <value>
- Set the environment variable <name> to <value> in the launched process.
+ Set the environment variable <name> to <value> in the launched process.
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]
- Create a unix domain socket named /dev/socket/<name> and pass
- its fd to the launched process. <type> must be "dgram", "stream" or "seqpacket".
- User and group default to 0.
- 'seclabel' is the SELinux security context for the socket.
- It defaults to the service security context, as specified by seclabel or
- computed based on the service executable file security context.
+ Create a unix domain socket named /dev/socket/<name> and pass
+ its fd to the launched process. <type> must be "dgram", "stream" or "seqpacket".
+ User and group default to 0.
+ 'seclabel' is the SELinux security context for the socket.
+ It defaults to the service security context, as specified by seclabel or
+ computed based on the service executable file security context.
user <username>
- Change to username before exec'ing this service.
- Currently defaults to root. (??? probably should default to nobody)
- Currently, if your process requires linux capabilities then you cannot use
- this command. You must instead request the capabilities in-process while
- still root, and then drop to your desired uid.
+ Change to username before exec'ing this service.
+ Currently defaults to root. (??? probably should default to nobody)
+ Currently, if your process requires linux capabilities then you cannot use
+ this command. You must instead request the capabilities in-process while
+ still root, and then drop to your desired uid.
group <groupname> [ <groupname> ]*
- Change to groupname before exec'ing this service. Additional
- groupnames beyond the (required) first one are used to set the
- supplemental groups of the process (via setgroups()).
- Currently defaults to root. (??? probably should default to nobody)
+ Change to groupname before exec'ing this service. Additional
+ groupnames beyond the (required) first one are used to set the
+ supplemental groups of the process (via setgroups()).
+ Currently defaults to root. (??? probably should default to nobody)
seclabel <seclabel>
Change to 'seclabel' before exec'ing this service.
@@ -99,22 +99,26 @@
If not specified and no transition is defined in policy, defaults to the init context.
oneshot
- Do not restart the service when it exits.
+ Do not restart the service when it exits.
class <name>
- Specify a class name for the service. All services in a
- named class may be started or stopped together. A service
- is in the class "default" if one is not specified via the
- class option.
+ Specify a class name for the service. All services in a
+ named class may be started or stopped together. A service
+ is in the class "default" if one is not specified via the
+ class option.
onrestart
- Execute a Command (see below) when service restarts.
+ Execute a Command (see below) when service restarts.
+
+writepid <file...>
+ Write the child's pid to the given files when it forks. Meant for
+ cgroup/cpuset usage.
Triggers
--------
- Triggers are strings which can be used to match certain kinds
- of events and used to cause an action to occur.
+Triggers are strings which can be used to match certain kinds
+of events and used to cause an action to occur.
boot
This is the first trigger that will occur when init starts
@@ -180,7 +184,7 @@
Fork and execute command with the given arguments. The command starts
after "--" so that an optional security context, user, and supplementary
groups can be provided. No other commands will be run until this one
- finishes.
+ finishes. <seclabel> can be a - to denote default.
export <name> <value>
Set the environment variable <name> equal to <value> in the
@@ -193,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>
@@ -347,6 +354,52 @@
actually started init.
+Comparing two bootcharts
+------------------------
+A handy script named compare-bootcharts.py can be used to compare the
+start/end time of selected processes. The aforementioned grab-bootchart.sh
+will leave a bootchart tarball named bootchart.tgz at /tmp/android-bootchart.
+If two such barballs are preserved on the host machine under different
+directories, the script can list the timestamps differences. For example:
+
+Usage: system/core/init/compare-bootcharts.py base_bootchart_dir
+ exp_bootchart_dir
+
+process: baseline experiment (delta)
+ - Unit is ms (a jiffy is 10 ms on the system)
+------------------------------------
+/init: 50 40 (-10)
+/system/bin/surfaceflinger: 4320 4470 (+150)
+/system/bin/bootanimation: 6980 6990 (+10)
+zygote64: 10410 10640 (+230)
+zygote: 10410 10640 (+230)
+system_server: 15350 15150 (-200)
+bootanimation ends at: 33790 31230 (-2560)
+
+
+Systrace
+--------
+Systrace [1] can be used for obtaining performance analysis reports during boot
+time on userdebug or eng builds.
+Here is an example of trace events of "wm" and "am" categories:
+
+ $ANDROID_BUILD_TOP/external/chromium-trace/systrace.py wm am --boot
+
+This command will cause the device to reboot. After the device is rebooted and
+the boot sequence has finished, the trace report is obtained from the device
+and written as trace.html on the host by hitting Ctrl+C.
+
+LIMITATION
+Recording trace events is started after persistent properties are loaded, so
+the trace events that are emitted before that are not recorded. Several
+services such as vold, surfaceflinger, and servicemanager are affected by this
+limitation since they are started before persistent properties are loaded.
+Zygote initialization and the processes that are forked from the zygote are not
+affected.
+
+[1] http://developer.android.com/tools/help/systrace.html
+
+
Debugging init
--------------
By default, programs executed by init will drop stdout and stderr into
diff --git a/init/service.cpp b/init/service.cpp
new file mode 100644
index 0000000..a370d25
--- /dev/null
+++ b/init/service.cpp
@@ -0,0 +1,805 @@
+/*
+ * 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.
+ */
+
+#include "service.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <selinux/selinux.h>
+
+#include <base/file.h>
+#include <base/stringprintf.h>
+#include <cutils/android_reboot.h>
+#include <cutils/sockets.h>
+
+#include "action.h"
+#include "init.h"
+#include "init_parser.h"
+#include "keywords.h"
+#include "log.h"
+#include "property_service.h"
+#include "util.h"
+
+#define CRITICAL_CRASH_THRESHOLD 4 // if we crash >4 times ...
+#define CRITICAL_CRASH_WINDOW (4*60) // ... in 4 minutes, goto recovery
+
+SocketInfo::SocketInfo() : uid(0), gid(0), perm(0) {
+}
+
+SocketInfo::SocketInfo(const std::string& name, const std::string& type, uid_t uid,
+ gid_t gid, int perm, const std::string& socketcon)
+ : name(name), type(type), uid(uid), gid(gid), perm(perm), socketcon(socketcon) {
+}
+
+ServiceEnvironmentInfo::ServiceEnvironmentInfo() {
+}
+
+ServiceEnvironmentInfo::ServiceEnvironmentInfo(const std::string& name,
+ const std::string& value)
+ : name(name), value(value) {
+}
+
+Service::Service(const std::string& name, const std::string& classname,
+ const std::vector<std::string>& args)
+ : name_(name), classname_(classname), flags_(0), pid_(0), time_started_(0),
+ time_crashed_(0), nr_crashed_(0), uid_(0), gid_(0), seclabel_(""),
+ ioprio_class_(IoSchedClass_NONE), ioprio_pri_(0), args_(args) {
+ onrestart_.InitSingleTrigger("onrestart");
+}
+
+Service::Service(const std::string& name, const std::string& classname,
+ unsigned flags, uid_t uid, gid_t gid, const std::vector<gid_t>& supp_gids,
+ const std::string& seclabel, const std::vector<std::string>& args)
+ : name_(name), classname_(classname), flags_(flags), pid_(0), time_started_(0),
+ time_crashed_(0), nr_crashed_(0), uid_(uid), gid_(gid), supp_gids_(supp_gids),
+ seclabel_(seclabel), ioprio_class_(IoSchedClass_NONE), ioprio_pri_(0), args_(args) {
+ onrestart_.InitSingleTrigger("onrestart");
+}
+
+void Service::NotifyStateChange(const std::string& new_state) const {
+ if (!properties_initialized()) {
+ // If properties aren't available yet, we can't set them.
+ return;
+ }
+
+ if ((flags_ & SVC_EXEC) != 0) {
+ // 'exec' commands don't have properties tracking their state.
+ return;
+ }
+
+ std::string prop_name = android::base::StringPrintf("init.svc.%s", name_.c_str());
+ if (prop_name.length() >= PROP_NAME_MAX) {
+ // If the property name would be too long, we can't set it.
+ ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n",
+ name_.c_str(), new_state.c_str());
+ return;
+ }
+
+ property_set(prop_name.c_str(), new_state.c_str());
+}
+
+bool Service::Reap() {
+ if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
+ NOTICE("Service '%s' (pid %d) killing any children in process group\n",
+ name_.c_str(), pid_);
+ kill(-pid_, SIGKILL);
+ }
+
+ // Remove any sockets we may have created.
+ for (const auto& si : sockets_) {
+ std::string tmp = android::base::StringPrintf(ANDROID_SOCKET_DIR "/%s",
+ si.name.c_str());
+ unlink(tmp.c_str());
+ }
+
+ if (flags_ & SVC_EXEC) {
+ INFO("SVC_EXEC pid %d finished...\n", pid_);
+ return true;
+ }
+
+ pid_ = 0;
+ flags_ &= (~SVC_RUNNING);
+
+ // Oneshot processes go into the disabled state on exit,
+ // except when manually restarted.
+ if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
+ flags_ |= SVC_DISABLED;
+ }
+
+ // Disabled and reset processes do not get restarted automatically.
+ if (flags_ & (SVC_DISABLED | SVC_RESET)) {
+ NotifyStateChange("stopped");
+ return false;
+ }
+
+ time_t now = gettime();
+ if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
+ if (time_crashed_ + CRITICAL_CRASH_WINDOW >= now) {
+ if (++nr_crashed_ > CRITICAL_CRASH_THRESHOLD) {
+ ERROR("critical process '%s' exited %d times in %d minutes; "
+ "rebooting into recovery mode\n", name_.c_str(),
+ CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
+ android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
+ return false;
+ }
+ } else {
+ time_crashed_ = now;
+ nr_crashed_ = 1;
+ }
+ }
+
+ flags_ &= (~SVC_RESTART);
+ flags_ |= SVC_RESTARTING;
+
+ // Execute all onrestart commands for this service.
+ onrestart_.ExecuteAllCommands();
+
+ NotifyStateChange("restarting");
+ return false;
+}
+
+void Service::DumpState() const {
+ INFO("service %s\n", name_.c_str());
+ INFO(" class '%s'\n", classname_.c_str());
+ INFO(" exec");
+ for (const auto& s : args_) {
+ INFO(" '%s'", s.c_str());
+ }
+ INFO("\n");
+ for (const auto& si : sockets_) {
+ INFO(" socket %s %s 0%o\n", si.name.c_str(), si.type.c_str(), si.perm);
+ }
+}
+
+bool Service::HandleLine(int kw, const std::vector<std::string>& args, std::string* err) {
+ std::vector<std::string> str_args;
+
+ ioprio_class_ = IoSchedClass_NONE;
+
+ switch (kw) {
+ case K_class:
+ if (args.size() != 2) {
+ *err = "class option requires a classname\n";
+ return false;
+ } else {
+ classname_ = args[1];
+ }
+ break;
+ case K_console:
+ flags_ |= SVC_CONSOLE;
+ break;
+ case K_disabled:
+ flags_ |= SVC_DISABLED;
+ flags_ |= SVC_RC_DISABLED;
+ break;
+ case K_ioprio:
+ if (args.size() != 3) {
+ *err = "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n";
+ return false;
+ } else {
+ ioprio_pri_ = std::stoul(args[2], 0, 8);
+
+ if (ioprio_pri_ < 0 || ioprio_pri_ > 7) {
+ *err = "priority value must be range 0 - 7\n";
+ return false;
+ }
+
+ if (args[1] == "rt") {
+ ioprio_class_ = IoSchedClass_RT;
+ } else if (args[1] == "be") {
+ ioprio_class_ = IoSchedClass_BE;
+ } else if (args[1] == "idle") {
+ ioprio_class_ = IoSchedClass_IDLE;
+ } else {
+ *err = "ioprio option usage: ioprio <rt|be|idle> <0-7>\n";
+ return false;
+ }
+ }
+ break;
+ case K_group:
+ if (args.size() < 2) {
+ *err = "group option requires a group id\n";
+ return false;
+ } else if (args.size() > NR_SVC_SUPP_GIDS + 2) {
+ *err = android::base::StringPrintf("group option accepts at most %d supp. groups\n",
+ NR_SVC_SUPP_GIDS);
+ return false;
+ } else {
+ gid_ = decode_uid(args[1].c_str());
+ for (std::size_t n = 2; n < args.size(); n++) {
+ supp_gids_.push_back(decode_uid(args[n].c_str()));
+ }
+ }
+ break;
+ case K_keycodes:
+ if (args.size() < 2) {
+ *err = "keycodes option requires atleast one keycode\n";
+ return false;
+ } else {
+ for (std::size_t i = 1; i < args.size(); i++) {
+ keycodes_.push_back(std::stoi(args[i]));
+ }
+ }
+ break;
+ case K_oneshot:
+ flags_ |= SVC_ONESHOT;
+ break;
+ case K_onrestart:
+ if (args.size() < 2) {
+ return false;
+ }
+ str_args.assign(args.begin() + 1, args.end());
+ add_command_to_action(&onrestart_, str_args, "", 0, err);
+ break;
+ case K_critical:
+ flags_ |= SVC_CRITICAL;
+ break;
+ case K_setenv: { /* name value */
+ if (args.size() < 3) {
+ *err = "setenv option requires name and value arguments\n";
+ return false;
+ }
+
+ envvars_.push_back({args[1], args[2]});
+ break;
+ }
+ case K_socket: {/* name type perm [ uid gid context ] */
+ if (args.size() < 4) {
+ *err = "socket option requires name, type, perm arguments\n";
+ return false;
+ }
+ if (args[2] != "dgram" && args[2] != "stream" &&
+ args[2] != "seqpacket") {
+ *err = "socket type must be 'dgram', 'stream' or 'seqpacket'\n";
+ return false;
+ }
+
+ int perm = std::stoul(args[3], 0, 8);
+ uid_t uid = args.size() > 4 ? decode_uid(args[4].c_str()) : 0;
+ gid_t gid = args.size() > 5 ? decode_uid(args[5].c_str()) : 0;
+ std::string socketcon = args.size() > 6 ? args[6] : "";
+
+ sockets_.push_back({args[1], args[2], uid, gid, perm, socketcon});
+ break;
+ }
+ case K_user:
+ if (args.size() != 2) {
+ *err = "user option requires a user id\n";
+ return false;
+ } else {
+ uid_ = decode_uid(args[1].c_str());
+ }
+ break;
+ case K_seclabel:
+ if (args.size() != 2) {
+ *err = "seclabel option requires a label string\n";
+ return false;
+ } else {
+ seclabel_ = args[1];
+ }
+ break;
+ case K_writepid:
+ if (args.size() < 2) {
+ *err = "writepid option requires at least one filename\n";
+ return false;
+ }
+ writepid_files_.assign(args.begin() + 1, args.end());
+ break;
+
+ default:
+ *err = android::base::StringPrintf("invalid option '%s'\n", args[0].c_str());
+ return false;
+ }
+ return true;
+}
+
+bool Service::Start(const std::vector<std::string>& dynamic_args) {
+ // Starting a service removes it from the disabled or reset state and
+ // immediately takes it out of the restarting state if it was in there.
+ flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
+ time_started_ = 0;
+
+ // Running processes require no additional work --- if they're in the
+ // process of exiting, we've ensured that they will immediately restart
+ // on exit, unless they are ONESHOT.
+ if (flags_ & SVC_RUNNING) {
+ return false;
+ }
+
+ bool needs_console = (flags_ & SVC_CONSOLE);
+ if (needs_console && !have_console) {
+ ERROR("service '%s' requires console\n", name_.c_str());
+ flags_ |= SVC_DISABLED;
+ return false;
+ }
+
+ struct stat sb;
+ if (stat(args_[0].c_str(), &sb) == -1) {
+ ERROR("cannot find '%s' (%s), disabling '%s'\n",
+ args_[0].c_str(), strerror(errno), name_.c_str());
+ flags_ |= SVC_DISABLED;
+ return false;
+ }
+
+ if ((!(flags_ & SVC_ONESHOT)) && !dynamic_args.empty()) {
+ ERROR("service '%s' must be one-shot to use dynamic args, disabling\n",
+ args_[0].c_str());
+ flags_ |= SVC_DISABLED;
+ return false;
+ }
+
+ std::string scon;
+ if (!seclabel_.empty()) {
+ scon = seclabel_;
+ } else {
+ char* mycon = nullptr;
+ char* fcon = nullptr;
+
+ INFO("computing context for service '%s'\n", args_[0].c_str());
+ int rc = getcon(&mycon);
+ if (rc < 0) {
+ ERROR("could not get context while starting '%s'\n", name_.c_str());
+ return false;
+ }
+
+ rc = getfilecon(args_[0].c_str(), &fcon);
+ if (rc < 0) {
+ ERROR("could not get context while starting '%s'\n", name_.c_str());
+ free(mycon);
+ return false;
+ }
+
+ char* ret_scon = nullptr;
+ rc = security_compute_create(mycon, fcon, string_to_security_class("process"),
+ &ret_scon);
+ if (rc == 0) {
+ scon = ret_scon;
+ free(ret_scon);
+ }
+ if (rc == 0 && scon == mycon) {
+ ERROR("Service %s does not have a SELinux domain defined.\n", name_.c_str());
+ free(mycon);
+ free(fcon);
+ return false;
+ }
+ free(mycon);
+ free(fcon);
+ if (rc < 0) {
+ ERROR("could not get context while starting '%s'\n", name_.c_str());
+ return false;
+ }
+ }
+
+ NOTICE("Starting service '%s'...\n", name_.c_str());
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ int fd, sz;
+
+ umask(077);
+ if (properties_initialized()) {
+ get_property_workspace(&fd, &sz);
+ std::string tmp = android::base::StringPrintf("%d,%d", dup(fd), sz);
+ add_environment("ANDROID_PROPERTY_WORKSPACE", tmp.c_str());
+ }
+
+ for (const auto& ei : envvars_) {
+ add_environment(ei.name.c_str(), ei.value.c_str());
+ }
+
+ for (const auto& si : sockets_) {
+ int socket_type = ((si.type == "stream" ? SOCK_STREAM :
+ (si.type == "dgram" ? SOCK_DGRAM :
+ SOCK_SEQPACKET)));
+ const char* socketcon =
+ !si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();
+
+ int s = create_socket(si.name.c_str(), socket_type, si.perm,
+ si.uid, si.gid, socketcon);
+ if (s >= 0) {
+ PublishSocket(si.name, s);
+ }
+ }
+
+ std::string pid_str = android::base::StringPrintf("%d", pid);
+ for (const auto& file : writepid_files_) {
+ if (!android::base::WriteStringToFile(pid_str, file)) {
+ ERROR("couldn't write %s to %s: %s\n",
+ pid_str.c_str(), file.c_str(), strerror(errno));
+ }
+ }
+
+ if (ioprio_class_ != IoSchedClass_NONE) {
+ if (android_set_ioprio(getpid(), ioprio_class_, ioprio_pri_)) {
+ ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
+ getpid(), ioprio_class_, ioprio_pri_, strerror(errno));
+ }
+ }
+
+ if (needs_console) {
+ setsid();
+ OpenConsole();
+ } else {
+ ZapStdio();
+ }
+
+ setpgid(0, getpid());
+
+ // As requested, set our gid, supplemental gids, and uid.
+ if (gid_) {
+ if (setgid(gid_) != 0) {
+ ERROR("setgid failed: %s\n", strerror(errno));
+ _exit(127);
+ }
+ }
+ if (!supp_gids_.empty()) {
+ if (setgroups(supp_gids_.size(), &supp_gids_[0]) != 0) {
+ ERROR("setgroups failed: %s\n", strerror(errno));
+ _exit(127);
+ }
+ }
+ if (uid_) {
+ if (setuid(uid_) != 0) {
+ ERROR("setuid failed: %s\n", strerror(errno));
+ _exit(127);
+ }
+ }
+ if (!seclabel_.empty()) {
+ if (setexeccon(seclabel_.c_str()) < 0) {
+ ERROR("cannot setexeccon('%s'): %s\n",
+ seclabel_.c_str(), strerror(errno));
+ _exit(127);
+ }
+ }
+
+ std::vector<char*> strs;
+ for (const auto& s : args_) {
+ strs.push_back(const_cast<char*>(s.c_str()));
+ }
+ for (const auto& s : dynamic_args) {
+ strs.push_back(const_cast<char*>(s.c_str()));
+ }
+ strs.push_back(nullptr);
+ if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
+ ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));
+ }
+
+ _exit(127);
+ }
+
+ if (pid < 0) {
+ ERROR("failed to start '%s'\n", name_.c_str());
+ pid_ = 0;
+ return false;
+ }
+
+ time_started_ = gettime();
+ pid_ = pid;
+ flags_ |= SVC_RUNNING;
+
+ if ((flags_ & SVC_EXEC) != 0) {
+ INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n",
+ pid_, uid_, gid_, supp_gids_.size(),
+ !seclabel_.empty() ? seclabel_.c_str() : "default");
+ }
+
+ NotifyStateChange("running");
+ return true;
+}
+
+bool Service::Start() {
+ const std::vector<std::string> null_dynamic_args;
+ return Start(null_dynamic_args);
+}
+
+bool Service::StartIfNotDisabled() {
+ if (!(flags_ & SVC_DISABLED)) {
+ return Start();
+ } else {
+ flags_ |= SVC_DISABLED_START;
+ }
+ return true;
+}
+
+bool Service::Enable() {
+ flags_ &= ~(SVC_DISABLED | SVC_RC_DISABLED);
+ if (flags_ & SVC_DISABLED_START) {
+ return Start();
+ }
+ return true;
+}
+
+void Service::Reset() {
+ StopOrReset(SVC_RESET);
+}
+
+void Service::Stop() {
+ StopOrReset(SVC_DISABLED);
+}
+
+void Service::Restart() {
+ if (flags_ & SVC_RUNNING) {
+ /* Stop, wait, then start the service. */
+ StopOrReset(SVC_RESTART);
+ } else if (!(flags_ & SVC_RESTARTING)) {
+ /* Just start the service since it's not running. */
+ Start();
+ } /* else: Service is restarting anyways. */
+}
+
+void Service::RestartIfNeeded(time_t& process_needs_restart) {
+ time_t next_start_time = time_started_ + 5;
+
+ if (next_start_time <= gettime()) {
+ flags_ &= (~SVC_RESTARTING);
+ Start();
+ return;
+ }
+
+ if ((next_start_time < process_needs_restart) ||
+ (process_needs_restart == 0)) {
+ process_needs_restart = next_start_time;
+ }
+}
+
+/* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */
+void Service::StopOrReset(int how) {
+ /* The service is still SVC_RUNNING until its process exits, but if it has
+ * already exited it shoudn't attempt a restart yet. */
+ flags_ &= ~(SVC_RESTARTING | SVC_DISABLED_START);
+
+ if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) {
+ /* Hrm, an illegal flag. Default to SVC_DISABLED */
+ how = SVC_DISABLED;
+ }
+ /* if the service has not yet started, prevent
+ * it from auto-starting with its class
+ */
+ if (how == SVC_RESET) {
+ flags_ |= (flags_ & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET;
+ } else {
+ flags_ |= how;
+ }
+
+ if (pid_) {
+ NOTICE("Service '%s' is being killed...\n", name_.c_str());
+ kill(-pid_, SIGKILL);
+ NotifyStateChange("stopping");
+ } else {
+ NotifyStateChange("stopped");
+ }
+}
+
+void Service::ZapStdio() const {
+ int fd;
+ fd = open("/dev/null", O_RDWR);
+ dup2(fd, 0);
+ dup2(fd, 1);
+ dup2(fd, 2);
+ close(fd);
+}
+
+void Service::OpenConsole() const {
+ int fd;
+ if ((fd = open(console_name.c_str(), O_RDWR)) < 0) {
+ fd = open("/dev/null", O_RDWR);
+ }
+ ioctl(fd, TIOCSCTTY, 0);
+ dup2(fd, 0);
+ dup2(fd, 1);
+ dup2(fd, 2);
+ close(fd);
+}
+
+void Service::PublishSocket(const std::string& name, int fd) const {
+ std::string key = android::base::StringPrintf(ANDROID_SOCKET_ENV_PREFIX "%s",
+ name.c_str());
+ std::string val = android::base::StringPrintf("%d", fd);
+ add_environment(key.c_str(), val.c_str());
+
+ /* make sure we don't close-on-exec */
+ fcntl(fd, F_SETFD, 0);
+}
+
+int ServiceManager::exec_count_ = 0;
+
+ServiceManager::ServiceManager() {
+}
+
+ServiceManager& ServiceManager::GetInstance() {
+ static ServiceManager instance;
+ return instance;
+}
+
+Service* ServiceManager::AddNewService(const std::string& name,
+ const std::string& classname,
+ const std::vector<std::string>& args,
+ std::string* err) {
+ if (!IsValidName(name)) {
+ *err = android::base::StringPrintf("invalid service name '%s'\n", name.c_str());
+ return nullptr;
+ }
+
+ Service* svc = ServiceManager::GetInstance().FindServiceByName(name);
+ if (svc) {
+ *err = android::base::StringPrintf("ignored duplicate definition of service '%s'\n",
+ name.c_str());
+ return nullptr;
+ }
+
+ std::unique_ptr<Service> svc_p(new Service(name, classname, args));
+ if (!svc_p) {
+ ERROR("Couldn't allocate service for service '%s'", name.c_str());
+ return nullptr;
+ }
+ svc = svc_p.get();
+ services_.push_back(std::move(svc_p));
+
+ return svc;
+}
+
+Service* ServiceManager::MakeExecOneshotService(const std::vector<std::string>& args) {
+ // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
+ // SECLABEL can be a - to denote default
+ std::size_t command_arg = 1;
+ for (std::size_t i = 1; i < args.size(); ++i) {
+ if (args[i] == "--") {
+ command_arg = i + 1;
+ break;
+ }
+ }
+ if (command_arg > 4 + NR_SVC_SUPP_GIDS) {
+ ERROR("exec called with too many supplementary group ids\n");
+ return nullptr;
+ }
+
+ if (command_arg >= args.size()) {
+ ERROR("exec called without command\n");
+ return nullptr;
+ }
+ std::vector<std::string> str_args(args.begin() + command_arg, args.end());
+
+ exec_count_++;
+ std::string name = android::base::StringPrintf("exec %d (%s)", exec_count_,
+ str_args[0].c_str());
+ unsigned flags = SVC_EXEC | SVC_ONESHOT;
+
+ std::string seclabel = "";
+ if (command_arg > 2 && args[1] != "-") {
+ seclabel = args[1];
+ }
+ uid_t uid = 0;
+ if (command_arg > 3) {
+ uid = decode_uid(args[2].c_str());
+ }
+ gid_t gid = 0;
+ std::vector<gid_t> supp_gids;
+ if (command_arg > 4) {
+ gid = decode_uid(args[3].c_str());
+ std::size_t nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */;
+ for (size_t i = 0; i < nr_supp_gids; ++i) {
+ supp_gids.push_back(decode_uid(args[4 + i].c_str()));
+ }
+ }
+
+ std::unique_ptr<Service> svc_p(new Service(name, "default", flags, uid, gid,
+ supp_gids, seclabel, str_args));
+ if (!svc_p) {
+ ERROR("Couldn't allocate service for exec of '%s'",
+ str_args[0].c_str());
+ return nullptr;
+ }
+ Service* svc = svc_p.get();
+ services_.push_back(std::move(svc_p));
+
+ return svc;
+}
+
+Service* ServiceManager::FindServiceByName(const std::string& name) const {
+ auto svc = std::find_if(services_.begin(), services_.end(),
+ [&name] (const std::unique_ptr<Service>& s) {
+ return name == s->name();
+ });
+ if (svc != services_.end()) {
+ return svc->get();
+ }
+ return nullptr;
+}
+
+Service* ServiceManager::FindServiceByPid(pid_t pid) const {
+ auto svc = std::find_if(services_.begin(), services_.end(),
+ [&pid] (const std::unique_ptr<Service>& s) {
+ return s->pid() == pid;
+ });
+ if (svc != services_.end()) {
+ return svc->get();
+ }
+ return nullptr;
+}
+
+Service* ServiceManager::FindServiceByKeychord(int keychord_id) const {
+ auto svc = std::find_if(services_.begin(), services_.end(),
+ [&keychord_id] (const std::unique_ptr<Service>& s) {
+ return s->keychord_id() == keychord_id;
+ });
+
+ if (svc != services_.end()) {
+ return svc->get();
+ }
+ return nullptr;
+}
+
+void ServiceManager::ForEachService(void (*func)(Service* svc)) const {
+ for (const auto& s : services_) {
+ func(s.get());
+ }
+}
+
+void ServiceManager::ForEachServiceInClass(const std::string& classname,
+ void (*func)(Service* svc)) const {
+ for (const auto& s : services_) {
+ if (classname == s->classname()) {
+ func(s.get());
+ }
+ }
+}
+
+void ServiceManager::ForEachServiceWithFlags(unsigned matchflags,
+ void (*func)(Service* svc)) const {
+ for (const auto& s : services_) {
+ if (s->flags() & matchflags) {
+ func(s.get());
+ }
+ }
+}
+
+void ServiceManager::RemoveService(const Service& svc)
+{
+ auto svc_it = std::find_if(services_.begin(), services_.end(),
+ [&svc] (const std::unique_ptr<Service>& s) {
+ return svc.name() == s->name();
+ });
+ if (svc_it == services_.end()) {
+ return;
+ }
+
+ services_.erase(svc_it);
+}
+
+bool ServiceManager::IsValidName(const std::string& name) const
+{
+ if (name.size() > 16) {
+ return false;
+ }
+ for (const auto& c : name) {
+ if (!isalnum(c) && (c != '_') && (c != '-')) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ServiceManager::DumpState() const
+{
+ for (const auto& s : services_) {
+ s->DumpState();
+ }
+ INFO("\n");
+}
diff --git a/init/service.h b/init/service.h
new file mode 100644
index 0000000..1ecf78a
--- /dev/null
+++ b/init/service.h
@@ -0,0 +1,167 @@
+/*
+ * 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.
+ */
+
+#ifndef _INIT_SERVICE_H
+#define _INIT_SERVICE_H
+
+#include <sys/types.h>
+
+#include <cutils/iosched_policy.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "action.h"
+
+#define SVC_DISABLED 0x001 // do not autostart with class
+#define SVC_ONESHOT 0x002 // do not restart on exit
+#define SVC_RUNNING 0x004 // currently active
+#define SVC_RESTARTING 0x008 // waiting to restart
+#define SVC_CONSOLE 0x010 // requires console
+#define SVC_CRITICAL 0x020 // will reboot into recovery if keeps crashing
+#define SVC_RESET 0x040 // Use when stopping a process,
+ // but not disabling so it can be restarted with its class.
+#define SVC_RC_DISABLED 0x080 // Remember if the disabled flag was set in the rc script.
+#define SVC_RESTART 0x100 // Use to safely restart (stop, wait, start) a service.
+#define SVC_DISABLED_START 0x200 // A start was requested but it was disabled at the time.
+#define SVC_EXEC 0x400 // This synthetic service corresponds to an 'exec'.
+
+#define NR_SVC_SUPP_GIDS 12 // twelve supplementary groups
+
+class Action;
+class ServiceManager;
+
+struct SocketInfo {
+ SocketInfo();
+ SocketInfo(const std::string& name, const std::string& type, uid_t uid,
+ gid_t gid, int perm, const std::string& socketcon);
+ std::string name;
+ std::string type;
+ uid_t uid;
+ gid_t gid;
+ int perm;
+ std::string socketcon;
+};
+
+struct ServiceEnvironmentInfo {
+ ServiceEnvironmentInfo();
+ ServiceEnvironmentInfo(const std::string& name, const std::string& value);
+ std::string name;
+ std::string value;
+};
+
+class Service {
+public:
+ Service(const std::string& name, const std::string& classname,
+ const std::vector<std::string>& args);
+
+ Service(const std::string& name, const std::string& classname,
+ unsigned flags, uid_t uid, gid_t gid, const std::vector<gid_t>& supp_gids,
+ const std::string& seclabel, const std::vector<std::string>& args);
+
+ bool HandleLine(int kw, const std::vector<std::string>& args, std::string* err);
+ bool Start(const std::vector<std::string>& dynamic_args);
+ bool Start();
+ bool StartIfNotDisabled();
+ bool Enable();
+ void Reset();
+ void Stop();
+ void Restart();
+ void RestartIfNeeded(time_t& process_needs_restart);
+ bool Reap();
+ void DumpState() const;
+
+ const std::string& name() const { return name_; }
+ const std::string& classname() const { return classname_; }
+ unsigned flags() const { return flags_; }
+ pid_t pid() const { return pid_; }
+ uid_t uid() const { return uid_; }
+ gid_t gid() const { return gid_; }
+ const std::vector<gid_t>& supp_gids() const { return supp_gids_; }
+ const std::string& seclabel() const { return seclabel_; }
+ const std::vector<int>& keycodes() const { return keycodes_; }
+ int keychord_id() const { return keychord_id_; }
+ void set_keychord_id(int keychord_id) { keychord_id_ = keychord_id; }
+ const std::vector<std::string>& args() const { return args_; }
+
+private:
+ void NotifyStateChange(const std::string& new_state) const;
+ void StopOrReset(int how);
+ void ZapStdio() const;
+ void OpenConsole() const;
+ void PublishSocket(const std::string& name, int fd) const;
+
+ std::string name_;
+ std::string classname_;
+
+ unsigned flags_;
+ pid_t pid_;
+ time_t time_started_; // time of last start
+ time_t time_crashed_; // first crash within inspection window
+ int nr_crashed_; // number of times crashed within window
+
+ uid_t uid_;
+ gid_t gid_;
+ std::vector<gid_t> supp_gids_;
+
+ std::string seclabel_;
+
+ std::vector<SocketInfo> sockets_;
+ std::vector<ServiceEnvironmentInfo> envvars_;
+
+ Action onrestart_; // Commands to execute on restart.
+
+ std::vector<std::string> writepid_files_;
+
+ // keycodes for triggering this service via /dev/keychord
+ std::vector<int> keycodes_;
+ int keychord_id_;
+
+ IoSchedClass ioprio_class_;
+ int ioprio_pri_;
+
+ std::vector<std::string> args_;
+};
+
+class ServiceManager {
+public:
+ static ServiceManager& GetInstance();
+
+ Service* AddNewService(const std::string& name, const std::string& classname,
+ const std::vector<std::string>& args,
+ std::string* err);
+ Service* MakeExecOneshotService(const std::vector<std::string>& args);
+ Service* FindServiceByName(const std::string& name) const;
+ Service* FindServiceByPid(pid_t pid) const;
+ Service* FindServiceByKeychord(int keychord_id) const;
+ void ForEachService(void (*func)(Service* svc)) const;
+ void ForEachServiceInClass(const std::string& classname,
+ void (*func)(Service* svc)) const;
+ void ForEachServiceWithFlags(unsigned matchflags,
+ void (*func)(Service* svc)) const;
+ void RemoveService(const Service& svc);
+ void DumpState() const;
+private:
+ ServiceManager();
+
+ bool IsValidName(const std::string& name) const;
+
+ static int exec_count_; // Every service needs a unique name.
+ std::vector<std::unique_ptr<Service>> services_;
+};
+
+#endif
diff --git a/init/signal_handler.cpp b/init/signal_handler.cpp
index 39a466d..867abbc 100644
--- a/init/signal_handler.cpp
+++ b/init/signal_handler.cpp
@@ -28,13 +28,12 @@
#include <cutils/list.h>
#include <cutils/sockets.h>
+#include "action.h"
#include "init.h"
#include "log.h"
+#include "service.h"
#include "util.h"
-#define CRITICAL_CRASH_THRESHOLD 4 /* if we crash >4 times ... */
-#define CRITICAL_CRASH_WINDOW (4*60) /* ... in 4 minutes, goto recovery */
-
static int signal_write_fd = -1;
static int signal_read_fd = -1;
@@ -60,11 +59,11 @@
return false;
}
- service* svc = service_find_by_pid(pid);
+ Service* svc = ServiceManager::GetInstance().FindServiceByPid(pid);
std::string name;
if (svc) {
- name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid);
+ name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name().c_str(), pid);
} else {
name = android::base::StringPrintf("Untracked pid %d", pid);
}
@@ -75,70 +74,11 @@
return true;
}
- // TODO: all the code from here down should be a member function on service.
-
- if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
- NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);
- kill(-pid, SIGKILL);
- }
-
- // Remove any sockets we may have created.
- for (socketinfo* si = svc->sockets; si; si = si->next) {
- char tmp[128];
- snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
- unlink(tmp);
- }
-
- if (svc->flags & SVC_EXEC) {
- INFO("SVC_EXEC pid %d finished...\n", svc->pid);
+ if (svc->Reap()) {
waiting_for_exec = false;
- list_remove(&svc->slist);
- free(svc->name);
- free(svc);
- return true;
+ ServiceManager::GetInstance().RemoveService(*svc);
}
- svc->pid = 0;
- svc->flags &= (~SVC_RUNNING);
-
- // Oneshot processes go into the disabled state on exit,
- // except when manually restarted.
- if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
- svc->flags |= SVC_DISABLED;
- }
-
- // Disabled and reset processes do not get restarted automatically.
- if (svc->flags & (SVC_DISABLED | SVC_RESET)) {
- svc->NotifyStateChange("stopped");
- return true;
- }
-
- time_t now = gettime();
- if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
- if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
- if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
- ERROR("critical process '%s' exited %d times in %d minutes; "
- "rebooting into recovery mode\n", svc->name,
- CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
- android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
- return true;
- }
- } else {
- svc->time_crashed = now;
- svc->nr_crashed = 1;
- }
- }
-
- svc->flags &= (~SVC_RESTART);
- svc->flags |= SVC_RESTARTING;
-
- // Execute all onrestart commands for this service.
- 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);
- }
- 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 8216892..f6131e3 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -47,7 +47,7 @@
/*
* android_name_to_id - returns the integer uid/gid associated with the given
- * name, or -1U on error.
+ * name, or UINT_MAX on error.
*/
static unsigned int android_name_to_id(const char *name)
{
@@ -59,27 +59,35 @@
return info[n].aid;
}
- return -1U;
+ return UINT_MAX;
}
-/*
- * decode_uid - decodes and returns the given string, which can be either the
- * numeric or name representation, into the integer uid or gid. Returns -1U on
- * error.
- */
-unsigned int decode_uid(const char *s)
+static unsigned int do_decode_uid(const char *s)
{
unsigned int v;
if (!s || *s == '\0')
- return -1U;
+ return UINT_MAX;
if (isalpha(s[0]))
return android_name_to_id(s);
errno = 0;
v = (unsigned int) strtoul(s, 0, 0);
if (errno)
- return -1U;
+ return UINT_MAX;
+ return v;
+}
+
+/*
+ * decode_uid - decodes and returns the given string, which can be either the
+ * numeric or name representation, into the integer uid or gid. Returns
+ * UINT_MAX on error.
+ */
+unsigned int decode_uid(const char *s) {
+ unsigned int v = do_decode_uid(s);
+ if (v == UINT_MAX) {
+ ERROR("decode_uid: Unable to find UID for '%s'. Returning UINT_MAX\n", s);
+ }
return v;
}
@@ -457,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/util_test.cpp b/init/util_test.cpp
index 5b3ab50..228954b 100644
--- a/init/util_test.cpp
+++ b/init/util_test.cpp
@@ -38,6 +38,6 @@
TEST(util, decode_uid) {
EXPECT_EQ(0U, decode_uid("root"));
- EXPECT_EQ(-1U, decode_uid("toot"));
+ EXPECT_EQ(UINT_MAX, decode_uid("toot"));
EXPECT_EQ(123U, decode_uid("123"));
}
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/libbacktrace/Android.build.mk b/libbacktrace/Android.build.mk
index 35fed6d..4983b55 100644
--- a/libbacktrace/Android.build.mk
+++ b/libbacktrace/Android.build.mk
@@ -20,7 +20,7 @@
LOCAL_MODULE_TAGS := $(module_tag)
LOCAL_MULTILIB := $($(module)_multilib)
ifeq ($(LOCAL_MULTILIB),both)
-ifneq ($(build_target),$(filter $(build_target),SHARED_LIBRARY STATIC_LIBRRARY))
+ifneq ($(build_target),$(filter $(build_target),SHARED_LIBRARY STATIC_LIBRARY))
LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
endif
diff --git a/libbacktrace/Android.mk b/libbacktrace/Android.mk
index be8b803..395d677 100644
--- a/libbacktrace/Android.mk
+++ b/libbacktrace/Android.mk
@@ -68,6 +68,14 @@
build_type := host
libbacktrace_multilib := both
include $(LOCAL_PATH)/Android.build.mk
+libbacktrace_static_libraries := \
+ libbase \
+ liblog \
+ libunwind \
+
+build_target := STATIC_LIBRARY
+include $(LOCAL_PATH)/Android.build.mk
+libbacktrace_static_libraries :=
#-------------------------------------------------------------------------
# The libbacktrace_test library needed by backtrace_test.
diff --git a/libbacktrace/Backtrace.cpp b/libbacktrace/Backtrace.cpp
index 128bb04..97f0ef4 100644
--- a/libbacktrace/Backtrace.cpp
+++ b/libbacktrace/Backtrace.cpp
@@ -102,6 +102,11 @@
uintptr_t relative_pc = BacktraceMap::GetRelativePc(frame->map, frame->pc);
std::string line(StringPrintf("#%02zu pc %" PRIPTR " %s", frame->num, relative_pc, map_name));
+ // Special handling for non-zero offset maps, we need to print that
+ // information.
+ if (frame->map.offset != 0) {
+ line += " (offset " + StringPrintf("0x%" PRIxPTR, frame->map.offset) + ")";
+ }
if (!frame->func_name.empty()) {
line += " (" + frame->func_name;
if (frame->func_offset) {
diff --git a/libbacktrace/BacktracePtrace.cpp b/libbacktrace/BacktracePtrace.cpp
index e10cce1..fd8b713 100644
--- a/libbacktrace/BacktracePtrace.cpp
+++ b/libbacktrace/BacktracePtrace.cpp
@@ -37,8 +37,6 @@
errno = 0;
*out_value = ptrace(PTRACE_PEEKTEXT, tid, reinterpret_cast<void*>(addr), nullptr);
if (*out_value == static_cast<word_t>(-1) && errno) {
- BACK_LOGW("invalid pointer %p reading from tid %d, ptrace() strerror(errno)=%s",
- reinterpret_cast<void*>(addr), tid, strerror(errno));
return false;
}
return true;
diff --git a/libbacktrace/backtrace_test.cpp b/libbacktrace/backtrace_test.cpp
index 760f5cc..9ebd639 100644
--- a/libbacktrace/backtrace_test.cpp
+++ b/libbacktrace/backtrace_test.cpp
@@ -825,6 +825,15 @@
EXPECT_EQ("#01 pc 123456dc MapFake (ProcFake+645)",
#endif
backtrace->FormatFrameData(&frame));
+
+ // Check a non-zero map offset.
+ frame.map.offset = 0x1000;
+#if defined(__LP64__)
+ EXPECT_EQ("#01 pc 00000000123456dc MapFake (offset 0x1000) (ProcFake+645)",
+#else
+ EXPECT_EQ("#01 pc 123456dc MapFake (offset 0x1000) (ProcFake+645)",
+#endif
+ backtrace->FormatFrameData(&frame));
}
struct map_test_t {
@@ -974,8 +983,8 @@
<< "Offset at " << i << " length " << j << " wrote too much data";
}
}
- delete data;
- delete expected;
+ delete[] data;
+ delete[] expected;
}
TEST(libbacktrace, thread_read) {
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 9f8023e..5f6f8f9 100644
--- a/libcutils/fs_config.c
+++ b/libcutils/fs_config.c
@@ -76,6 +76,7 @@
static const struct fs_path_config android_dirs[] = {
{ 00770, AID_SYSTEM, AID_CACHE, 0, "cache" },
+ { 00500, AID_ROOT, AID_ROOT, 0, "config" },
{ 00771, AID_SYSTEM, AID_SYSTEM, 0, "data/app" },
{ 00771, AID_SYSTEM, AID_SYSTEM, 0, "data/app-private" },
{ 00771, AID_ROOT, AID_ROOT, 0, "data/dalvik-cache" },
@@ -88,7 +89,10 @@
{ 00775, AID_MEDIA_RW, AID_MEDIA_RW, 0, "data/media" },
{ 00775, AID_MEDIA_RW, AID_MEDIA_RW, 0, "data/media/Music" },
{ 00771, AID_SYSTEM, AID_SYSTEM, 0, "data" },
+ { 00755, AID_ROOT, AID_SYSTEM, 0, "mnt" },
+ { 00755, AID_ROOT, AID_ROOT, 0, "root" },
{ 00750, AID_ROOT, AID_SHELL, 0, "sbin" },
+ { 00751, AID_ROOT, AID_SDCARD_R, 0, "storage" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system/bin" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system/vendor" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system/xbin" },
@@ -114,6 +118,7 @@
{ 00550, AID_DHCP, AID_SHELL, 0, "system/etc/dhcpcd/dhcpcd-run-hooks" },
{ 00555, AID_ROOT, AID_ROOT, 0, "system/etc/ppp/*" },
{ 00555, AID_ROOT, AID_ROOT, 0, "system/etc/rc.*" },
+ { 00440, AID_ROOT, AID_ROOT, 0, "system/etc/recovery.img" },
{ 00444, AID_ROOT, AID_ROOT, 0, conf_dir + 1 },
{ 00444, AID_ROOT, AID_ROOT, 0, conf_file + 1 },
{ 00644, AID_SYSTEM, AID_SYSTEM, 0, "data/app/*" },
@@ -156,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/iosched_policy.c b/libcutils/iosched_policy.c
index 8946d3c..71bc94b 100644
--- a/libcutils/iosched_policy.c
+++ b/libcutils/iosched_policy.c
@@ -23,7 +23,7 @@
#include <cutils/iosched_policy.h>
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
#include <linux/ioprio.h>
#include <sys/syscall.h>
#define __android_unused
@@ -32,7 +32,7 @@
#endif
int android_set_ioprio(int pid __android_unused, IoSchedClass clazz __android_unused, int ioprio __android_unused) {
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
if (syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, pid, ioprio | (clazz << IOPRIO_CLASS_SHIFT))) {
return -1;
}
@@ -41,7 +41,7 @@
}
int android_get_ioprio(int pid __android_unused, IoSchedClass *clazz, int *ioprio) {
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
int rc;
if ((rc = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, pid)) < 0) {
diff --git a/libcutils/process_name.c b/libcutils/process_name.c
index cc931eb..5d28b6f 100644
--- a/libcutils/process_name.c
+++ b/libcutils/process_name.c
@@ -25,19 +25,19 @@
#include <unistd.h>
#include <cutils/process_name.h>
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
#include <cutils/properties.h>
#endif
#define PROCESS_NAME_DEVICE "/sys/qemu_trace/process_name"
static const char* process_name = "unknown";
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
static int running_in_emulator = -1;
#endif
void set_process_name(const char* new_name) {
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
char propBuf[PROPERTY_VALUE_MAX];
#endif
@@ -59,7 +59,7 @@
}
#endif
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
// If we know we are not running in the emulator, then return.
if (running_in_emulator == 0) {
return;
diff --git a/libcutils/record_stream.c b/libcutils/record_stream.c
index 6994904..2bc4226 100644
--- a/libcutils/record_stream.c
+++ b/libcutils/record_stream.c
@@ -22,7 +22,7 @@
#include <cutils/record_stream.h>
#include <string.h>
#include <stdint.h>
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
#include <winsock2.h> /* for ntohl */
#else
#include <netinet/in.h>
diff --git a/libcutils/sched_policy.c b/libcutils/sched_policy.c
index dfc8777..103ff66 100644
--- a/libcutils/sched_policy.c
+++ b/libcutils/sched_policy.c
@@ -37,7 +37,7 @@
return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p;
}
-#if defined(HAVE_ANDROID_OS)
+#if defined(__ANDROID__)
#include <pthread.h>
#include <sched.h>
@@ -142,7 +142,7 @@
*/
static int getSchedulerGroup(int tid, char* buf, size_t bufLen)
{
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
char pathBuf[32];
char lineBuf[256];
FILE *fp;
diff --git a/libcutils/socket_inaddr_any_server.c b/libcutils/socket_inaddr_any_server.c
index 6c849de..7f0ccb8 100644
--- a/libcutils/socket_inaddr_any_server.c
+++ b/libcutils/socket_inaddr_any_server.c
@@ -20,7 +20,7 @@
#include <string.h>
#include <unistd.h>
-#ifndef HAVE_WINSOCK
+#if !defined(_WIN32)
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
diff --git a/libcutils/socket_local_client.c b/libcutils/socket_local_client.c
index 7b42daa..2526146 100644
--- a/libcutils/socket_local_client.c
+++ b/libcutils/socket_local_client.c
@@ -22,7 +22,7 @@
#include <cutils/sockets.h>
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
int socket_local_client(const char *name, int namespaceId, int type)
{
@@ -30,7 +30,7 @@
return -1;
}
-#else /* !HAVE_WINSOCK */
+#else /* !_WIN32 */
#include <sys/socket.h>
#include <sys/un.h>
@@ -165,4 +165,4 @@
return s;
}
-#endif /* !HAVE_WINSOCK */
+#endif /* !_WIN32 */
diff --git a/libcutils/socket_local_server.c b/libcutils/socket_local_server.c
index 60eb86b..c9acdad 100644
--- a/libcutils/socket_local_server.c
+++ b/libcutils/socket_local_server.c
@@ -23,7 +23,7 @@
#include <errno.h>
#include <stddef.h>
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
int socket_local_server(const char *name, int namespaceId, int type)
{
@@ -31,7 +31,7 @@
return -1;
}
-#else /* !HAVE_WINSOCK */
+#else /* !_WIN32 */
#include <sys/socket.h>
#include <sys/un.h>
@@ -123,4 +123,4 @@
return s;
}
-#endif /* !HAVE_WINSOCK */
+#endif /* !_WIN32 */
diff --git a/libcutils/socket_loopback_client.c b/libcutils/socket_loopback_client.c
index 9aed7b7..e14cffb 100644
--- a/libcutils/socket_loopback_client.c
+++ b/libcutils/socket_loopback_client.c
@@ -20,7 +20,7 @@
#include <string.h>
#include <unistd.h>
-#ifndef HAVE_WINSOCK
+#if !defined(_WIN32)
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
diff --git a/libcutils/socket_loopback_server.c b/libcutils/socket_loopback_server.c
index 71afce7..b600e34 100644
--- a/libcutils/socket_loopback_server.c
+++ b/libcutils/socket_loopback_server.c
@@ -22,7 +22,7 @@
#define LISTEN_BACKLOG 4
-#ifndef HAVE_WINSOCK
+#if !defined(_WIN32)
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
diff --git a/libcutils/socket_network_client.c b/libcutils/socket_network_client.c
index e0031ba..3300b8f 100644
--- a/libcutils/socket_network_client.c
+++ b/libcutils/socket_network_client.c
@@ -29,77 +29,78 @@
#include <cutils/sockets.h>
-/* Connect to port on the IP interface. type is
- * SOCK_STREAM or SOCK_DGRAM.
- * return is a file descriptor or -1 on error
- */
-int socket_network_client(const char *host, int port, int type)
-{
- return socket_network_client_timeout(host, port, type, 0);
+static int toggle_O_NONBLOCK(int s) {
+ int flags = fcntl(s, F_GETFL);
+ if (flags == -1 || fcntl(s, F_SETFL, flags ^ O_NONBLOCK) == -1) {
+ close(s);
+ return -1;
+ }
+ return s;
}
-/* Connect to port on the IP interface. type is SOCK_STREAM or SOCK_DGRAM.
- * timeout in seconds return is a file descriptor or -1 on error
- */
-int socket_network_client_timeout(const char *host, int port, int type, int timeout)
-{
- struct hostent *hp;
- struct sockaddr_in addr;
- int s;
- int flags = 0, error = 0, ret = 0;
- fd_set rset, wset;
- socklen_t len = sizeof(error);
- struct timeval ts;
+// Connect to the given host and port.
+// 'timeout' is in seconds (0 for no timeout).
+// Returns a file descriptor or -1 on error.
+// 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;
+ hints.ai_socktype = type;
+ char port_str[16];
+ snprintf(port_str, sizeof(port_str), "%d", port);
+
+ struct addrinfo* addrs;
+ *getaddrinfo_error = getaddrinfo(host, port_str, &hints, &addrs);
+ if (*getaddrinfo_error != 0) {
+ return -1;
+ }
+
+ // TODO: try all the addresses if there's more than one?
+ int family = addrs[0].ai_family;
+ int protocol = addrs[0].ai_protocol;
+ socklen_t addr_len = addrs[0].ai_addrlen;
+ struct sockaddr_storage addr;
+ memcpy(&addr, addrs[0].ai_addr, addr_len);
+
+ freeaddrinfo(addrs);
+
+ // The Mac doesn't have SOCK_NONBLOCK.
+ int s = socket(family, type, protocol);
+ if (s == -1 || toggle_O_NONBLOCK(s) == -1) return -1;
+
+ int rc = connect(s, (const struct sockaddr*) &addr, addr_len);
+ if (rc == 0) {
+ return toggle_O_NONBLOCK(s);
+ } else if (rc == -1 && errno != EINPROGRESS) {
+ close(s);
+ return -1;
+ }
+
+ fd_set r_set;
+ FD_ZERO(&r_set);
+ FD_SET(s, &r_set);
+ fd_set w_set = r_set;
+
+ struct timeval ts;
ts.tv_sec = timeout;
ts.tv_usec = 0;
-
- hp = gethostbyname(host);
- if (hp == 0) return -1;
-
- 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);
- if (s < 0) return -1;
-
- if ((flags = fcntl(s, F_GETFL, 0)) < 0) {
+ if ((rc = select(s + 1, &r_set, &w_set, NULL, (timeout != 0) ? &ts : NULL)) == -1) {
close(s);
return -1;
}
-
- if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
- close(s);
- return -1;
- }
-
- if ((ret = connect(s, (struct sockaddr *) &addr, sizeof(addr))) < 0) {
- if (errno != EINPROGRESS) {
- close(s);
- return -1;
- }
- }
-
- if (ret == 0)
- goto done;
-
- FD_ZERO(&rset);
- FD_SET(s, &rset);
- wset = rset;
-
- if ((ret = select(s + 1, &rset, &wset, NULL, (timeout) ? &ts : NULL)) < 0) {
- close(s);
- return -1;
- }
- if (ret == 0) { // we had a timeout
+ if (rc == 0) { // we had a timeout
errno = ETIMEDOUT;
close(s);
return -1;
}
- if (FD_ISSET(s, &rset) || FD_ISSET(s, &wset)) {
+ int error = 0;
+ socklen_t len = sizeof(error);
+ if (FD_ISSET(s, &r_set) || FD_ISSET(s, &w_set)) {
if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
close(s);
return -1;
@@ -115,11 +116,10 @@
return -1;
}
-done:
- if (fcntl(s, F_SETFL, flags) < 0) {
- close(s);
- return -1;
- }
+ return toggle_O_NONBLOCK(s);
+}
- return s;
+int socket_network_client(const char* host, int port, int type) {
+ int getaddrinfo_error;
+ return socket_network_client_timeout(host, port, type, 0, &getaddrinfo_error);
}
diff --git a/libcutils/sockets.c b/libcutils/sockets.c
index 15ede2b..d438782 100644
--- a/libcutils/sockets.c
+++ b/libcutils/sockets.c
@@ -17,7 +17,7 @@
#include <cutils/sockets.h>
#include <log/log.h>
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
/* For the socket trust (credentials) check */
#include <private/android_filesystem_config.h>
#define __android_unused
@@ -27,7 +27,7 @@
bool socket_peer_is_trusted(int fd __android_unused)
{
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
struct ucred cr;
socklen_t len = sizeof(cr);
int n = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);
diff --git a/liblog/Android.mk b/liblog/Android.mk
index d7766f5..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
# ========================================================
@@ -68,6 +60,7 @@
LOCAL_LDLIBS := -lrt
endif
LOCAL_MULTILIB := both
+LOCAL_CXX_STL := none
include $(BUILD_HOST_SHARED_LIBRARY)
@@ -77,6 +70,8 @@
LOCAL_MODULE := liblog
LOCAL_SRC_FILES := $(liblog_target_sources)
LOCAL_CFLAGS := -Werror $(liblog_cflags)
+# AddressSanitizer runtime library depends on liblog.
+LOCAL_SANITIZE := never
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
@@ -87,6 +82,9 @@
# TODO: This is to work around b/19059885. Remove after root cause is fixed
LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
+LOCAL_SANITIZE := never
+LOCAL_CXX_STL := none
+
include $(BUILD_SHARED_LIBRARY)
include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/liblog/log_is_loggable.c b/liblog/log_is_loggable.c
index 2e09192..7a8e33f 100644
--- a/liblog/log_is_loggable.c
+++ b/liblog/log_is_loggable.c
@@ -15,41 +15,158 @@
*/
#include <ctype.h>
+#include <pthread.h>
+#include <stdlib.h>
#include <string.h>
-#include <sys/system_properties.h>
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
#include <android/log.h>
-static int __android_log_level(const char *tag, int def)
+struct cache {
+ const prop_info *pinfo;
+ uint32_t serial;
+ char c;
+};
+
+static void refresh_cache(struct cache *cache, const char *key)
{
+ uint32_t serial;
char buf[PROP_VALUE_MAX];
- if (!tag || !*tag) {
- return def;
+ if (!cache->pinfo) {
+ cache->pinfo = __system_property_find(key);
+ if (!cache->pinfo) {
+ return;
+ }
}
- {
- static const char log_namespace[] = "persist.log.tag.";
- char key[sizeof(log_namespace) + strlen(tag)];
+ serial = __system_property_serial(cache->pinfo);
+ if (serial == cache->serial) {
+ return;
+ }
+ cache->serial = serial;
+ __system_property_read(cache->pinfo, 0, buf);
+ cache->c = buf[0];
+}
- strcpy(key, log_namespace);
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int __android_log_level(const char *tag, int def)
+{
+ /* sizeof() is used on this array below */
+ static const char log_namespace[] = "persist.log.tag.";
+ static const size_t base_offset = 8; /* skip "persist." */
+ /* calculate the size of our key temporary buffer */
+ const size_t taglen = (tag && *tag) ? strlen(tag) : 0;
+ /* sizeof(log_namespace) = strlen(log_namespace) + 1 */
+ char key[sizeof(log_namespace) + taglen];
+ char *kp;
+ size_t i;
+ char c = 0;
+ /*
+ * Single layer cache of four properties. Priorities are:
+ * log.tag.<tag>
+ * persist.log.tag.<tag>
+ * log.tag
+ * persist.log.tag
+ * Where the missing tag matches all tags and becomes the
+ * system global default. We do not support ro.log.tag* .
+ */
+ static char *last_tag;
+ static uint32_t global_serial;
+ uint32_t current_global_serial;
+ static struct cache tag_cache[2] = {
+ { NULL, -1, 0 },
+ { NULL, -1, 0 }
+ };
+ static struct cache global_cache[2] = {
+ { NULL, -1, 0 },
+ { NULL, -1, 0 }
+ };
+
+ strcpy(key, log_namespace);
+
+ pthread_mutex_lock(&lock);
+
+ current_global_serial = __system_property_area_serial();
+
+ if (taglen) {
+ uint32_t current_local_serial = current_global_serial;
+
+ if (!last_tag || strcmp(last_tag, tag)) {
+ /* invalidate log.tag.<tag> cache */
+ for(i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
+ tag_cache[i].pinfo = NULL;
+ tag_cache[i].serial = -1;
+ tag_cache[i].c = '\0';
+ }
+ free(last_tag);
+ last_tag = NULL;
+ current_global_serial = -1;
+ }
+ if (!last_tag) {
+ last_tag = strdup(tag);
+ }
strcpy(key + sizeof(log_namespace) - 1, tag);
- if (__system_property_get(key + 8, buf) <= 0) {
- buf[0] = '\0';
- }
- if (!buf[0] && __system_property_get(key, buf) <= 0) {
- buf[0] = '\0';
+ kp = key;
+ for(i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) {
+ if (current_local_serial != global_serial) {
+ refresh_cache(&tag_cache[i], kp);
+ }
+
+ if (tag_cache[i].c) {
+ c = tag_cache[i].c;
+ break;
+ }
+
+ kp = key + base_offset;
}
}
- switch (toupper(buf[0])) {
- case 'V': return ANDROID_LOG_VERBOSE;
- case 'D': return ANDROID_LOG_DEBUG;
- case 'I': return ANDROID_LOG_INFO;
- case 'W': return ANDROID_LOG_WARN;
- case 'E': return ANDROID_LOG_ERROR;
- case 'F': /* FALLTHRU */ /* Not officially supported */
- case 'A': return ANDROID_LOG_FATAL;
- case 'S': return -1; /* ANDROID_LOG_SUPPRESS */
+
+ switch (toupper(c)) { /* if invalid, resort to global */
+ case 'V':
+ case 'D':
+ case 'I':
+ case 'W':
+ case 'E':
+ case 'F': /* Not officially supported */
+ case 'A':
+ case 'S':
+ break;
+ default:
+ /* clear '.' after log.tag */
+ key[sizeof(log_namespace) - 2] = '\0';
+
+ kp = key;
+ for(i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) {
+ if (current_global_serial != global_serial) {
+ refresh_cache(&global_cache[i], kp);
+ }
+
+ if (global_cache[i].c) {
+ c = global_cache[i].c;
+ break;
+ }
+
+ kp = key + base_offset;
+ }
+ break;
+ }
+
+ global_serial = current_global_serial;
+
+ pthread_mutex_unlock(&lock);
+
+ switch (toupper(c)) {
+ case 'V': return ANDROID_LOG_VERBOSE;
+ case 'D': return ANDROID_LOG_DEBUG;
+ case 'I': return ANDROID_LOG_INFO;
+ case 'W': return ANDROID_LOG_WARN;
+ case 'E': return ANDROID_LOG_ERROR;
+ case 'F': /* FALLTHRU */ /* Not officially supported */
+ case 'A': return ANDROID_LOG_FATAL;
+ case 'S': return -1; /* ANDROID_LOG_SUPPRESS */
}
return def;
}
diff --git a/liblog/log_read.c b/liblog/log_read.c
index 9c4af30..cfc8a7a 100644
--- a/liblog/log_read.c
+++ b/liblog/log_read.c
@@ -37,7 +37,7 @@
/* branchless on many architectures. */
#define min(x,y) ((y) ^ (((x) ^ (y)) & -((x) < (y))))
-#if (defined(USE_MINGW) || defined(HAVE_WINSOCK))
+#if defined(_WIN32)
#define WEAK static
#else
#define WEAK __attribute__((weak))
@@ -48,7 +48,7 @@
/* Private copy of ../libcutils/socket_local_client.c prevent library loops */
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
int WEAK socket_local_client(const char *name, int namespaceId, int type)
{
@@ -56,7 +56,7 @@
return -ENOSYS;
}
-#else /* !HAVE_WINSOCK */
+#else /* !_WIN32 */
#include <sys/socket.h>
#include <sys/un.h>
@@ -193,7 +193,7 @@
return s;
}
-#endif /* !HAVE_WINSOCK */
+#endif /* !_WIN32 */
/* End of ../libcutils/socket_local_client.c */
#define logger_for_each(logger, logger_list) \
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/log_time.cpp b/liblog/log_time.cpp
index 50742df..9d5ea0e 100644
--- a/liblog/log_time.cpp
+++ b/liblog/log_time.cpp
@@ -22,7 +22,7 @@
#include <log/log_read.h>
-const char log_time::default_format[] = "%m-%d %H:%M:%S.%3q";
+const char log_time::default_format[] = "%m-%d %H:%M:%S.%q";
const timespec log_time::EPOCH = { 0, 0 };
// Add %#q for fractional seconds to standard strptime function
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/logprint.c b/liblog/logprint.c
index a3f1d7e..c2f1545 100644
--- a/liblog/logprint.c
+++ b/liblog/logprint.c
@@ -32,6 +32,9 @@
#include <log/logd.h>
#include <log/logprint.h>
+/* open coded fragment, prevent circular dependencies */
+#define WEAK static
+
typedef struct FilterInfo_t {
char *mTag;
android_LogPriority mPri;
@@ -44,6 +47,7 @@
AndroidLogPrintFormat format;
bool colored_output;
bool usec_time_output;
+ bool printable_output;
};
/*
@@ -187,6 +191,7 @@
p_ret->format = FORMAT_BRIEF;
p_ret->colored_output = false;
p_ret->usec_time_output = false;
+ p_ret->printable_output = false;
return p_ret;
}
@@ -212,13 +217,18 @@
int android_log_setPrintFormat(AndroidLogFormat *p_format,
AndroidLogPrintFormat format)
{
- if (format == FORMAT_MODIFIER_COLOR) {
+ switch (format) {
+ case FORMAT_MODIFIER_COLOR:
p_format->colored_output = true;
return 0;
- }
- if (format == FORMAT_MODIFIER_TIME_USEC) {
+ case FORMAT_MODIFIER_TIME_USEC:
p_format->usec_time_output = true;
return 0;
+ case FORMAT_MODIFIER_PRINTABLE:
+ p_format->printable_output = true;
+ return 0;
+ default:
+ break;
}
p_format->format = format;
return 1;
@@ -241,6 +251,7 @@
else if (strcmp(formatString, "long") == 0) format = FORMAT_LONG;
else if (strcmp(formatString, "color") == 0) format = FORMAT_MODIFIER_COLOR;
else if (strcmp(formatString, "usec") == 0) format = FORMAT_MODIFIER_TIME_USEC;
+ else if (strcmp(formatString, "printable") == 0) format = FORMAT_MODIFIER_PRINTABLE;
else format = FORMAT_OFF;
return format;
@@ -276,29 +287,35 @@
}
if(0 == strncmp("*", filterExpression, tagNameLength)) {
- // This filter expression refers to the global filter
- // The default level for this is DEBUG if the priority
- // is unspecified
+ /*
+ * This filter expression refers to the global filter
+ * The default level for this is DEBUG if the priority
+ * is unspecified
+ */
if (pri == ANDROID_LOG_DEFAULT) {
pri = ANDROID_LOG_DEBUG;
}
p_format->global_pri = pri;
} else {
- // for filter expressions that don't refer to the global
- // filter, the default is verbose if the priority is unspecified
+ /*
+ * for filter expressions that don't refer to the global
+ * filter, the default is verbose if the priority is unspecified
+ */
if (pri == ANDROID_LOG_DEFAULT) {
pri = ANDROID_LOG_VERBOSE;
}
char *tagName;
-// Presently HAVE_STRNDUP is never defined, so the second case is always taken
-// Darwin doesn't have strnup, everything else does
+/*
+ * Presently HAVE_STRNDUP is never defined, so the second case is always taken
+ * Darwin doesn't have strnup, everything else does
+ */
#ifdef HAVE_STRNDUP
tagName = strndup(filterExpression, tagNameLength);
#else
- //a few extra bytes copied...
+ /* a few extra bytes copied... */
tagName = strdup(filterExpression);
tagName[tagNameLength] = '\0';
#endif /*HAVE_STRNDUP*/
@@ -335,9 +352,9 @@
char *p_ret;
int err;
- // Yes, I'm using strsep
+ /* Yes, I'm using strsep */
while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
- // ignore whitespace-only entries
+ /* ignore whitespace-only entries */
if(p_ret[0] != '\0') {
err = android_log_addFilterRule(p_format, p_ret);
@@ -381,8 +398,10 @@
* When that happens, we must null-terminate the message ourselves.
*/
if (buf->len < 3) {
- // An well-formed entry must consist of at least a priority
- // and two null characters
+ /*
+ * An well-formed entry must consist of at least a priority
+ * and two null characters
+ */
fprintf(stderr, "+++ LOG: entry too small\n");
return -1;
}
@@ -412,7 +431,7 @@
return -1;
}
if (msgEnd == -1) {
- // incoming message not null-terminated; force it
+ /* incoming message not null-terminated; force it */
msgEnd = buf->len - 1;
msg[msgEnd] = '\0';
}
@@ -473,8 +492,6 @@
type = *eventData++;
eventDataLen--;
- //fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen);
-
switch (type) {
case EVENT_TYPE_INT:
/* 32-bit signed int */
@@ -735,6 +752,122 @@
return 0;
}
+/*
+ * One utf8 character at a time
+ *
+ * Returns the length of the utf8 character in the buffer,
+ * or -1 if illegal or truncated
+ *
+ * Open coded from libutils/Unicode.cpp, borrowed from utf8_length(),
+ * can not remove from here because of library circular dependencies.
+ * Expect one-day utf8_character_length with the same signature could
+ * _also_ be part of libutils/Unicode.cpp if its usefullness needs to
+ * propagate globally.
+ */
+WEAK ssize_t utf8_character_length(const char *src, size_t len)
+{
+ const char *cur = src;
+ const char first_char = *cur++;
+ static const uint32_t kUnicodeMaxCodepoint = 0x0010FFFF;
+ int32_t mask, to_ignore_mask;
+ size_t num_to_read;
+ uint32_t utf32;
+
+ if ((first_char & 0x80) == 0) { /* ASCII */
+ return 1;
+ }
+
+ /*
+ * (UTF-8's character must not be like 10xxxxxx,
+ * but 110xxxxx, 1110xxxx, ... or 1111110x)
+ */
+ if ((first_char & 0x40) == 0) {
+ return -1;
+ }
+
+ for (utf32 = 1, num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80;
+ num_to_read < 5 && (first_char & mask);
+ num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
+ if (num_to_read > len) {
+ return -1;
+ }
+ if ((*cur & 0xC0) != 0x80) { /* can not be 10xxxxxx? */
+ return -1;
+ }
+ utf32 = (utf32 << 6) + (*cur++ & 0b00111111);
+ }
+ /* "first_char" must be (110xxxxx - 11110xxx) */
+ if (num_to_read >= 5) {
+ return -1;
+ }
+ to_ignore_mask |= mask;
+ utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1));
+ if (utf32 > kUnicodeMaxCodepoint) {
+ return -1;
+ }
+ return num_to_read;
+}
+
+/*
+ * Convert to printable from message to p buffer, return string length. If p is
+ * NULL, do not copy, but still return the expected string length.
+ */
+static size_t convertPrintable(char *p, const char *message, size_t messageLen)
+{
+ char *begin = p;
+ bool print = p != NULL;
+
+ while (messageLen) {
+ char buf[6];
+ ssize_t len = sizeof(buf) - 1;
+ if ((size_t)len > messageLen) {
+ len = messageLen;
+ }
+ len = utf8_character_length(message, len);
+
+ if (len < 0) {
+ snprintf(buf, sizeof(buf),
+ ((messageLen > 1) && isdigit(message[1]))
+ ? "\\%03o"
+ : "\\%o",
+ *message & 0377);
+ len = 1;
+ } else {
+ buf[0] = '\0';
+ if (len == 1) {
+ if (*message == '\a') {
+ strcpy(buf, "\\a");
+ } else if (*message == '\b') {
+ strcpy(buf, "\\b");
+ } else if (*message == '\t') {
+ strcpy(buf, "\\t");
+ } else if (*message == '\v') {
+ strcpy(buf, "\\v");
+ } else if (*message == '\f') {
+ strcpy(buf, "\\f");
+ } else if (*message == '\r') {
+ strcpy(buf, "\\r");
+ } else if (*message == '\\') {
+ strcpy(buf, "\\\\");
+ } else if ((*message < ' ') || (*message & 0x80)) {
+ snprintf(buf, sizeof(buf), "\\%o", *message & 0377);
+ }
+ }
+ if (!buf[0]) {
+ strncpy(buf, message, len);
+ buf[len] = '\0';
+ }
+ }
+ if (print) {
+ strcpy(p, buf);
+ }
+ p += strlen(buf);
+ message += len;
+ messageLen -= len;
+ }
+ return p - begin;
+}
+
/**
* Formats a log message into a buffer
*
@@ -778,7 +911,7 @@
#else
ptm = localtime(&(entry->tv_sec));
#endif
- //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm);
+ /* strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm); */
strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
len = strlen(timeBuf);
if (p_format->usec_time_output) {
@@ -873,24 +1006,34 @@
const char *pm;
if (prefixSuffixIsHeaderFooter) {
- // we're just wrapping message with a header/footer
+ /* we're just wrapping message with a header/footer */
numLines = 1;
} else {
pm = entry->message;
numLines = 0;
- // The line-end finding here must match the line-end finding
- // in for ( ... numLines...) loop below
+ /*
+ * The line-end finding here must match the line-end finding
+ * in for ( ... numLines...) loop below
+ */
while (pm < (entry->message + entry->messageLen)) {
if (*pm++ == '\n') numLines++;
}
- // plus one line for anything not newline-terminated at the end
+ /* plus one line for anything not newline-terminated at the end */
if (pm > entry->message && *(pm-1) != '\n') numLines++;
}
- // this is an upper bound--newlines in message may be counted
- // extraneously
- bufferSize = (numLines * (prefixLen + suffixLen)) + entry->messageLen + 1;
+ /*
+ * this is an upper bound--newlines in message may be counted
+ * extraneously
+ */
+ bufferSize = (numLines * (prefixLen + suffixLen)) + 1;
+ if (p_format->printable_output) {
+ /* Calculate extra length to convert non-printable to printable */
+ bufferSize += convertPrintable(NULL, entry->message, entry->messageLen);
+ } else {
+ bufferSize += entry->messageLen;
+ }
if (defaultBufferSize >= bufferSize) {
ret = defaultBuffer;
@@ -910,8 +1053,12 @@
if (prefixSuffixIsHeaderFooter) {
strcat(p, prefixBuf);
p += prefixLen;
- strncat(p, entry->message, entry->messageLen);
- p += entry->messageLen;
+ if (p_format->printable_output) {
+ p += convertPrintable(p, entry->message, entry->messageLen);
+ } else {
+ strncat(p, entry->message, entry->messageLen);
+ p += entry->messageLen;
+ }
strcat(p, suffixBuf);
p += suffixLen;
} else {
@@ -920,15 +1067,19 @@
size_t lineLen;
lineStart = pm;
- // Find the next end-of-line in message
+ /* Find the next end-of-line in message */
while (pm < (entry->message + entry->messageLen)
&& *pm != '\n') pm++;
lineLen = pm - lineStart;
strcat(p, prefixBuf);
p += prefixLen;
- strncat(p, lineStart, lineLen);
- p += lineLen;
+ if (p_format->printable_output) {
+ p += convertPrintable(p, lineStart, lineLen);
+ } else {
+ strncat(p, lineStart, lineLen);
+ p += lineLen;
+ }
strcat(p, suffixBuf);
p += suffixLen;
diff --git a/liblog/tests/Android.mk b/liblog/tests/Android.mk
index d75bbc9..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:
@@ -77,6 +73,6 @@
LOCAL_MODULE := $(test_module_prefix)unit-tests
LOCAL_MODULE_TAGS := $(test_tags)
LOCAL_CFLAGS += $(test_c_flags)
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_SRC_FILES := $(test_src_files)
include $(BUILD_NATIVE_TEST)
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/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index 33f6481..abe0239 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -17,6 +17,8 @@
#include <fcntl.h>
#include <inttypes.h>
#include <signal.h>
+
+#include <cutils/properties.h>
#include <gtest/gtest.h>
#include <log/log.h>
#include <log/logger.h>
@@ -439,6 +441,7 @@
LOG_FAILURE_RETRY(__android_log_buf_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
tag, max_payload_buf));
+ sleep(2);
struct logger_list *logger_list;
@@ -603,10 +606,14 @@
if (id != android_name_to_log_id(name)) {
continue;
}
+ fprintf(stderr, "log buffer %s\r", name);
struct logger * logger;
EXPECT_TRUE(NULL != (logger = android_logger_open(logger_list, id)));
EXPECT_EQ(id, android_logger_get_id(logger));
- EXPECT_LT(0, android_logger_get_log_size(logger));
+ /* crash buffer is allowed to be empty, that is actually healthy! */
+ if (android_logger_get_log_size(logger) || strcmp("crash", name)) {
+ EXPECT_LT(0, android_logger_get_log_size(logger));
+ }
EXPECT_LT(0, android_logger_get_log_readable_size(logger));
EXPECT_LT(0, android_logger_get_log_version(logger));
}
@@ -682,3 +689,190 @@
android_log_format_free(p_format);
}
+
+TEST(liblog, is_loggable) {
+ static const char tag[] = "is_loggable";
+ static const char log_namespace[] = "persist.log.tag.";
+ static const size_t base_offset = 8; /* skip "persist." */
+ // sizeof("string") = strlen("string") + 1
+ char key[sizeof(log_namespace) + sizeof(tag) - 1];
+ char hold[4][PROP_VALUE_MAX];
+ static const struct {
+ int level;
+ char type;
+ } levels[] = {
+ { ANDROID_LOG_VERBOSE, 'v' },
+ { ANDROID_LOG_DEBUG , 'd' },
+ { ANDROID_LOG_INFO , 'i' },
+ { ANDROID_LOG_WARN , 'w' },
+ { ANDROID_LOG_ERROR , 'e' },
+ { ANDROID_LOG_FATAL , 'a' },
+ { -1 , 's' },
+ { -2 , 'g' }, // Illegal value, resort to default
+ };
+
+ // Set up initial test condition
+ memset(hold, 0, sizeof(hold));
+ snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+ property_get(key, hold[0], "");
+ property_set(key, "");
+ property_get(key + base_offset, hold[1], "");
+ property_set(key + base_offset, "");
+ strcpy(key, log_namespace);
+ key[sizeof(log_namespace) - 2] = '\0';
+ property_get(key, hold[2], "");
+ property_set(key, "");
+ property_get(key, hold[3], "");
+ property_set(key + base_offset, "");
+
+ // All combinations of level and defaults
+ for(size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
+ if (levels[i].level == -2) {
+ continue;
+ }
+ for(size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
+ if (levels[j].level == -2) {
+ continue;
+ }
+ fprintf(stderr, "i=%zu j=%zu\r", i, j);
+ if ((levels[i].level < levels[j].level)
+ || (levels[j].level == -1)) {
+ EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
+ levels[j].level));
+ } else {
+ EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
+ levels[j].level));
+ }
+ }
+ }
+
+ // All combinations of level and tag and global properties
+ for(size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
+ if (levels[i].level == -2) {
+ continue;
+ }
+ for(size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
+ char buf[2];
+ buf[0] = levels[j].type;
+ buf[1] = '\0';
+
+ snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+ fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
+ i, j, key, buf);
+ property_set(key, buf);
+ if ((levels[i].level < levels[j].level)
+ || (levels[j].level == -1)
+ || ((levels[i].level < ANDROID_LOG_DEBUG)
+ && (levels[j].level == -2))) {
+ EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ } else {
+ EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ }
+ property_set(key, "");
+
+ fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
+ i, j, key + base_offset, buf);
+ property_set(key + base_offset, buf);
+ if ((levels[i].level < levels[j].level)
+ || (levels[j].level == -1)
+ || ((levels[i].level < ANDROID_LOG_DEBUG)
+ && (levels[j].level == -2))) {
+ EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ } else {
+ EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ }
+ property_set(key + base_offset, "");
+
+ strcpy(key, log_namespace);
+ key[sizeof(log_namespace) - 2] = '\0';
+ fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
+ i, j, key, buf);
+ property_set(key, buf);
+ if ((levels[i].level < levels[j].level)
+ || (levels[j].level == -1)
+ || ((levels[i].level < ANDROID_LOG_DEBUG)
+ && (levels[j].level == -2))) {
+ EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ } else {
+ EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ }
+ property_set(key, "");
+
+ fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
+ i, j, key + base_offset, buf);
+ property_set(key + base_offset, buf);
+ if ((levels[i].level < levels[j].level)
+ || (levels[j].level == -1)
+ || ((levels[i].level < ANDROID_LOG_DEBUG)
+ && (levels[j].level == -2))) {
+ EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ } else {
+ EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ }
+ property_set(key + base_offset, "");
+ }
+ }
+
+ // All combinations of level and tag properties, but with global set to INFO
+ strcpy(key, log_namespace);
+ key[sizeof(log_namespace) - 2] = '\0';
+ property_set(key, "I");
+ snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+ for(size_t i = 0; i < (sizeof(levels) / sizeof(levels[0])); ++i) {
+ if (levels[i].level == -2) {
+ continue;
+ }
+ for(size_t j = 0; j < (sizeof(levels) / sizeof(levels[0])); ++j) {
+ char buf[2];
+ buf[0] = levels[j].type;
+ buf[1] = '\0';
+
+ fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
+ i, j, key, buf);
+ property_set(key, buf);
+ if ((levels[i].level < levels[j].level)
+ || (levels[j].level == -1)
+ || ((levels[i].level < ANDROID_LOG_INFO) // Yes INFO
+ && (levels[j].level == -2))) {
+ EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ } else {
+ EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ }
+ property_set(key, "");
+
+ fprintf(stderr, "i=%zu j=%zu property_set(\"%s\",\"%s\")\r",
+ i, j, key + base_offset, buf);
+ property_set(key + base_offset, buf);
+ if ((levels[i].level < levels[j].level)
+ || (levels[j].level == -1)
+ || ((levels[i].level < ANDROID_LOG_INFO) // Yes INFO
+ && (levels[j].level == -2))) {
+ EXPECT_FALSE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ } else {
+ EXPECT_TRUE(__android_log_is_loggable(levels[i].level, tag,
+ ANDROID_LOG_DEBUG));
+ }
+ property_set(key + base_offset, "");
+ }
+ }
+
+ // reset parms
+ snprintf(key, sizeof(key), "%s%s", log_namespace, tag);
+ property_set(key, hold[0]);
+ property_set(key + base_offset, hold[1]);
+ strcpy(key, log_namespace);
+ key[sizeof(log_namespace) - 2] = '\0';
+ property_set(key, hold[2]);
+ property_set(key + base_offset, hold[3]);
+}
diff --git a/libnativebridge/Android.mk b/libnativebridge/Android.mk
index 83169eb..d20d44c 100644
--- a/libnativebridge/Android.mk
+++ b/libnativebridge/Android.mk
@@ -37,4 +37,22 @@
include $(BUILD_HOST_SHARED_LIBRARY)
+# Static library for host
+# ========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:= libnativebridge
+
+LOCAL_SRC_FILES:= $(NATIVE_BRIDGE_COMMON_SRC_FILES)
+LOCAL_STATIC_LIBRARIES := liblog
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CFLAGS += -Werror -Wall
+LOCAL_CPPFLAGS := -std=gnu++11 -fvisibility=protected
+LOCAL_LDFLAGS := -ldl
+LOCAL_MULTILIB := both
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
include $(LOCAL_PATH)/tests/Android.mk
diff --git a/libnativebridge/native_bridge.cc b/libnativebridge/native_bridge.cc
index f63497b..32a65ea 100644
--- a/libnativebridge/native_bridge.cc
+++ b/libnativebridge/native_bridge.cc
@@ -109,6 +109,13 @@
}
}
+static void ReleaseAppCodeCacheDir() {
+ if (app_code_cache_dir != nullptr) {
+ delete[] app_code_cache_dir;
+ app_code_cache_dir = nullptr;
+ }
+}
+
// We only allow simple names for the library. It is supposed to be a file in
// /system/lib or /vendor/lib. Only allow a small range of characters, that is
// names consisting of [a-zA-Z0-9._-] and starting with [a-zA-Z].
@@ -162,8 +169,7 @@
static void CloseNativeBridge(bool with_error) {
state = NativeBridgeState::kClosed;
had_error |= with_error;
- delete[] app_code_cache_dir;
- app_code_cache_dir = nullptr;
+ ReleaseAppCodeCacheDir();
}
bool LoadNativeBridge(const char* nb_library_filename,
@@ -289,13 +295,13 @@
// so we save the extra file existence check.
char cpuinfo_path[1024];
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
snprintf(cpuinfo_path, sizeof(cpuinfo_path), "/system/lib"
#ifdef __LP64__
"64"
#endif // __LP64__
"/%s/cpuinfo", instruction_set);
-#else // !HAVE_ANDROID_OS
+#else // !__ANDROID__
// To be able to test on the host, we hardwire a relative path.
snprintf(cpuinfo_path, sizeof(cpuinfo_path), "./cpuinfo");
#endif
@@ -406,16 +412,16 @@
if (stat(app_code_cache_dir, &st) == -1) {
if (errno == ENOENT) {
if (mkdir(app_code_cache_dir, S_IRWXU | S_IRWXG | S_IXOTH) == -1) {
- ALOGE("Cannot create code cache directory %s: %s.", app_code_cache_dir, strerror(errno));
- CloseNativeBridge(true);
+ ALOGW("Cannot create code cache directory %s: %s.", app_code_cache_dir, strerror(errno));
+ ReleaseAppCodeCacheDir();
}
} else {
- ALOGE("Cannot stat code cache directory %s: %s.", app_code_cache_dir, strerror(errno));
- CloseNativeBridge(true);
+ ALOGW("Cannot stat code cache directory %s: %s.", app_code_cache_dir, strerror(errno));
+ ReleaseAppCodeCacheDir();
}
} else if (!S_ISDIR(st.st_mode)) {
- ALOGE("Code cache is not a directory %s.", app_code_cache_dir);
- CloseNativeBridge(true);
+ ALOGW("Code cache is not a directory %s.", app_code_cache_dir);
+ ReleaseAppCodeCacheDir();
}
// If we're still PreInitialized (dind't fail the code cache checks) try to initialize.
@@ -424,8 +430,7 @@
SetupEnvironment(callbacks, env, instruction_set);
state = NativeBridgeState::kInitialized;
// We no longer need the code cache path, release the memory.
- delete[] app_code_cache_dir;
- app_code_cache_dir = nullptr;
+ ReleaseAppCodeCacheDir();
} else {
// Unload the library.
dlclose(native_bridge_handle);
diff --git a/libnativebridge/tests/Android.mk b/libnativebridge/tests/Android.mk
index 285e8c2..7265939 100644
--- a/libnativebridge/tests/Android.mk
+++ b/libnativebridge/tests/Android.mk
@@ -9,6 +9,7 @@
test_src_files := \
CodeCacheCreate_test.cpp \
CodeCacheExists_test.cpp \
+ CodeCacheStatFail_test.cpp \
CompleteFlow_test.cpp \
InvalidCharsNativeBridge_test.cpp \
NativeBridge2Signal_test.cpp \
diff --git a/libnativebridge/tests/CodeCacheStatFail_test.cpp b/libnativebridge/tests/CodeCacheStatFail_test.cpp
new file mode 100644
index 0000000..4ea519e
--- /dev/null
+++ b/libnativebridge/tests/CodeCacheStatFail_test.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 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 "NativeBridgeTest.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace android {
+
+// Tests that the bridge is initialized without errors if the code_cache is
+// existed as a file.
+TEST_F(NativeBridgeTest, CodeCacheStatFail) {
+ int fd = creat(kCodeCache, O_RDWR);
+ ASSERT_NE(-1, fd);
+ close(fd);
+
+ struct stat st;
+ ASSERT_EQ(-1, stat(kCodeCacheStatFail, &st));
+ ASSERT_EQ(ENOTDIR, errno);
+
+ // Init
+ ASSERT_TRUE(LoadNativeBridge(kNativeBridgeLibrary, nullptr));
+ ASSERT_TRUE(PreInitializeNativeBridge(kCodeCacheStatFail, "isa"));
+ ASSERT_TRUE(InitializeNativeBridge(nullptr, nullptr));
+ ASSERT_TRUE(NativeBridgeAvailable());
+ ASSERT_FALSE(NativeBridgeError());
+
+ // Clean up
+ UnloadNativeBridge();
+
+ ASSERT_FALSE(NativeBridgeError());
+ unlink(kCodeCache);
+}
+
+} // namespace android
diff --git a/libnativebridge/tests/NativeBridgeTest.h b/libnativebridge/tests/NativeBridgeTest.h
index 6a5c126..d489420 100644
--- a/libnativebridge/tests/NativeBridgeTest.h
+++ b/libnativebridge/tests/NativeBridgeTest.h
@@ -24,6 +24,7 @@
constexpr const char* kNativeBridgeLibrary = "libnativebridge-dummy.so";
constexpr const char* kCodeCache = "./code_cache";
+constexpr const char* kCodeCacheStatFail = "./code_cache/temp";
namespace android {
diff --git a/libnativebridge/tests/PreInitializeNativeBridge_test.cpp b/libnativebridge/tests/PreInitializeNativeBridge_test.cpp
index cec26ce..d3bbebe 100644
--- a/libnativebridge/tests/PreInitializeNativeBridge_test.cpp
+++ b/libnativebridge/tests/PreInitializeNativeBridge_test.cpp
@@ -32,8 +32,8 @@
TEST_F(NativeBridgeTest, PreInitializeNativeBridge) {
ASSERT_TRUE(LoadNativeBridge(kNativeBridgeLibrary, nullptr));
-#ifndef __APPLE__ // Mac OS does not support bind-mount.
-#ifndef HAVE_ANDROID_OS // Cannot write into the hard-wired location.
+#if !defined(__APPLE__) // Mac OS does not support bind-mount.
+#if !defined(__ANDROID__) // Cannot write into the hard-wired location.
// Try to create our mount namespace.
if (unshare(CLONE_NEWNS) != -1) {
// Create a dummy file.
diff --git a/libnetutils/ifc_utils.c b/libnetutils/ifc_utils.c
index 7d2a5fb..956ed30 100644
--- a/libnetutils/ifc_utils.c
+++ b/libnetutils/ifc_utils.c
@@ -50,7 +50,7 @@
#define ALOGW printf
#endif
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
/* SIOCKILLADDR is an Android extension. */
#define SIOCKILLADDR 0x8939
#endif
@@ -596,7 +596,7 @@
int ifc_reset_connections(const char *ifname, const int reset_mask)
{
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
int result, success;
in_addr_t myaddr = 0;
struct ifreq ifr;
diff --git a/libpixelflinger/Android.mk b/libpixelflinger/Android.mk
index 697db25..11e7988 100644
--- a/libpixelflinger/Android.mk
+++ b/libpixelflinger/Android.mk
@@ -70,10 +70,6 @@
# libhardware, but this at least gets us built.
LOCAL_SHARED_LIBRARIES += libhardware_legacy
LOCAL_CFLAGS += -DWITH_LIB_HARDWARE
-# t32cb16blend.S does not compile with Clang.
-LOCAL_CLANG_ASFLAGS_arm += -no-integrated-as
-# arch-arm64/col32cb16blend.S does not compile with Clang.
-LOCAL_CLANG_ASFLAGS_arm64 += -no-integrated-as
include $(BUILD_SHARED_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/libpixelflinger/arch-arm64/col32cb16blend.S b/libpixelflinger/arch-arm64/col32cb16blend.S
index 18a01fd..8d9c7c4 100644
--- a/libpixelflinger/arch-arm64/col32cb16blend.S
+++ b/libpixelflinger/arch-arm64/col32cb16blend.S
@@ -26,7 +26,7 @@
* SUCH DAMAGE.
*/
.text
- .align
+ .align 0
.global scanline_col32cb16blend_arm64
diff --git a/libpixelflinger/arch-arm64/t32cb16blend.S b/libpixelflinger/arch-arm64/t32cb16blend.S
index 7da8cf5..230f47b 100644
--- a/libpixelflinger/arch-arm64/t32cb16blend.S
+++ b/libpixelflinger/arch-arm64/t32cb16blend.S
@@ -26,7 +26,7 @@
* SUCH DAMAGE.
*/
.text
- .align
+ .align 0
.global scanline_t32cb16blend_arm64
diff --git a/libpixelflinger/t32cb16blend.S b/libpixelflinger/t32cb16blend.S
index caf9eb7..1d40ad4 100644
--- a/libpixelflinger/t32cb16blend.S
+++ b/libpixelflinger/t32cb16blend.S
@@ -17,6 +17,7 @@
.text
+ .syntax unified
.align
.global scanline_t32cb16blend_arm
@@ -146,7 +147,7 @@
tst r0, #0x3
beq aligned
subs r2, r2, #1
- ldmlofd sp!, {r4-r7, lr} // return
+ ldmfdlo sp!, {r4-r7, lr} // return
bxlo lr
last:
@@ -197,6 +198,6 @@
mov r4, r5
9: adds r2, r2, #1
- ldmlofd sp!, {r4-r7, lr} // return
+ ldmfdlo sp!, {r4-r7, lr} // return
bxlo lr
b last
diff --git a/libpixelflinger/tests/arch-arm64/assembler/Android.mk b/libpixelflinger/tests/arch-arm64/assembler/Android.mk
index 448d298..bd0f24b 100644
--- a/libpixelflinger/tests/arch-arm64/assembler/Android.mk
+++ b/libpixelflinger/tests/arch-arm64/assembler/Android.mk
@@ -5,9 +5,6 @@
arm64_assembler_test.cpp\
asm_test_jacket.S
-# asm_test_jacket.S does not compile with Clang.
-LOCAL_CLANG_ASFLAGS_arm64 += -no-integrated-as
-
LOCAL_SHARED_LIBRARIES := \
libcutils \
libpixelflinger
diff --git a/libpixelflinger/tests/arch-arm64/assembler/asm_test_jacket.S b/libpixelflinger/tests/arch-arm64/assembler/asm_test_jacket.S
index a1392c2..f44859f 100644
--- a/libpixelflinger/tests/arch-arm64/assembler/asm_test_jacket.S
+++ b/libpixelflinger/tests/arch-arm64/assembler/asm_test_jacket.S
@@ -27,7 +27,7 @@
*/
.text
- .align
+ .align 0
.global asm_test_jacket
diff --git a/libpixelflinger/tests/arch-arm64/col32cb16blend/Android.mk b/libpixelflinger/tests/arch-arm64/col32cb16blend/Android.mk
index 5d69203..3368eb0 100644
--- a/libpixelflinger/tests/arch-arm64/col32cb16blend/Android.mk
+++ b/libpixelflinger/tests/arch-arm64/col32cb16blend/Android.mk
@@ -5,8 +5,6 @@
col32cb16blend_test.c \
../../../arch-arm64/col32cb16blend.S
-LOCAL_CLANG_ASFLAGS_arm64 += -no-integrated-as
-
LOCAL_SHARED_LIBRARIES :=
LOCAL_C_INCLUDES :=
diff --git a/libpixelflinger/tests/arch-arm64/t32cb16blend/Android.mk b/libpixelflinger/tests/arch-arm64/t32cb16blend/Android.mk
index 2c1379b..8e5ec5e 100644
--- a/libpixelflinger/tests/arch-arm64/t32cb16blend/Android.mk
+++ b/libpixelflinger/tests/arch-arm64/t32cb16blend/Android.mk
@@ -5,8 +5,6 @@
t32cb16blend_test.c \
../../../arch-arm64/t32cb16blend.S
-LOCAL_CLANG_ASFLAGS_arm64 += -no-integrated-as
-
LOCAL_SHARED_LIBRARIES :=
LOCAL_C_INCLUDES :=
diff --git a/libsparse/backed_block.c b/libsparse/backed_block.c
index 3e72b57..794cd6b 100644
--- a/libsparse/backed_block.c
+++ b/libsparse/backed_block.c
@@ -221,7 +221,8 @@
}
break;
case BACKED_BLOCK_FILE:
- if (a->file.filename != b->file.filename ||
+ /* Already make sure b->type is BACKED_BLOCK_FILE */
+ if (strcmp(a->file.filename, b->file.filename) ||
a->file.offset + a->len != b->file.offset) {
return -EINVAL;
}
@@ -279,7 +280,10 @@
}
merge_bb(bbl, new_bb, new_bb->next);
- merge_bb(bbl, bb, new_bb);
+ if (!merge_bb(bbl, bb, new_bb)) {
+ /* new_bb destroyed, point to retained as last_used */
+ bbl->last_used = bb;
+ }
return 0;
}
diff --git a/libsparse/sparse_crc32.h b/libsparse/sparse_crc32.h
index cad8a86..50cd9e9 100644
--- a/libsparse/sparse_crc32.h
+++ b/libsparse/sparse_crc32.h
@@ -14,7 +14,19 @@
* limitations under the License.
*/
+#ifndef _LIBSPARSE_SPARSE_CRC32_H_
+#define _LIBSPARSE_SPARSE_CRC32_H_
+
#include <stdint.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
uint32_t sparse_crc32(uint32_t crc, const void *buf, size_t size);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libutils/Android.mk b/libutils/Android.mk
index e9c5f89..8f829f3 100644
--- a/libutils/Android.mk
+++ b/libutils/Android.mk
@@ -91,7 +91,7 @@
liblog \
libdl
-LOCAL_MODULE:= libutils
+LOCAL_MODULE := libutils
include $(BUILD_STATIC_LIBRARY)
# For the device, shared
diff --git a/libutils/JenkinsHash.cpp b/libutils/JenkinsHash.cpp
index 52c9bb7..ff5d252 100644
--- a/libutils/JenkinsHash.cpp
+++ b/libutils/JenkinsHash.cpp
@@ -19,10 +19,14 @@
* should still be quite good.
**/
+#include <stdlib.h>
#include <utils/JenkinsHash.h>
namespace android {
+#ifdef __clang__
+__attribute__((no_sanitize("integer")))
+#endif
hash_t JenkinsHashWhiten(uint32_t hash) {
hash += (hash << 3);
hash ^= (hash >> 11);
@@ -31,6 +35,9 @@
}
uint32_t JenkinsHashMixBytes(uint32_t hash, const uint8_t* bytes, size_t size) {
+ if (size > UINT32_MAX) {
+ abort();
+ }
hash = JenkinsHashMix(hash, (uint32_t)size);
size_t i;
for (i = 0; i < (size & -4); i += 4) {
@@ -47,6 +54,9 @@
}
uint32_t JenkinsHashMixShorts(uint32_t hash, const uint16_t* shorts, size_t size) {
+ if (size > UINT32_MAX) {
+ abort();
+ }
hash = JenkinsHashMix(hash, (uint32_t)size);
size_t i;
for (i = 0; i < (size & -2); i += 2) {
diff --git a/libutils/Looper.cpp b/libutils/Looper.cpp
index 9a2dd6c..20c1e92 100644
--- a/libutils/Looper.cpp
+++ b/libutils/Looper.cpp
@@ -17,9 +17,11 @@
#include <utils/Looper.h>
#include <utils/Timers.h>
-#include <unistd.h>
+#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <string.h>
+#include <unistd.h>
namespace android {
@@ -71,32 +73,32 @@
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
int result = pipe(wakeFds);
- LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe: %s", strerror(errno));
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
- LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
- errno);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking: %s",
+ strerror(errno));
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
- LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
- errno);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking: %s",
+ strerror(errno));
mIdling = false;
// Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
- LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
+ LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
- LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
- errno);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance: %s",
+ strerror(errno));
}
Looper::~Looper() {
@@ -233,7 +235,7 @@
if (errno == EINTR) {
goto Done;
}
- ALOGW("Poll failed with an unexpected error, errno=%d", errno);
+ ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
result = POLL_ERROR;
goto Done;
}
@@ -377,7 +379,7 @@
if (nWrite != 1) {
if (errno != EAGAIN) {
- ALOGW("Could not write wake signal, errno=%d", errno);
+ ALOGW("Could not write wake signal: %s", strerror(errno));
}
}
}
@@ -447,14 +449,14 @@
if (requestIndex < 0) {
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
if (epollResult < 0) {
- ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
+ ALOGE("Error adding epoll events for fd %d: %s", fd, strerror(errno));
return -1;
}
mRequests.add(fd, request);
} else {
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
if (epollResult < 0) {
- ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
+ ALOGE("Error modifying epoll events for fd %d: %s", fd, strerror(errno));
return -1;
}
mRequests.replaceValueAt(requestIndex, request);
@@ -477,7 +479,7 @@
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, NULL);
if (epollResult < 0) {
- ALOGE("Error removing epoll events for fd %d, errno=%d", fd, errno);
+ ALOGE("Error removing epoll events for fd %d: %s", fd, strerror(errno));
return -1;
}
diff --git a/libutils/ProcessCallStack.cpp b/libutils/ProcessCallStack.cpp
index db07e56..011c302 100644
--- a/libutils/ProcessCallStack.cpp
+++ b/libutils/ProcessCallStack.cpp
@@ -17,9 +17,10 @@
#define LOG_TAG "ProcessCallStack"
// #define LOG_NDEBUG 0
-#include <string.h>
-#include <stdio.h>
#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
#include <utils/Log.h>
#include <utils/Errors.h>
@@ -135,8 +136,8 @@
dp = opendir(PATH_SELF_TASK);
if (dp == NULL) {
- ALOGE("%s: Failed to update the process's call stacks (errno = %d, '%s')",
- __FUNCTION__, errno, strerror(errno));
+ ALOGE("%s: Failed to update the process's call stacks: %s",
+ __FUNCTION__, strerror(errno));
return;
}
@@ -172,8 +173,8 @@
ssize_t idx = mThreadMap.add(tid, ThreadInfo());
if (idx < 0) { // returns negative error value on error
- ALOGE("%s: Failed to add new ThreadInfo (errno = %zd, '%s')",
- __FUNCTION__, idx, strerror(-idx));
+ ALOGE("%s: Failed to add new ThreadInfo: %s",
+ __FUNCTION__, strerror(-idx));
continue;
}
@@ -195,8 +196,8 @@
__FUNCTION__, tid, threadInfo.callStack.size());
}
if (code != 0) { // returns positive error value on error
- ALOGE("%s: Failed to readdir from %s (errno = %d, '%s')",
- __FUNCTION__, PATH_SELF_TASK, -code, strerror(code));
+ ALOGE("%s: Failed to readdir from %s: %s",
+ __FUNCTION__, PATH_SELF_TASK, strerror(code));
}
#endif
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 3323b82..69313ea 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -16,6 +16,7 @@
#include <utils/String8.h>
+#include <utils/Compat.h>
#include <utils/Log.h>
#include <utils/Unicode.h>
#include <utils/SharedBuffer.h>
@@ -78,6 +79,9 @@
static char* allocFromUTF8(const char* in, size_t len)
{
if (len > 0) {
+ if (len == SIZE_MAX) {
+ return NULL;
+ }
SharedBuffer* buf = SharedBuffer::alloc(len+1);
ALOG_ASSERT(buf, "Unable to allocate shared buffer");
if (buf) {
diff --git a/libutils/SystemClock.cpp b/libutils/SystemClock.cpp
index ac3dd98..1fca2b2 100644
--- a/libutils/SystemClock.cpp
+++ b/libutils/SystemClock.cpp
@@ -19,7 +19,7 @@
* System clock functions.
*/
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
#include <linux/ioctl.h>
#include <linux/rtc.h>
#include <utils/Atomic.h>
@@ -29,7 +29,6 @@
#include <sys/time.h>
#include <limits.h>
#include <fcntl.h>
-#include <errno.h>
#include <string.h>
#include <utils/SystemClock.h>
@@ -108,7 +107,7 @@
*/
int64_t elapsedRealtimeNano()
{
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
struct timespec ts;
int result;
int64_t timestamp;
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 1e014c6..6dda6b5 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -22,6 +22,7 @@
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#if !defined(_WIN32)
@@ -44,7 +45,7 @@
#include <cutils/sched_policy.h>
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
# define __android_unused
#else
# define __android_unused __attribute__((__unused__))
@@ -131,7 +132,7 @@
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-#ifdef HAVE_ANDROID_OS /* valgrind is rejecting RT-priority create reqs */
+#if defined(__ANDROID__) /* valgrind is rejecting RT-priority create reqs */
if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) {
// Now that the pthread_t has a method to find the associated
// android_thread_id_t (pid) from pthread_t, it would be possible to avoid
@@ -160,9 +161,9 @@
(android_pthread_entry)entryFunction, userData);
pthread_attr_destroy(&attr);
if (result != 0) {
- ALOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, errno=%d)\n"
+ ALOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, %s)\n"
"(android threadPriority=%d)",
- entryFunction, result, errno, threadPriority);
+ entryFunction, result, strerror(errno), threadPriority);
return 0;
}
@@ -175,7 +176,7 @@
return 1;
}
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
static pthread_t android_thread_id_t_to_pthread(android_thread_id_t thread)
{
return (pthread_t) thread;
@@ -301,12 +302,10 @@
gCreateThreadFn = func;
}
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
int androidSetThreadPriority(pid_t tid, int pri)
{
int rc = 0;
-
-#if !defined(_WIN32)
int lasterr = 0;
if (pri >= ANDROID_PRIORITY_BACKGROUND) {
@@ -324,17 +323,12 @@
} else {
errno = lasterr;
}
-#endif
return rc;
}
int androidGetThreadPriority(pid_t tid) {
-#if !defined(_WIN32)
return getpriority(PRIO_PROCESS, tid);
-#else
- return ANDROID_PRIORITY_NORMAL;
-#endif
}
#endif
@@ -657,7 +651,7 @@
mLock("Thread::mLock"),
mStatus(NO_ERROR),
mExitPending(false), mRunning(false)
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
, mTid(-1)
#endif
{
@@ -727,7 +721,7 @@
wp<Thread> weak(strong);
self->mHoldSelf.clear();
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
// this is very useful for debugging with gdb
self->mTid = gettid();
#endif
@@ -838,7 +832,7 @@
return mRunning;
}
-#ifdef HAVE_ANDROID_OS
+#if defined(__ANDROID__)
pid_t Thread::getTid() const
{
// mTid is not defined until the child initializes it, and the caller may need it earlier
diff --git a/libutils/Timers.cpp b/libutils/Timers.cpp
index fb70e15..201bc41 100644
--- a/libutils/Timers.cpp
+++ b/libutils/Timers.cpp
@@ -23,7 +23,7 @@
#include <sys/time.h>
#include <time.h>
-#if defined(HAVE_ANDROID_OS)
+#if defined(__ANDROID__)
nsecs_t systemTime(int clock)
{
static const clockid_t clocks[] = {
diff --git a/libutils/Tokenizer.cpp b/libutils/Tokenizer.cpp
index 610002f..2d0e83d 100644
--- a/libutils/Tokenizer.cpp
+++ b/libutils/Tokenizer.cpp
@@ -56,12 +56,12 @@
int fd = ::open(filename.string(), O_RDONLY);
if (fd < 0) {
result = -errno;
- ALOGE("Error opening file '%s', %s.", filename.string(), strerror(errno));
+ ALOGE("Error opening file '%s': %s", filename.string(), strerror(errno));
} else {
struct stat stat;
if (fstat(fd, &stat)) {
result = -errno;
- ALOGE("Error getting size of file '%s', %s.", filename.string(), strerror(errno));
+ ALOGE("Error getting size of file '%s': %s", filename.string(), strerror(errno));
} else {
size_t length = size_t(stat.st_size);
@@ -83,7 +83,7 @@
ssize_t nrd = read(fd, buffer, length);
if (nrd < 0) {
result = -errno;
- ALOGE("Error reading file '%s', %s.", filename.string(), strerror(errno));
+ ALOGE("Error reading file '%s': %s", filename.string(), strerror(errno));
delete[] buffer;
buffer = NULL;
} else {
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index fb876c9..6f4b721 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -18,7 +18,7 @@
#include <stddef.h>
-#ifdef HAVE_WINSOCK
+#if defined(_WIN32)
# undef nhtol
# undef htonl
# undef nhtos
diff --git a/libutils/VectorImpl.cpp b/libutils/VectorImpl.cpp
index 30ca663..bdb54b1 100644
--- a/libutils/VectorImpl.cpp
+++ b/libutils/VectorImpl.cpp
@@ -551,6 +551,10 @@
ssize_t SortedVectorImpl::_indexOrderOf(const void* item, size_t* order) const
{
+ if (order) *order = 0;
+ if (isEmpty()) {
+ return NAME_NOT_FOUND;
+ }
// binary search
ssize_t err = NAME_NOT_FOUND;
ssize_t l = 0;
diff --git a/libutils/misc.cpp b/libutils/misc.cpp
index ed1ba23..216dc14 100644
--- a/libutils/misc.cpp
+++ b/libutils/misc.cpp
@@ -24,7 +24,6 @@
#include <sys/stat.h>
#include <string.h>
-#include <errno.h>
#include <stdio.h>
#if !defined(_WIN32)
diff --git a/libutils/tests/BitSet_test.cpp b/libutils/tests/BitSet_test.cpp
index 38b668a..59d913e 100644
--- a/libutils/tests/BitSet_test.cpp
+++ b/libutils/tests/BitSet_test.cpp
@@ -138,11 +138,11 @@
TEST_F(BitSet32Test, GetIndexOfBit) {
b1.markBit(1);
b1.markBit(4);
- EXPECT_EQ(b1.getIndexOfBit(1), 0);
- EXPECT_EQ(b1.getIndexOfBit(4), 1);
+ EXPECT_EQ(0U, b1.getIndexOfBit(1));
+ EXPECT_EQ(1U, b1.getIndexOfBit(4));
b1.markFirstUnmarkedBit();
- EXPECT_EQ(b1.getIndexOfBit(1), 1);
- EXPECT_EQ(b1.getIndexOfBit(4), 2);
+ EXPECT_EQ(1U, b1.getIndexOfBit(1));
+ EXPECT_EQ(2U, b1.getIndexOfBit(4));
}
class BitSet64Test : public testing::Test {
@@ -260,11 +260,11 @@
TEST_F(BitSet64Test, GetIndexOfBit) {
b1.markBit(10);
b1.markBit(40);
- EXPECT_EQ(b1.getIndexOfBit(10), 0);
- EXPECT_EQ(b1.getIndexOfBit(40), 1);
+ EXPECT_EQ(0U, b1.getIndexOfBit(10));
+ EXPECT_EQ(1U, b1.getIndexOfBit(40));
b1.markFirstUnmarkedBit();
- EXPECT_EQ(b1.getIndexOfBit(10), 1);
- EXPECT_EQ(b1.getIndexOfBit(40), 2);
+ EXPECT_EQ(1U, b1.getIndexOfBit(10));
+ EXPECT_EQ(2U, b1.getIndexOfBit(40));
}
} // namespace android
diff --git a/libutils/tests/LruCache_test.cpp b/libutils/tests/LruCache_test.cpp
index 6534211..6155def 100644
--- a/libutils/tests/LruCache_test.cpp
+++ b/libutils/tests/LruCache_test.cpp
@@ -220,7 +220,7 @@
cache.put(ComplexKey(0), ComplexValue(0));
cache.put(ComplexKey(1), ComplexValue(1));
- EXPECT_EQ(2, cache.size());
+ EXPECT_EQ(2U, cache.size());
assertInstanceCount(2, 3); // the null value counts as an instance
}
@@ -229,7 +229,7 @@
cache.put(ComplexKey(0), ComplexValue(0));
cache.put(ComplexKey(1), ComplexValue(1));
- EXPECT_EQ(2, cache.size());
+ EXPECT_EQ(2U, cache.size());
assertInstanceCount(2, 3);
cache.clear();
assertInstanceCount(0, 1);
@@ -241,7 +241,7 @@
cache.put(ComplexKey(0), ComplexValue(0));
cache.put(ComplexKey(1), ComplexValue(1));
- EXPECT_EQ(2, cache.size());
+ EXPECT_EQ(2U, cache.size());
assertInstanceCount(2, 3);
cache.removeOldest();
cache.clear();
@@ -255,13 +255,13 @@
cache.put(ComplexKey(0), ComplexValue(0));
cache.put(ComplexKey(1), ComplexValue(1));
- EXPECT_EQ(2, cache.size());
+ EXPECT_EQ(2U, cache.size());
assertInstanceCount(2, 3);
cache.clear();
assertInstanceCount(0, 1);
cache.put(ComplexKey(0), ComplexValue(0));
cache.put(ComplexKey(1), ComplexValue(1));
- EXPECT_EQ(2, cache.size());
+ EXPECT_EQ(2U, cache.size());
assertInstanceCount(2, 3);
}
@@ -273,7 +273,7 @@
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
- EXPECT_EQ(3, cache.size());
+ EXPECT_EQ(3U, cache.size());
cache.removeOldest();
EXPECT_EQ(1, callback.callbackCount);
EXPECT_EQ(1, callback.lastKey);
@@ -288,7 +288,7 @@
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
- EXPECT_EQ(3, cache.size());
+ EXPECT_EQ(3U, cache.size());
cache.clear();
EXPECT_EQ(3, callback.callbackCount);
}
diff --git a/libutils/tests/Vector_test.cpp b/libutils/tests/Vector_test.cpp
index d29c054..0ba7161 100644
--- a/libutils/tests/Vector_test.cpp
+++ b/libutils/tests/Vector_test.cpp
@@ -45,26 +45,26 @@
vector.add(2);
vector.add(3);
- EXPECT_EQ(vector.size(), 3);
+ EXPECT_EQ(3U, vector.size());
// copy the vector
other = vector;
- EXPECT_EQ(other.size(), 3);
+ EXPECT_EQ(3U, other.size());
// add an element to the first vector
vector.add(4);
// make sure the sizes are correct
- EXPECT_EQ(vector.size(), 4);
- EXPECT_EQ(other.size(), 3);
+ EXPECT_EQ(4U, vector.size());
+ EXPECT_EQ(3U, other.size());
// add an element to the copy
other.add(5);
// make sure the sizes are correct
- EXPECT_EQ(vector.size(), 4);
- EXPECT_EQ(other.size(), 4);
+ EXPECT_EQ(4U, vector.size());
+ EXPECT_EQ(4U, other.size());
// make sure the content of both vectors are correct
EXPECT_EQ(vector[3], 4);
diff --git a/libziparchive/Android.mk b/libziparchive/Android.mk
index a3087ee..559c48b 100644
--- a/libziparchive/Android.mk
+++ b/libziparchive/Android.mk
@@ -42,8 +42,8 @@
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := ${source_files}
-LOCAL_STATIC_LIBRARIES := libz libutils
-LOCAL_SHARED_LIBRARIES := liblog libbase
+LOCAL_STATIC_LIBRARIES := libutils
+LOCAL_SHARED_LIBRARIES := libz-host liblog libbase
LOCAL_MODULE:= libziparchive-host
LOCAL_CFLAGS := -Werror
LOCAL_MULTILIB := both
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 79c4c53..3716343 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -307,7 +307,7 @@
* ((4 * UINT16_MAX) / 3 + 1) which can safely fit into a uint32_t.
*/
uint32_t hash_table_size;
- ZipEntryName* hash_table;
+ ZipString* hash_table;
ZipArchive(const int fd, bool assume_ownership) :
fd(fd),
@@ -343,7 +343,7 @@
return val;
}
-static uint32_t ComputeHash(const ZipEntryName& name) {
+static uint32_t ComputeHash(const ZipString& name) {
uint32_t hash = 0;
uint16_t len = name.name_length;
const uint8_t* str = name.name;
@@ -359,16 +359,15 @@
* Convert a ZipEntry to a hash table index, verifying that it's in a
* valid range.
*/
-static int64_t EntryToIndex(const ZipEntryName* hash_table,
+static int64_t EntryToIndex(const ZipString* hash_table,
const uint32_t hash_table_size,
- const ZipEntryName& name) {
+ const ZipString& name) {
const uint32_t hash = ComputeHash(name);
// NOTE: (hash_table_size - 1) is guaranteed to be non-negative.
uint32_t ent = hash & (hash_table_size - 1);
while (hash_table[ent].name != NULL) {
- if (hash_table[ent].name_length == name.name_length &&
- memcmp(hash_table[ent].name, name.name, name.name_length) == 0) {
+ if (hash_table[ent] == name) {
return ent;
}
@@ -382,8 +381,8 @@
/*
* Add a new entry to the hash table.
*/
-static int32_t AddToHash(ZipEntryName *hash_table, const uint64_t hash_table_size,
- const ZipEntryName& name) {
+static int32_t AddToHash(ZipString *hash_table, const uint64_t hash_table_size,
+ const ZipString& name) {
const uint64_t hash = ComputeHash(name);
uint32_t ent = hash & (hash_table_size - 1);
@@ -392,8 +391,7 @@
* Further, we guarantee that the hashtable size is not 0.
*/
while (hash_table[ent].name != NULL) {
- if (hash_table[ent].name_length == name.name_length &&
- memcmp(hash_table[ent].name, name.name, name.name_length) == 0) {
+ if (hash_table[ent] == name) {
// We've found a duplicate entry. We don't accept it
ALOGW("Zip: Found duplicate entry %.*s", name.name_length, name.name);
return kDuplicateEntry;
@@ -473,7 +471,7 @@
return kEmptyArchive;
}
- ALOGV("+++ num_entries=%" PRIu32 "dir_size=%" PRIu32 " dir_offset=%" PRIu32,
+ ALOGV("+++ num_entries=%" PRIu32 " dir_size=%" PRIu32 " dir_offset=%" PRIu32,
eocd->num_records, eocd->cd_size, eocd->cd_start_offset);
/*
@@ -565,8 +563,8 @@
* least one unused entry to avoid an infinite loop during creation.
*/
archive->hash_table_size = RoundUpPower2(1 + (num_entries * 4) / 3);
- archive->hash_table = reinterpret_cast<ZipEntryName*>(calloc(archive->hash_table_size,
- sizeof(ZipEntryName)));
+ archive->hash_table = reinterpret_cast<ZipString*>(calloc(archive->hash_table_size,
+ sizeof(ZipString)));
/*
* Walk through the central directory, adding entries to the hash
@@ -605,7 +603,7 @@
}
/* add the CDE filename to the hash table */
- ZipEntryName entry_name;
+ ZipString entry_name;
entry_name.name = file_name;
entry_name.name_length = file_name_length;
const int add_result = AddToHash(archive->hash_table,
@@ -851,26 +849,41 @@
uint32_t position;
// We're not using vector here because this code is used in the Windows SDK
// where the STL is not available.
- const uint8_t* prefix;
- uint16_t prefix_len;
+ ZipString prefix;
+ ZipString suffix;
ZipArchive* archive;
- IterationHandle() : prefix(NULL), prefix_len(0) {}
-
- IterationHandle(const ZipEntryName& prefix_name)
- : prefix_len(prefix_name.name_length) {
- uint8_t* prefix_copy = new uint8_t[prefix_len];
- memcpy(prefix_copy, prefix_name.name, prefix_len);
- prefix = prefix_copy;
+ IterationHandle(const ZipString* in_prefix,
+ const ZipString* in_suffix) {
+ if (in_prefix) {
+ uint8_t* name_copy = new uint8_t[in_prefix->name_length];
+ memcpy(name_copy, in_prefix->name, in_prefix->name_length);
+ prefix.name = name_copy;
+ prefix.name_length = in_prefix->name_length;
+ } else {
+ prefix.name = NULL;
+ prefix.name_length = 0;
+ }
+ if (in_suffix) {
+ uint8_t* name_copy = new uint8_t[in_suffix->name_length];
+ memcpy(name_copy, in_suffix->name, in_suffix->name_length);
+ suffix.name = name_copy;
+ suffix.name_length = in_suffix->name_length;
+ } else {
+ suffix.name = NULL;
+ suffix.name_length = 0;
+ }
}
~IterationHandle() {
- delete[] prefix;
+ delete[] prefix.name;
+ delete[] suffix.name;
}
};
int32_t StartIteration(ZipArchiveHandle handle, void** cookie_ptr,
- const ZipEntryName* optional_prefix) {
+ const ZipString* optional_prefix,
+ const ZipString* optional_suffix) {
ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle);
if (archive == NULL || archive->hash_table == NULL) {
@@ -878,8 +891,7 @@
return kInvalidHandle;
}
- IterationHandle* cookie =
- optional_prefix != NULL ? new IterationHandle(*optional_prefix) : new IterationHandle();
+ IterationHandle* cookie = new IterationHandle(optional_prefix, optional_suffix);
cookie->position = 0;
cookie->archive = archive;
@@ -891,7 +903,7 @@
delete reinterpret_cast<IterationHandle*>(cookie);
}
-int32_t FindEntry(const ZipArchiveHandle handle, const ZipEntryName& entryName,
+int32_t FindEntry(const ZipArchiveHandle handle, const ZipString& entryName,
ZipEntry* data) {
const ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle);
if (entryName.name_length == 0) {
@@ -910,7 +922,7 @@
return FindEntry(archive, ent, data);
}
-int32_t Next(void* cookie, ZipEntry* data, ZipEntryName* name) {
+int32_t Next(void* cookie, ZipEntry* data, ZipString* name) {
IterationHandle* handle = reinterpret_cast<IterationHandle*>(cookie);
if (handle == NULL) {
return kInvalidHandle;
@@ -924,12 +936,14 @@
const uint32_t currentOffset = handle->position;
const uint32_t hash_table_length = archive->hash_table_size;
- const ZipEntryName *hash_table = archive->hash_table;
+ const ZipString* hash_table = archive->hash_table;
for (uint32_t i = currentOffset; i < hash_table_length; ++i) {
if (hash_table[i].name != NULL &&
- (handle->prefix_len == 0 ||
- (memcmp(handle->prefix, hash_table[i].name, handle->prefix_len) == 0))) {
+ (handle->prefix.name_length == 0 ||
+ hash_table[i].StartsWith(handle->prefix)) &&
+ (handle->suffix.name_length == 0 ||
+ hash_table[i].EndsWith(handle->suffix))) {
handle->position = (i + 1);
const int error = FindEntry(archive, i, data);
if (!error) {
@@ -1008,8 +1022,13 @@
// entry. Note that the call to ftruncate below will change the file size but
// will not allocate space on disk and this call to fallocate will not
// change the file size.
+ // Note: fallocate is only supported by the following filesystems -
+ // btrfs, ext4, ocfs2, and xfs. Therefore fallocate might fail with
+ // EOPNOTSUPP error when issued in other filesystems.
+ // Hence, check for the return error code before concluding that the
+ // disk does not have enough space.
result = TEMP_FAILURE_RETRY(fallocate(fd, 0, current_offset, declared_length));
- if (result == -1) {
+ if (result == -1 && errno == ENOSPC) {
ALOGW("Zip: unable to allocate space for file to %" PRId64 ": %s",
static_cast<int64_t>(declared_length + current_offset), strerror(errno));
return std::unique_ptr<FileWriter>(nullptr);
@@ -1260,4 +1279,3 @@
int GetFileDescriptor(const ZipArchiveHandle handle) {
return reinterpret_cast<ZipArchive*>(handle)->fd;
}
-
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index f8952ce..9a3cdb4 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -70,7 +70,7 @@
}
static void AssertNameEquals(const std::string& name_str,
- const ZipEntryName& name) {
+ const ZipString& name) {
ASSERT_EQ(name_str.size(), name.name_length);
ASSERT_EQ(0, memcmp(name_str.c_str(), name.name, name.name_length));
}
@@ -115,10 +115,10 @@
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
void* iteration_cookie;
- ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL));
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, NULL));
ZipEntry data;
- ZipEntryName name;
+ ZipString name;
// b/c.txt
ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
@@ -146,12 +146,122 @@
CloseArchive(handle);
}
+TEST(ziparchive, IterationWithPrefix) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
+
+ void* iteration_cookie;
+ ZipString prefix("b/");
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, &prefix, NULL));
+
+ ZipEntry data;
+ ZipString name;
+
+ // b/c.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b/c.txt", name);
+
+ // b/d.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b/d.txt", name);
+
+ // b/
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b/", name);
+
+ // End of iteration.
+ ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
+
+ CloseArchive(handle);
+}
+
+TEST(ziparchive, IterationWithSuffix) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
+
+ void* iteration_cookie;
+ ZipString suffix(".txt");
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, &suffix));
+
+ ZipEntry data;
+ ZipString name;
+
+ // b/c.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b/c.txt", name);
+
+ // b/d.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b/d.txt", name);
+
+ // a.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("a.txt", name);
+
+ // b.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b.txt", name);
+
+ // End of iteration.
+ ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
+
+ CloseArchive(handle);
+}
+
+TEST(ziparchive, IterationWithPrefixAndSuffix) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
+
+ void* iteration_cookie;
+ ZipString prefix("b");
+ ZipString suffix(".txt");
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, &prefix, &suffix));
+
+ ZipEntry data;
+ ZipString name;
+
+ // b/c.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b/c.txt", name);
+
+ // b/d.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b/d.txt", name);
+
+ // b.txt
+ ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+ AssertNameEquals("b.txt", name);
+
+ // End of iteration.
+ ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
+
+ CloseArchive(handle);
+}
+
+TEST(ziparchive, IterationWithBadPrefixAndSuffix) {
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
+
+ void* iteration_cookie;
+ ZipString prefix("x");
+ ZipString suffix("y");
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, &prefix, &suffix));
+
+ ZipEntry data;
+ ZipString name;
+
+ // End of iteration.
+ ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
+
+ CloseArchive(handle);
+}
+
TEST(ziparchive, FindEntry) {
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
ZipEntry data;
- ZipEntryName name;
+ ZipString name;
name.name = kATxtName;
name.name_length = kATxtNameLength;
ASSERT_EQ(0, FindEntry(handle, name, &data));
@@ -164,7 +274,7 @@
ASSERT_EQ(0x950821c5, data.crc32);
// An entry that doesn't exist. Should be a negative return code.
- ZipEntryName absent_name;
+ ZipString absent_name;
absent_name.name = kNonexistentTxtName;
absent_name.name_length = kNonexistentTxtNameLength;
ASSERT_LT(FindEntry(handle, absent_name, &data), 0);
@@ -177,9 +287,9 @@
ASSERT_EQ(0, OpenArchiveWrapper("declaredlength.zip", &handle));
void* iteration_cookie;
- ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL));
+ ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, NULL));
- ZipEntryName name;
+ ZipString name;
ZipEntry data;
ASSERT_EQ(Next(iteration_cookie, &data, &name), 0);
@@ -194,7 +304,7 @@
// An entry that's deflated.
ZipEntry data;
- ZipEntryName a_name;
+ ZipString a_name;
a_name.name = kATxtName;
a_name.name_length = kATxtNameLength;
ASSERT_EQ(0, FindEntry(handle, a_name, &data));
@@ -206,7 +316,7 @@
delete[] buffer;
// An entry that's stored.
- ZipEntryName b_name;
+ ZipString b_name;
b_name.name = kBTxtName;
b_name.name_length = kBTxtNameLength;
ASSERT_EQ(0, FindEntry(handle, b_name, &data));
@@ -293,7 +403,7 @@
ASSERT_EQ(0, OpenArchiveFd(fd, "EmptyEntriesTest", &handle));
ZipEntry entry;
- ZipEntryName empty_name;
+ ZipString empty_name;
empty_name.name = kEmptyTxtName;
empty_name.name_length = kEmptyTxtNameLength;
ASSERT_EQ(0, FindEntry(handle, empty_name, &entry));
@@ -324,7 +434,7 @@
ASSERT_EQ(0, OpenArchiveFd(fd, "EntryLargerThan32KTest", &handle));
ZipEntry entry;
- ZipEntryName ab_name;
+ ZipString ab_name;
ab_name.name = kAbTxtName;
ab_name.name_length = kAbTxtNameLength;
ASSERT_EQ(0, FindEntry(handle, ab_name, &entry));
@@ -392,7 +502,7 @@
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
ZipEntry entry;
- ZipEntryName name;
+ ZipString name;
name.name = kATxtName;
name.name_length = kATxtNameLength;
ASSERT_EQ(0, FindEntry(handle, name, &entry));
diff --git a/lmkd/Android.mk b/lmkd/Android.mk
index 39081d6..8c88661 100644
--- a/lmkd/Android.mk
+++ b/lmkd/Android.mk
@@ -7,4 +7,6 @@
LOCAL_MODULE := lmkd
+LOCAL_INIT_RC := lmkd.rc
+
include $(BUILD_EXECUTABLE)
diff --git a/lmkd/lmkd.rc b/lmkd/lmkd.rc
new file mode 100644
index 0000000..83c5ff0
--- /dev/null
+++ b/lmkd/lmkd.rc
@@ -0,0 +1,4 @@
+service lmkd /system/bin/lmkd
+ class core
+ critical
+ socket lmkd seqpacket 0660 system system
diff --git a/logcat/Android.mk b/logcat/Android.mk
index f46a4de..844ab8b 100644
--- a/logcat/Android.mk
+++ b/logcat/Android.mk
@@ -5,12 +5,14 @@
LOCAL_SRC_FILES:= logcat.cpp event.logtags
-LOCAL_SHARED_LIBRARIES := liblog
+LOCAL_SHARED_LIBRARIES := liblog libbase libcutils
LOCAL_MODULE := logcat
LOCAL_CFLAGS := -Werror
+LOCAL_INIT_RC := logcatd.rc
+
include $(BUILD_EXECUTABLE)
include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 2c2d785..e598bb8 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -1,29 +1,40 @@
// Copyright 2006-2015 The Android Open Source Project
+#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
-#include <stdarg.h>
#include <string.h>
-#include <signal.h>
-#include <time.h>
-#include <unistd.h>
#include <sys/cdefs.h>
+#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
-#include <arpa/inet.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <memory>
+#include <string>
+
+#include <base/file.h>
+#include <base/strings.h>
+#include <cutils/sched_policy.h>
#include <cutils/sockets.h>
+#include <log/event_tag_map.h>
#include <log/log.h>
#include <log/log_read.h>
-#include <log/logger.h>
#include <log/logd.h>
+#include <log/logger.h>
#include <log/logprint.h>
-#include <log/event_tag_map.h>
+#include <utils/threads.h>
#define DEFAULT_MAX_ROTATED_LOGS 4
@@ -202,7 +213,19 @@
g_outFD = STDOUT_FILENO;
} else {
- struct stat statbuf;
+ if (set_sched_policy(0, SP_BACKGROUND) < 0) {
+ fprintf(stderr, "failed to set background scheduling policy\n");
+ }
+
+ struct sched_param param;
+ memset(¶m, 0, sizeof(param));
+ if (sched_setscheduler((pid_t) 0, SCHED_BATCH, ¶m) < 0) {
+ fprintf(stderr, "failed to set to batch scheduler\n");
+ }
+
+ if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
+ fprintf(stderr, "failed set to priority\n");
+ }
g_outFD = openLogFile (g_outputFileName);
@@ -210,6 +233,7 @@
logcat_panic(false, "couldn't open output file");
}
+ struct stat statbuf;
if (fstat(g_outFD, &statbuf) == -1) {
close(g_outFD);
logcat_panic(false, "couldn't get output file stat\n");
@@ -231,11 +255,12 @@
fprintf(stderr, "options include:\n"
" -s Set default filter to silent.\n"
" Like specifying filterspec '*:S'\n"
- " -f <filename> Log to file. Default to stdout\n"
+ " -f <filename> Log to file. Default is stdout\n"
" -r <kbytes> Rotate log every kbytes. Requires -f\n"
" -n <count> Sets max number of rotated logs to <count>, default 4\n"
" -v <format> Sets the log print format, where <format> is:\n\n"
- " brief color long process raw tag thread threadtime time usec\n\n"
+ " brief color long printable process raw tag thread\n"
+ " threadtime time usec\n\n"
" -D print dividers between each log buffer\n"
" -c clear (flush) the entire log and exit\n"
" -d dump the log and then exit (don't block)\n"
@@ -352,6 +377,86 @@
exit(EXIT_FAILURE);
}
+static const char g_defaultTimeFormat[] = "%m-%d %H:%M:%S.%q";
+
+// Find last logged line in gestalt of all matching existing output files
+static log_time lastLogTime(char *outputFileName) {
+ log_time retval(log_time::EPOCH);
+ if (!outputFileName) {
+ return retval;
+ }
+
+ log_time now(CLOCK_REALTIME);
+
+ std::string directory;
+ char *file = strrchr(outputFileName, '/');
+ if (!file) {
+ directory = ".";
+ file = outputFileName;
+ } else {
+ *file = '\0';
+ directory = outputFileName;
+ *file = '/';
+ ++file;
+ }
+ size_t len = strlen(file);
+ log_time modulo(0, NS_PER_SEC);
+ std::unique_ptr<DIR, int(*)(DIR*)>dir(opendir(directory.c_str()), closedir);
+ struct dirent *dp;
+ while ((dp = readdir(dir.get())) != NULL) {
+ if ((dp->d_type != DT_REG)
+ || strncmp(dp->d_name, file, len)
+ || (dp->d_name[len]
+ && ((dp->d_name[len] != '.')
+ || !isdigit(dp->d_name[len+1])))) {
+ continue;
+ }
+
+ std::string file_name = directory;
+ file_name += "/";
+ file_name += dp->d_name;
+ std::string file;
+ if (!android::base::ReadFileToString(file_name, &file)) {
+ continue;
+ }
+
+ bool found = false;
+ for (const auto& line : android::base::Split(file, "\n")) {
+ log_time t(log_time::EPOCH);
+ char *ep = t.strptime(line.c_str(), g_defaultTimeFormat);
+ if (!ep || (*ep != ' ')) {
+ continue;
+ }
+ // determine the time precision of the logs (eg: msec or usec)
+ for (unsigned long mod = 1UL; mod < modulo.tv_nsec; mod *= 10) {
+ if (t.tv_nsec % (mod * 10)) {
+ modulo.tv_nsec = mod;
+ break;
+ }
+ }
+ // We filter any times later than current as we may not have the
+ // year stored with each log entry. Also, since it is possible for
+ // entries to be recorded out of order (very rare) we select the
+ // maximum we find just in case.
+ if ((t < now) && (t > retval)) {
+ retval = t;
+ found = true;
+ }
+ }
+ // We count on the basename file to be the definitive end, so stop here.
+ if (!dp->d_name[len] && found) {
+ break;
+ }
+ }
+ if (retval == log_time::EPOCH) {
+ return retval;
+ }
+ // tail_time prints matching or higher, round up by the modulo to prevent
+ // a replay of the last entry we have just checked.
+ retval += modulo;
+ return retval;
+}
+
} /* namespace android */
@@ -417,12 +522,11 @@
/* FALLTHRU */
case 'T':
if (strspn(optarg, "0123456789") != strlen(optarg)) {
- char *cp = tail_time.strptime(optarg,
- log_time::default_format);
+ char *cp = tail_time.strptime(optarg, g_defaultTimeFormat);
if (!cp) {
logcat_panic(false,
"-%c \"%s\" not in \"%s\" time format\n",
- ret, optarg, log_time::default_format);
+ ret, optarg, g_defaultTimeFormat);
}
if (*cp) {
char c = *cp;
@@ -545,9 +649,11 @@
break;
case 'f':
+ if ((tail_time == log_time::EPOCH) && (tail_lines != 0)) {
+ tail_time = lastLogTime(optarg);
+ }
// redirect output to a file
g_outputFileName = optarg;
-
break;
case 'r':
diff --git a/logcat/logcatd.rc b/logcat/logcatd.rc
new file mode 100644
index 0000000..0bc581e
--- /dev/null
+++ b/logcat/logcatd.rc
@@ -0,0 +1,13 @@
+on property:persist.logd.logpersistd=logcatd
+ # all exec/services are called with umask(077), so no gain beyond 0700
+ mkdir /data/misc/logd 0700 logd log
+ # logd for write to /data/misc/logd, log group for read from pstore (-L)
+ exec - logd log -- /system/bin/logcat -L -b all -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 64 -n 256
+ start logcatd
+
+service logcatd /system/bin/logcat -b all -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r 64 -n 256
+ class late_start
+ disabled
+ # logd for write to /data/misc/logd, log group for read from log daemon
+ user logd
+ group log
diff --git a/logd/Android.mk b/logd/Android.mk
index 73da8dc..01c51c7 100644
--- a/logd/Android.mk
+++ b/logd/Android.mk
@@ -4,6 +4,8 @@
LOCAL_MODULE:= logd
+LOCAL_INIT_RC := logd.rc
+
LOCAL_SRC_FILES := \
main.cpp \
LogCommand.cpp \
@@ -25,7 +27,7 @@
libsysutils \
liblog \
libcutils \
- libutils
+ libbase
# This is what we want to do:
# event_logtags = $(shell \
@@ -41,4 +43,15 @@
include $(BUILD_EXECUTABLE)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := logpersist.start
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(bin_dir)
+LOCAL_SRC_FILES := logpersist
+ALL_TOOLS := logpersist.start logpersist.stop logpersist.cat
+LOCAL_POST_INSTALL_CMD := $(hide) $(foreach t,$(filter-out $(LOCAL_MODULE),$(ALL_TOOLS)),ln -sf $(LOCAL_MODULE) $(TARGET_OUT)/bin/$(t);)
+include $(BUILD_PREBUILT)
+
include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/logd/CommandListener.cpp b/logd/CommandListener.cpp
index 5489cc9..031c740 100644
--- a/logd/CommandListener.cpp
+++ b/logd/CommandListener.cpp
@@ -25,6 +25,9 @@
#include <sys/socket.h>
#include <sys/types.h>
+#include <string>
+
+#include <base/stringprintf.h>
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>
#include <sysutils/SocketClient.h>
@@ -34,8 +37,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 +49,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) {
}
@@ -191,22 +192,13 @@
mBuf(*buf) {
}
-static void package_string(char **strp) {
- const char *a = *strp;
- if (!a) {
- a = "";
- }
-
+static std::string package_string(const std::string &str) {
// Calculate total buffer size prefix, count is the string length w/o nul
char fmt[32];
- for(size_t l = strlen(a), y = 0, x = 6; y != x; y = x, x = strlen(fmt) - 2) {
+ for(size_t l = str.length(), y = 0, x = 6; y != x; y = x, x = strlen(fmt) - 2) {
snprintf(fmt, sizeof(fmt), "%zu\n%%s\n\f", l + x);
}
-
- char *b = *strp;
- *strp = NULL;
- asprintf(strp, fmt, a);
- free(b);
+ return android::base::StringPrintf(fmt, str.c_str());
}
int CommandListener::GetStatisticsCmd::runCommand(SocketClient *cli,
@@ -230,16 +222,7 @@
}
}
- char *buf = NULL;
-
- mBuf.formatStatistics(&buf, uid, logMask);
- if (!buf) {
- cli->sendMsg("Failed");
- } else {
- package_string(&buf);
- cli->sendMsg(buf);
- free(buf);
- }
+ cli->sendMsg(package_string(mBuf.formatStatistics(uid, logMask)).c_str());
return 0;
}
@@ -251,15 +234,7 @@
int CommandListener::GetPruneListCmd::runCommand(SocketClient *cli,
int /*argc*/, char ** /*argv*/) {
setname();
- char *buf = NULL;
- mBuf.formatPrune(&buf);
- if (!buf) {
- cli->sendMsg("Failed");
- } else {
- package_string(&buf);
- cli->sendMsg(buf);
- free(buf);
- }
+ cli->sendMsg(package_string(mBuf.formatPrune()).c_str());
return 0;
}
@@ -276,20 +251,15 @@
return 0;
}
- char *cp = NULL;
+ std::string str;
for (int i = 1; i < argc; ++i) {
- char *p = cp;
- if (p) {
- cp = NULL;
- asprintf(&cp, "%s %s", p, argv[i]);
- free(p);
- } else {
- asprintf(&cp, "%s", argv[i]);
+ if (str.length()) {
+ str += " ";
}
+ str += argv[i];
}
- int ret = mBuf.initPrune(cp);
- free(cp);
+ int ret = mBuf.initPrune(str.c_str());
if (ret) {
cli->sendMsg("Invalid");
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/FlushCommand.cpp b/logd/FlushCommand.cpp
index d584925..823a842 100644
--- a/logd/FlushCommand.cpp
+++ b/logd/FlushCommand.cpp
@@ -72,7 +72,7 @@
return;
}
entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid, mStart);
- times.push_back(entry);
+ times.push_front(entry);
}
client->incRef();
diff --git a/logd/LogAudit.cpp b/logd/LogAudit.cpp
index 4ec2e59..4b3547c 100644
--- a/logd/LogAudit.cpp
+++ b/logd/LogAudit.cpp
@@ -145,7 +145,9 @@
++cp;
}
tid = pid;
+ logbuf->lock();
uid = logbuf->pidToUid(pid);
+ logbuf->unlock();
memmove(pidptr, cp, strlen(cp) + 1);
}
@@ -180,14 +182,20 @@
static const char comm_str[] = " comm=\"";
const char *comm = strstr(str, comm_str);
const char *estr = str + strlen(str);
+ char *commfree = NULL;
if (comm) {
estr = comm;
comm += sizeof(comm_str) - 1;
} else if (pid == getpid()) {
pid = tid;
comm = "auditd";
- } else if (!(comm = logbuf->pidToName(pid))) {
- comm = "unknown";
+ } else {
+ logbuf->lock();
+ comm = commfree = logbuf->pidToName(pid);
+ logbuf->unlock();
+ if (!comm) {
+ comm = "unknown";
+ }
}
const char *ecomm = strchr(comm, '"');
@@ -218,6 +226,7 @@
}
}
+ free(commfree);
free(str);
if (notify) {
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index 1da52d8..6ea4109 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -22,6 +22,8 @@
#include <time.h>
#include <unistd.h>
+#include <unordered_map>
+
#include <cutils/properties.h>
#include <log/logger.h>
@@ -138,8 +140,26 @@
if ((log_id >= LOG_ID_MAX) || (log_id < 0)) {
return -EINVAL;
}
+
LogBufferElement *elem = new LogBufferElement(log_id, realtime,
uid, pid, tid, msg, len);
+ int prio = ANDROID_LOG_INFO;
+ const char *tag = NULL;
+ if (log_id == LOG_ID_EVENTS) {
+ tag = android::tagToName(elem->getTag());
+ } else {
+ prio = *msg;
+ tag = msg + 1;
+ }
+ if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
+ // Log traffic received to total
+ pthread_mutex_lock(&mLogElementsLock);
+ stats.add(elem);
+ stats.subtract(elem);
+ pthread_mutex_unlock(&mLogElementsLock);
+ delete elem;
+ return -EACCES;
+ }
pthread_mutex_lock(&mLogElementsLock);
@@ -197,19 +217,22 @@
return len;
}
-// If we're using more than 256K of memory for log entries, prune
-// at least 10% of the log entries.
+// Prune at most 10% of the log entries or 256, whichever is less.
//
// mLogElementsLock must be held when this function is called.
void LogBuffer::maybePrune(log_id_t id) {
size_t sizes = stats.sizes(id);
- if (sizes > log_buffer_size(id)) {
- size_t sizeOver90Percent = sizes - ((log_buffer_size(id) * 9) / 10);
+ unsigned long maxSize = log_buffer_size(id);
+ if (sizes > maxSize) {
+ size_t sizeOver = sizes - ((maxSize * 9) / 10);
size_t elements = stats.elements(id);
- unsigned long pruneRows = elements * sizeOver90Percent / sizes;
- elements /= 10;
- if (pruneRows <= elements) {
- pruneRows = elements;
+ size_t minElements = elements / 10;
+ unsigned long pruneRows = elements * sizeOver / sizes;
+ if (pruneRows <= minElements) {
+ pruneRows = minElements;
+ }
+ if (pruneRows > 256) {
+ pruneRows = 256;
}
prune(id, pruneRows);
}
@@ -217,7 +240,12 @@
LogBufferElementCollection::iterator LogBuffer::erase(LogBufferElementCollection::iterator it) {
LogBufferElement *e = *it;
+ log_id_t id = e->getLogId();
+ LogBufferIteratorMap::iterator f = mLastWorstUid[id].find(e->getUid());
+ if ((f != mLastWorstUid[id].end()) && (it == f->second)) {
+ mLastWorstUid[id].erase(f);
+ }
it = mLogElements.erase(it);
stats.subtract(e);
delete e;
@@ -246,31 +274,21 @@
uint64_t getKey() { return value; }
};
-class LogBufferElementEntry {
- const uint64_t key;
- LogBufferElement *last;
+class LogBufferElementLast {
+
+ typedef std::unordered_map<uint64_t, LogBufferElement *> LogBufferElementMap;
+ LogBufferElementMap map;
public:
- LogBufferElementEntry(const uint64_t &k, LogBufferElement *e):key(k),last(e) { }
- const uint64_t&getKey() const { return key; }
-
- LogBufferElement *getLast() { return last; }
-};
-
-class LogBufferElementLast : public android::BasicHashtable<uint64_t, LogBufferElementEntry> {
-
-public:
bool merge(LogBufferElement *e, unsigned short dropped) {
LogBufferElementKey key(e->getUid(), e->getPid(), e->getTid());
- android::hash_t hash = android::hash_type(key.getKey());
- ssize_t index = find(-1, hash, key.getKey());
- if (index != -1) {
- LogBufferElementEntry &entry = editEntryAt(index);
- LogBufferElement *l = entry.getLast();
+ LogBufferElementMap::iterator it = map.find(key.getKey());
+ if (it != map.end()) {
+ LogBufferElement *l = it->second;
unsigned short d = l->getDropped();
if ((dropped + d) > USHRT_MAX) {
- removeAt(index);
+ map.erase(it);
} else {
l->setDropped(dropped + d);
return true;
@@ -279,26 +297,25 @@
return false;
}
- size_t add(LogBufferElement *e) {
+ void add(LogBufferElement *e) {
LogBufferElementKey key(e->getUid(), e->getPid(), e->getTid());
- android::hash_t hash = android::hash_type(key.getKey());
- return android::BasicHashtable<uint64_t, LogBufferElementEntry>::
- add(hash, LogBufferElementEntry(key.getKey(), e));
+ map[key.getKey()] = e;
}
inline void clear() {
- android::BasicHashtable<uint64_t, LogBufferElementEntry>::clear();
+ map.clear();
}
void clear(LogBufferElement *e) {
- uint64_t current = e->getRealTime().nsec() - NS_PER_SEC;
- ssize_t index = -1;
- while((index = next(index)) >= 0) {
- LogBufferElement *l = editEntryAt(index).getLast();
+ uint64_t current = e->getRealTime().nsec()
+ - (EXPIRE_RATELIMIT * NS_PER_SEC);
+ for(LogBufferElementMap::iterator it = map.begin(); it != map.end();) {
+ LogBufferElement *l = it->second;
if ((l->getDropped() >= EXPIRE_THRESHOLD)
&& (current > l->getRealTime().nsec())) {
- removeAt(index);
- index = -1;
+ it = map.erase(it);
+ } else {
+ ++it;
}
}
}
@@ -387,8 +404,17 @@
bool kick = false;
bool leading = true;
+ it = mLogElements.begin();
+ if (worst != (uid_t) -1) {
+ LogBufferIteratorMap::iterator f = mLastWorstUid[id].find(worst);
+ if ((f != mLastWorstUid[id].end())
+ && (f->second != mLogElements.end())) {
+ leading = false;
+ it = f->second;
+ }
+ }
LogBufferElementLast last;
- for(it = mLogElements.begin(); it != mLogElements.end();) {
+ while (it != mLogElements.end()) {
LogBufferElement *e = *it;
if (oldest && (oldest->mStart <= e->getSequence())) {
@@ -438,8 +464,10 @@
continue;
}
+ // unmerged drop message
if (dropped) {
last.add(e);
+ mLastWorstUid[id][e->getUid()] = it;
++it;
continue;
}
@@ -484,6 +512,7 @@
delete e;
} else {
last.add(e);
+ mLastWorstUid[id][e->getUid()] = it;
++it;
}
}
@@ -661,10 +690,12 @@
return max;
}
-void LogBuffer::formatStatistics(char **strp, uid_t uid, unsigned int logMask) {
+std::string LogBuffer::formatStatistics(uid_t uid, unsigned int logMask) {
pthread_mutex_lock(&mLogElementsLock);
- stats.format(strp, uid, logMask);
+ std::string ret = stats.format(uid, logMask);
pthread_mutex_unlock(&mLogElementsLock);
+
+ return ret;
}
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index 00b19b6..fcb05f5 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -19,9 +19,11 @@
#include <sys/types.h>
+#include <list>
+#include <string>
+
#include <log/log.h>
#include <sysutils/SocketClient.h>
-#include <utils/List.h>
#include <private/android_filesystem_config.h>
@@ -30,7 +32,7 @@
#include "LogStatistics.h"
#include "LogWhiteBlackList.h"
-typedef android::List<LogBufferElement *> LogBufferElementCollection;
+typedef std::list<LogBufferElement *> LogBufferElementCollection;
class LogBuffer {
LogBufferElementCollection mLogElements;
@@ -39,6 +41,11 @@
LogStatistics stats;
PruneList mPrune;
+ // watermark of any worst/chatty uid processing
+ typedef std::unordered_map<uid_t,
+ LogBufferElementCollection::iterator>
+ LogBufferIteratorMap;
+ LogBufferIteratorMap mLastWorstUid[LOG_ID_MAX];
unsigned long mMaxSize[LOG_ID_MAX];
@@ -61,20 +68,21 @@
int setSize(log_id_t id, unsigned long size);
unsigned long getSizeUsed(log_id_t id);
// *strp uses malloc, use free to release.
- void formatStatistics(char **strp, uid_t uid, unsigned int logMask);
+ std::string formatStatistics(uid_t uid, unsigned int logMask);
void enableStatistics() {
stats.enableStatistics();
}
- int initPrune(char *cp) { return mPrune.init(cp); }
- // *strp uses malloc, use free to release.
- void formatPrune(char **strp) { mPrune.format(strp); }
+ int initPrune(const char *cp) { return mPrune.init(cp); }
+ std::string formatPrune() { return mPrune.format(); }
- // helper
+ // helper must be protected directly or implicitly by lock()/unlock()
char *pidToName(pid_t pid) { return stats.pidToName(pid); }
uid_t pidToUid(pid_t pid) { return stats.pidToUid(pid); }
char *uidToName(uid_t uid) { return stats.uidToName(uid); }
+ void lock() { pthread_mutex_lock(&mLogElementsLock); }
+ void unlock() { pthread_mutex_unlock(&mLogElementsLock); }
private:
void maybePrune(log_id_t id);
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index 3d7237e..9fb1439 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -30,7 +30,7 @@
#include "LogReader.h"
const uint64_t LogBufferElement::FLUSH_ERROR(0);
-atomic_int_fast64_t LogBufferElement::sequence;
+atomic_int_fast64_t LogBufferElement::sequence(1);
LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime,
uid_t uid, pid_t pid, pid_t tid,
@@ -104,20 +104,34 @@
// assumption: mMsg == NULL
size_t LogBufferElement::populateDroppedMessage(char *&buffer,
LogBuffer *parent) {
- static const char tag[] = "logd";
- static const char format_uid[] = "uid=%u%s too chatty%s, expire %u line%s";
+ static const char tag[] = "chatty";
+ if (!__android_log_is_loggable(ANDROID_LOG_INFO, tag, ANDROID_LOG_VERBOSE)) {
+ return 0;
+ }
+
+ static const char format_uid[] = "uid=%u%s%s expire %u line%s";
+ parent->lock();
char *name = parent->uidToName(mUid);
+ parent->unlock();
char *commName = android::tidToName(mTid);
if (!commName && (mTid != mPid)) {
commName = android::tidToName(mPid);
}
if (!commName) {
+ parent->lock();
commName = parent->pidToName(mPid);
+ parent->unlock();
}
- if (name && commName && !strcmp(name, commName)) {
- free(commName);
- commName = NULL;
+ size_t len = name ? strlen(name) : 0;
+ if (len && commName && !strncmp(name, commName, len)) {
+ if (commName[len] == '\0') {
+ free(commName);
+ commName = NULL;
+ } else {
+ free(name);
+ name = NULL;
+ }
}
if (name) {
char *p = NULL;
@@ -129,16 +143,16 @@
}
if (commName) {
char *p = NULL;
- asprintf(&p, " comm=%s", commName);
+ asprintf(&p, " %s", commName);
if (p) {
free(commName);
commName = p;
}
}
// identical to below to calculate the buffer size required
- size_t len = snprintf(NULL, 0, format_uid, mUid, name ? name : "",
- commName ? commName : "",
- mDropped, (mDropped > 1) ? "s" : "");
+ len = snprintf(NULL, 0, format_uid, mUid, name ? name : "",
+ commName ? commName : "",
+ mDropped, (mDropped > 1) ? "s" : "");
size_t hdrLen;
if (mLogId == LOG_ID_EVENTS) {
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index 3dcf9d1..ca2c3a6 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -50,8 +50,9 @@
#define EXPIRE_HOUR_THRESHOLD 24 // Only expire chatty UID logs to preserve
// non-chatty UIDs less than this age in hours
-#define EXPIRE_THRESHOLD 4 // A smaller expire count is considered too
+#define EXPIRE_THRESHOLD 10 // A smaller expire count is considered too
// chatty for the temporal expire messages
+#define EXPIRE_RATELIMIT 10 // maximum rate in seconds to report expiration
class LogBufferElement {
const log_id_t mLogId;
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index 8df0d0a..1e6f55f 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -36,6 +36,140 @@
static const char priority_message[] = { KMSG_PRIORITY(LOG_INFO), '\0' };
+// Parsing is hard
+
+// called if we see a '<', s is the next character, returns pointer after '>'
+static char *is_prio(char *s) {
+ if (!isdigit(*s++)) {
+ return NULL;
+ }
+ static const size_t max_prio_len = 4;
+ size_t len = 0;
+ char c;
+ while (((c = *s++)) && (++len <= max_prio_len)) {
+ if (!isdigit(c)) {
+ return (c == '>') ? s : NULL;
+ }
+ }
+ return NULL;
+}
+
+// called if we see a '[', s is the next character, returns pointer after ']'
+static char *is_timestamp(char *s) {
+ while (*s == ' ') {
+ ++s;
+ }
+ if (!isdigit(*s++)) {
+ return NULL;
+ }
+ bool first_period = true;
+ char c;
+ while ((c = *s++)) {
+ if ((c == '.') && first_period) {
+ first_period = false;
+ } else if (!isdigit(c)) {
+ return ((c == ']') && !first_period && (*s == ' ')) ? s : NULL;
+ }
+ }
+ return NULL;
+}
+
+// Like strtok_r with "\r\n" except that we look for log signatures (regex)
+// \(\(<[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
+// <digit> following ('0' to '9' masked with ~SIGNATURE_MASK) added to signature
+#define LESS_THAN_SIG SIGNATURE_MASK
+#define OPEN_BRACKET_SIG ((SIGNATURE_MASK << 1) & SIGNATURE_MASK)
+// space is one more than <digit> of 9
+#define OPEN_BRACKET_SPACE ((char)(OPEN_BRACKET_SIG | 10))
+
+char *log_strtok_r(char *s, char **last) {
+ if (!s) {
+ if (!(s = *last)) {
+ return NULL;
+ }
+ // fixup for log signature split <,
+ // LESS_THAN_SIG + <digit>
+ if ((*s & SIGNATURE_MASK) == LESS_THAN_SIG) {
+ *s = (*s & ~SIGNATURE_MASK) + '0';
+ *--s = '<';
+ }
+ // fixup for log signature split [,
+ // OPEN_BRACKET_SPACE is space, OPEN_BRACKET_SIG + <digit>
+ if ((*s & SIGNATURE_MASK) == OPEN_BRACKET_SIG) {
+ if (*s == OPEN_BRACKET_SPACE) {
+ *s = ' ';
+ } else {
+ *s = (*s & ~SIGNATURE_MASK) + '0';
+ }
+ *--s = '[';
+ }
+ }
+
+ s += strspn(s, "\r\n");
+
+ if (!*s) { // no non-delimiter characters
+ *last = NULL;
+ return NULL;
+ }
+ char *peek, *tok = s;
+
+ for (;;) {
+ char c = *s++;
+ switch (c) {
+ case '\0':
+ *last = NULL;
+ return tok;
+
+ case '\r':
+ case '\n':
+ s[-1] = '\0';
+ *last = s;
+ return tok;
+
+ case '<':
+ peek = is_prio(s);
+ if (!peek) {
+ break;
+ }
+ if (s != (tok + 1)) { // not first?
+ s[-1] = '\0';
+ *s &= ~SIGNATURE_MASK;
+ *s |= LESS_THAN_SIG; // signature for '<'
+ *last = s;
+ return tok;
+ }
+ s = peek;
+ if ((*s == '[') && ((peek = is_timestamp(s + 1)))) {
+ s = peek;
+ }
+ break;
+
+ case '[':
+ peek = is_timestamp(s);
+ if (!peek) {
+ break;
+ }
+ if (s != (tok + 1)) { // not first?
+ s[-1] = '\0';
+ if (*s == ' ') {
+ *s = OPEN_BRACKET_SPACE;
+ } else {
+ *s &= ~SIGNATURE_MASK;
+ *s |= OPEN_BRACKET_SIG; // signature for '['
+ }
+ *last = s;
+ return tok;
+ }
+ s = peek;
+ break;
+ }
+ }
+ // NOTREACHED
+}
+
log_time LogKlog::correction = log_time(CLOCK_REALTIME) - log_time(CLOCK_MONOTONIC);
LogKlog::LogKlog(LogBuffer *buf, LogReader *reader, int fdWrite, int fdRead, bool auditd) :
@@ -43,8 +177,6 @@
logbuf(buf),
reader(reader),
signature(CLOCK_MONOTONIC),
- fdWrite(fdWrite),
- fdRead(fdRead),
initialized(false),
enableLogging(true),
auditd(auditd) {
@@ -81,8 +213,8 @@
char *ep = buffer + len;
*ep = '\0';
len = 0;
- for(char *ptr, *tok = buffer;
- ((tok = strtok_r(tok, "\r\n", &ptr)));
+ for(char *ptr = NULL, *tok = buffer;
+ ((tok = log_strtok_r(tok, &ptr)));
tok = NULL) {
if (((tok + strlen(tok)) == ep) && (retval != 0) && full) {
len = strlen(tok);
@@ -122,6 +254,7 @@
if ((cp = now.strptime(*buf, "[ %s.%q]"))) {
static const char suspend[] = "PM: suspend entry ";
static const char resume[] = "PM: suspend exit ";
+ static const char healthd[] = "healthd: battery ";
static const char suspended[] = "Suspended for ";
if (isspace(*cp)) {
@@ -131,6 +264,15 @@
calculateCorrection(now, cp + sizeof(suspend) - 1);
} else if (!strncmp(cp, resume, sizeof(resume) - 1)) {
calculateCorrection(now, cp + sizeof(resume) - 1);
+ } else if (!strncmp(cp, healthd, sizeof(healthd) - 1)) {
+ // look for " 2???-??-?? ??:??:??.????????? ???"
+ const char *tp;
+ for (tp = cp + sizeof(healthd) - 1; *tp && (*tp != '\n'); ++tp) {
+ if ((tp[0] == ' ') && (tp[1] == '2') && (tp[5] == '-')) {
+ calculateCorrection(now, tp + 1);
+ break;
+ }
+ }
} else if (!strncmp(cp, suspended, sizeof(suspended) - 1)) {
log_time real;
char *endp;
@@ -333,7 +475,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 == '.') {
@@ -358,7 +500,6 @@
}
}
} else if (isspace(cp[size])) {
- const char *b = cp;
cp += size;
while (isspace(*++cp));
// <PRI>[<TIME>] <tag> <tag> : message
@@ -403,20 +544,35 @@
}
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 = "";
}
}
size_t l = etag - tag;
+ // skip leading space
while (isspace(*buf)) {
++buf;
}
- size_t n = 1 + l + 1 + strlen(buf) + 1;
+ // truncate trailing space
+ size_t b = strlen(buf);
+ while (b && isspace(buf[b-1])) {
+ --b;
+ }
+ // trick ... allow tag with empty content to be logged. log() drops empty
+ if (!b && l) {
+ buf = " ";
+ b = 1;
+ }
+ size_t n = 1 + l + 1 + b + 1;
// Allocate a buffer to hold the interpreted log message
int rc = n;
@@ -438,7 +594,8 @@
++np;
// Copy main message to the remainder
- strcpy(np, buf);
+ strncpy(np, buf, b);
+ np[b] = '\0';
// Log message
rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr,
diff --git a/logd/LogKlog.h b/logd/LogKlog.h
index 8de9c87..24b2685 100644
--- a/logd/LogKlog.h
+++ b/logd/LogKlog.h
@@ -21,12 +21,12 @@
#include <log/log_read.h>
#include "LogReader.h"
+char *log_strtok_r(char *str, char **saveptr);
+
class LogKlog : public SocketListener {
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/LogStatistics.cpp b/logd/LogStatistics.cpp
index 90c49c0..61fd559 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -20,9 +20,9 @@
#include <string.h>
#include <unistd.h>
+#include <base/stringprintf.h>
#include <log/logger.h>
#include <private/android_filesystem_config.h>
-#include <utils/String8.h>
#include "LogStatistics.h"
@@ -161,9 +161,8 @@
}
// report uid -> pid(s) -> pidToName if unique
- ssize_t index = -1;
- while ((index = pidTable.next(index)) != -1) {
- const PidEntry &entry = pidTable.entryAt(index);
+ for(pidTable_t::iterator it = pidTable.begin(); it != pidTable.end(); ++it) {
+ const PidEntry &entry = it->second;
if (entry.getUid() == uid) {
const char *n = entry.getName();
@@ -184,8 +183,10 @@
return name;
}
-static void format_line(android::String8 &output,
- android::String8 &name, android::String8 &size, android::String8 &pruned) {
+static std::string format_line(
+ const std::string &name,
+ const std::string &size,
+ const std::string &pruned) {
static const size_t pruned_len = 6;
static const size_t total_len = 70 + pruned_len;
@@ -194,26 +195,21 @@
total_len - name.length() - drop_len - 1);
if (pruned.length()) {
- output.appendFormat("%s%*s%*s\n", name.string(),
- (int)size_len, size.string(),
- (int)drop_len, pruned.string());
+ return android::base::StringPrintf("%s%*s%*s\n", name.c_str(),
+ (int)size_len, size.c_str(),
+ (int)drop_len, pruned.c_str());
} else {
- output.appendFormat("%s%*s\n", name.string(),
- (int)size_len, size.string());
+ return android::base::StringPrintf("%s%*s\n", name.c_str(),
+ (int)size_len, size.c_str());
}
}
-void LogStatistics::format(char **buf, uid_t uid, unsigned int logMask) {
+std::string LogStatistics::format(uid_t uid, unsigned int logMask) {
static const unsigned short spaces_total = 19;
- if (*buf) {
- free(*buf);
- *buf = NULL;
- }
-
// Report on total logging, current and for all time
- android::String8 output("size/num");
+ std::string output = "size/num";
size_t oldLength;
short spaces = 1;
@@ -225,12 +221,13 @@
if (spaces < 0) {
spaces = 0;
}
- output.appendFormat("%*s%s", spaces, "", android_log_id_to_name(id));
+ output += android::base::StringPrintf("%*s%s", spaces, "",
+ android_log_id_to_name(id));
spaces += spaces_total + oldLength - output.length();
}
spaces = 4;
- output.appendFormat("\nTotal");
+ output += android::base::StringPrintf("\nTotal");
log_id_for_each(id) {
if (!(logMask & (1 << id))) {
@@ -240,13 +237,14 @@
if (spaces < 0) {
spaces = 0;
}
- output.appendFormat("%*s%zu/%zu", spaces, "",
- sizesTotal(id), elementsTotal(id));
+ output += android::base::StringPrintf("%*s%zu/%zu", spaces, "",
+ sizesTotal(id),
+ elementsTotal(id));
spaces += spaces_total + oldLength - output.length();
}
spaces = 6;
- output.appendFormat("\nNow");
+ output += android::base::StringPrintf("\nNow");
log_id_for_each(id) {
if (!(logMask & (1 << id))) {
@@ -259,7 +257,8 @@
if (spaces < 0) {
spaces = 0;
}
- output.appendFormat("%*s%zu/%zu", spaces, "", sizes(id), els);
+ output += android::base::StringPrintf("%*s%zu/%zu", spaces, "",
+ sizes(id), els);
spaces -= output.length() - oldLength;
}
spaces += spaces_total;
@@ -285,53 +284,54 @@
}
if (!headerPrinted) {
- output.appendFormat("\n\n");
- android::String8 name("");
+ output += android::base::StringPrintf("\n\n");
+ std::string name;
if (uid == AID_ROOT) {
- name.appendFormat(
+ name = android::base::StringPrintf(
"Chattiest UIDs in %s log buffer:",
android_log_id_to_name(id));
} else {
- name.appendFormat(
+ name = android::base::StringPrintf(
"Logging for your UID in %s log buffer:",
android_log_id_to_name(id));
}
- android::String8 size("Size");
- android::String8 pruned("Pruned");
+ std::string size = "Size";
+ std::string pruned = "Pruned";
if (!worstUidEnabledForLogid(id)) {
- pruned.setTo("");
+ pruned = "";
}
- format_line(output, name, size, pruned);
+ output += format_line(name, size, pruned);
- name.setTo("UID PACKAGE");
- size.setTo("BYTES");
- pruned.setTo("LINES");
+ name = "UID PACKAGE";
+ size = "BYTES";
+ pruned = "LINES";
if (!worstUidEnabledForLogid(id)) {
- pruned.setTo("");
+ pruned = "";
}
- format_line(output, name, size, pruned);
+ output += format_line(name, size, pruned);
headerPrinted = true;
}
- android::String8 name("");
- name.appendFormat("%u", u);
+ std::string name = android::base::StringPrintf("%u", u);
char *n = uidToName(u);
if (n) {
- name.appendFormat("%*s%s", (int)std::max(6 - name.length(), (size_t)1), "", n);
+ name += android::base::StringPrintf(
+ "%*s%s", (int)std::max(6 - name.length(), (size_t)1),
+ "", n);
free(n);
}
- android::String8 size("");
- size.appendFormat("%zu", entry->getSizes());
+ std::string size = android::base::StringPrintf("%zu",
+ entry->getSizes());
- android::String8 pruned("");
+ std::string pruned = "";
size_t dropped = entry->getDropped();
if (dropped) {
- pruned.appendFormat("%zu", dropped);
+ pruned = android::base::StringPrintf("%zu", dropped);
}
- format_line(output, name, size, pruned);
+ output += format_line(name, size, pruned);
}
}
@@ -348,48 +348,52 @@
}
if (!headerPrinted) {
- output.appendFormat("\n\n");
- android::String8 name("");
+ output += android::base::StringPrintf("\n\n");
+ std::string name;
if (uid == AID_ROOT) {
- name.appendFormat("Chattiest PIDs:");
+ name = android::base::StringPrintf("Chattiest PIDs:");
} else {
- name.appendFormat("Logging for this PID:");
+ name = android::base::StringPrintf("Logging for this PID:");
}
- android::String8 size("Size");
- android::String8 pruned("Pruned");
- format_line(output, name, size, pruned);
+ std::string size = "Size";
+ std::string pruned = "Pruned";
+ output += format_line(name, size, pruned);
- name.setTo(" PID/UID COMMAND LINE");
- size.setTo("BYTES");
- pruned.setTo("LINES");
- format_line(output, name, size, pruned);
+ name = " PID/UID COMMAND LINE";
+ size = "BYTES";
+ pruned = "LINES";
+ output += format_line(name, size, pruned);
headerPrinted = true;
}
- android::String8 name("");
- name.appendFormat("%5u/%u", entry->getKey(), u);
+ std::string name = android::base::StringPrintf("%5u/%u",
+ entry->getKey(), u);
const char *n = entry->getName();
if (n) {
- name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", n);
+ name += android::base::StringPrintf(
+ "%*s%s", (int)std::max(12 - name.length(), (size_t)1),
+ "", n);
} else {
char *un = uidToName(u);
if (un) {
- name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", un);
+ name += android::base::StringPrintf(
+ "%*s%s", (int)std::max(12 - name.length(), (size_t)1),
+ "", un);
free(un);
}
}
- android::String8 size("");
- size.appendFormat("%zu", entry->getSizes());
+ std::string size = android::base::StringPrintf("%zu",
+ entry->getSizes());
- android::String8 pruned("");
+ std::string pruned = "";
size_t dropped = entry->getDropped();
if (dropped) {
- pruned.appendFormat("%zu", dropped);
+ pruned = android::base::StringPrintf("%zu", dropped);
}
- format_line(output, name, size, pruned);
+ output += format_line(name, size, pruned);
}
}
@@ -407,46 +411,50 @@
}
if (!headerPrinted) { // Only print header if we have table to print
- output.appendFormat("\n\n");
- android::String8 name("Chattiest TIDs:");
- android::String8 size("Size");
- android::String8 pruned("Pruned");
- format_line(output, name, size, pruned);
+ output += android::base::StringPrintf("\n\n");
+ std::string name = "Chattiest TIDs:";
+ std::string size = "Size";
+ std::string pruned = "Pruned";
+ output += format_line(name, size, pruned);
- name.setTo(" TID/UID COMM");
- size.setTo("BYTES");
- pruned.setTo("LINES");
- format_line(output, name, size, pruned);
+ name = " TID/UID COMM";
+ size = "BYTES";
+ pruned = "LINES";
+ output += format_line(name, size, pruned);
headerPrinted = true;
}
- android::String8 name("");
- name.appendFormat("%5u/%u", entry->getKey(), u);
+ std::string name = android::base::StringPrintf("%5u/%u",
+ entry->getKey(), u);
const char *n = entry->getName();
if (n) {
- name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", n);
+ name += android::base::StringPrintf(
+ "%*s%s", (int)std::max(12 - name.length(), (size_t)1),
+ "", n);
} else {
// if we do not have a PID name, lets punt to try UID name?
char *un = uidToName(u);
if (un) {
- name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", un);
+ name += android::base::StringPrintf(
+ "%*s%s", (int)std::max(12 - name.length(), (size_t)1),
+ "", un);
free(un);
}
// We tried, better to not have a name at all, we still
// have TID/UID by number to report in any case.
}
- android::String8 size("");
- size.appendFormat("%zu", entry->getSizes());
+ std::string size = android::base::StringPrintf("%zu",
+ entry->getSizes());
- android::String8 pruned("");
+ std::string pruned = "";
size_t dropped = entry->getDropped();
if (dropped) {
- pruned.appendFormat("%zu", dropped);
+ pruned = android::base::StringPrintf("%zu", dropped);
}
- format_line(output, name, size, pruned);
+ output += format_line(name, size, pruned);
}
}
@@ -462,40 +470,44 @@
continue;
}
- android::String8 pruned("");
+ std::string pruned = "";
if (!headerPrinted) {
- output.appendFormat("\n\n");
- android::String8 name("Chattiest events log buffer TAGs:");
- android::String8 size("Size");
- format_line(output, name, size, pruned);
+ output += android::base::StringPrintf("\n\n");
+ std::string name = "Chattiest events log buffer TAGs:";
+ std::string size = "Size";
+ output += format_line(name, size, pruned);
- name.setTo(" TAG/UID TAGNAME");
- size.setTo("BYTES");
- format_line(output, name, size, pruned);
+ name = " TAG/UID TAGNAME";
+ size = "BYTES";
+ output += format_line(name, size, pruned);
headerPrinted = true;
}
- android::String8 name("");
+ std::string name;
if (u == (uid_t)-1) {
- name.appendFormat("%7u", entry->getKey());
+ name = android::base::StringPrintf("%7u",
+ entry->getKey());
} else {
- name.appendFormat("%7u/%u", entry->getKey(), u);
+ name = android::base::StringPrintf("%7u/%u",
+ entry->getKey(), u);
}
const char *n = entry->getName();
if (n) {
- name.appendFormat("%*s%s", (int)std::max(14 - name.length(), (size_t)1), "", n);
+ name += android::base::StringPrintf(
+ "%*s%s", (int)std::max(14 - name.length(), (size_t)1),
+ "", n);
}
- android::String8 size("");
- size.appendFormat("%zu", entry->getSizes());
+ std::string size = android::base::StringPrintf("%zu",
+ entry->getSizes());
- format_line(output, name, size, pruned);
+ output += format_line(name, size, pruned);
}
}
- *buf = strdup(output.string());
+ return output;
}
namespace android {
@@ -520,12 +532,12 @@
}
uid_t LogStatistics::pidToUid(pid_t pid) {
- return pidTable.entryAt(pidTable.add(pid)).getUid();
+ return pidTable.add(pid)->second.getUid();
}
// caller must free character string
char *LogStatistics::pidToName(pid_t pid) {
- const char *name = pidTable.entryAt(pidTable.add(pid)).getName();
+ const char *name = pidTable.add(pid)->second.getName();
if (!name) {
return NULL;
}
diff --git a/logd/LogStatistics.h b/logd/LogStatistics.h
index f60f3ed..61000d2 100644
--- a/logd/LogStatistics.h
+++ b/logd/LogStatistics.h
@@ -21,8 +21,9 @@
#include <stdlib.h>
#include <sys/types.h>
+#include <unordered_map>
+
#include <log/log.h>
-#include <utils/BasicHashtable.h>
#include "LogBufferElement.h"
@@ -30,8 +31,14 @@
for (log_id_t i = LOG_ID_MIN; i < LOG_ID_MAX; i = (log_id_t) (i + 1))
template <typename TKey, typename TEntry>
-class LogHashtable : public android::BasicHashtable<TKey, TEntry> {
+class LogHashtable {
+
+ std::unordered_map<TKey, TEntry> map;
+
public:
+
+ typedef typename std::unordered_map<TKey, TEntry>::iterator iterator;
+
std::unique_ptr<const TEntry *[]> sort(size_t n) {
if (!n) {
std::unique_ptr<const TEntry *[]> sorted(NULL);
@@ -41,9 +48,8 @@
const TEntry **retval = new const TEntry* [n];
memset(retval, 0, sizeof(*retval) * n);
- ssize_t index = -1;
- while ((index = android::BasicHashtable<TKey, TEntry>::next(index)) >= 0) {
- const TEntry &entry = android::BasicHashtable<TKey, TEntry>::entryAt(index);
+ for(iterator it = map.begin(); it != map.end(); ++it) {
+ const TEntry &entry = it->second;
size_t s = entry.getSizes();
ssize_t i = n - 1;
while ((!retval[i] || (s > retval[i]->getSizes())) && (--i >= 0))
@@ -70,45 +76,43 @@
return index;
}
- ssize_t next(ssize_t index) {
- return android::BasicHashtable<TKey, TEntry>::next(index);
+ inline iterator add(TKey key, LogBufferElement *e) {
+ iterator it = map.find(key);
+ if (it == map.end()) {
+ it = map.insert(std::make_pair(key, TEntry(e))).first;
+ } else {
+ it->second.add(e);
+ }
+ return it;
}
- size_t add(TKey key, LogBufferElement *e) {
- android::hash_t hash = android::hash_type(key);
- ssize_t index = android::BasicHashtable<TKey, TEntry>::find(-1, hash, key);
- if (index == -1) {
- return android::BasicHashtable<TKey, TEntry>::add(hash, TEntry(e));
+ inline iterator add(TKey key) {
+ iterator it = map.find(key);
+ if (it == map.end()) {
+ it = map.insert(std::make_pair(key, TEntry(key))).first;
+ } else {
+ it->second.add(key);
}
- android::BasicHashtable<TKey, TEntry>::editEntryAt(index).add(e);
- return index;
- }
-
- inline size_t add(TKey key) {
- android::hash_t hash = android::hash_type(key);
- ssize_t index = android::BasicHashtable<TKey, TEntry>::find(-1, hash, key);
- if (index == -1) {
- return android::BasicHashtable<TKey, TEntry>::add(hash, TEntry(key));
- }
- android::BasicHashtable<TKey, TEntry>::editEntryAt(index).add(key);
- return index;
+ return it;
}
void subtract(TKey key, LogBufferElement *e) {
- ssize_t index = android::BasicHashtable<TKey, TEntry>::find(-1, android::hash_type(key), key);
- if ((index != -1)
- && android::BasicHashtable<TKey, TEntry>::editEntryAt(index).subtract(e)) {
- android::BasicHashtable<TKey, TEntry>::removeAt(index);
+ iterator it = map.find(key);
+ if ((it != map.end()) && it->second.subtract(e)) {
+ map.erase(it);
}
}
inline void drop(TKey key, LogBufferElement *e) {
- ssize_t index = android::BasicHashtable<TKey, TEntry>::find(-1, android::hash_type(key), key);
- if (index != -1) {
- android::BasicHashtable<TKey, TEntry>::editEntryAt(index).drop(e);
+ iterator it = map.find(key);
+ if (it != map.end()) {
+ it->second.drop(e);
}
}
+ inline iterator begin() { return map.begin(); }
+ inline iterator end() { return map.end(); }
+
};
struct EntryBase {
@@ -327,10 +331,9 @@
size_t sizesTotal(log_id_t id) const { return mSizesTotal[id]; }
size_t elementsTotal(log_id_t id) const { return mElementsTotal[id]; }
- // *strp = malloc, balance with free
- void format(char **strp, uid_t uid, unsigned int logMask);
+ std::string format(uid_t uid, unsigned int logMask);
- // helper
+ // helper (must be locked directly or implicitly by mLogElementsLock)
char *pidToName(pid_t pid);
uid_t pidToUid(pid_t pid);
char *uidToName(uid_t uid);
diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp
index ec67c07..68a0680 100644
--- a/logd/LogTimes.cpp
+++ b/logd/LogTimes.cpp
@@ -31,6 +31,7 @@
mRelease(false),
mError(false),
threadRunning(false),
+ leadingDropped(false),
mReader(reader),
mLogMask(logMask),
mPid(pid),
@@ -123,6 +124,8 @@
bool privileged = FlushCommand::hasReadLogs(client);
+ me->leadingDropped = true;
+
lock();
while (me->threadRunning && !me->isError_Locked()) {
@@ -132,6 +135,7 @@
if (me->mTail) {
logbuf.flushTo(client, start, privileged, FilterFirstPass, me);
+ me->leadingDropped = true;
}
start = logbuf.flushTo(client, start, privileged, FilterSecondPass, me);
@@ -163,6 +167,14 @@
LogTimeEntry::lock();
+ if (me->leadingDropped) {
+ if (element->getDropped()) {
+ LogTimeEntry::unlock();
+ return false;
+ }
+ me->leadingDropped = false;
+ }
+
if (me->mCount == 0) {
me->mStart = element->getSequence();
}
@@ -190,6 +202,13 @@
goto skip;
}
+ if (me->leadingDropped) {
+ if (element->getDropped()) {
+ goto skip;
+ }
+ me->leadingDropped = false;
+ }
+
// Truncate to close race between first and second pass
if (me->mNonBlock && me->mTail && (me->mIndex >= me->mCount)) {
goto stop;
diff --git a/logd/LogTimes.h b/logd/LogTimes.h
index ae2f92b..39bcdd4 100644
--- a/logd/LogTimes.h
+++ b/logd/LogTimes.h
@@ -20,8 +20,10 @@
#include <pthread.h>
#include <time.h>
#include <sys/types.h>
+
+#include <list>
+
#include <sysutils/SocketClient.h>
-#include <utils/List.h>
#include <log/log.h>
class LogReader;
@@ -32,6 +34,7 @@
bool mRelease;
bool mError;
bool threadRunning;
+ bool leadingDropped;
pthread_cond_t threadTriggeredCondition;
pthread_t mThread;
LogReader &mReader;
@@ -106,6 +109,6 @@
static int FilterSecondPass(const LogBufferElement *element, void *me);
};
-typedef android::List<LogTimeEntry *> LastLogTimes;
+typedef std::list<LogTimeEntry *> LastLogTimes;
-#endif
+#endif // _LOGD_LOG_TIMES_H__
diff --git a/logd/LogWhiteBlackList.cpp b/logd/LogWhiteBlackList.cpp
index 277b3ca..ad005ec 100644
--- a/logd/LogWhiteBlackList.cpp
+++ b/logd/LogWhiteBlackList.cpp
@@ -16,7 +16,7 @@
#include <ctype.h>
-#include <utils/String8.h>
+#include <base/stringprintf.h>
#include "LogWhiteBlackList.h"
@@ -35,46 +35,40 @@
return uid - mUid;
}
-void Prune::format(char **strp) {
+std::string Prune::format() {
if (mUid != uid_all) {
if (mPid != pid_all) {
- asprintf(strp, "%u/%u", mUid, mPid);
- } else {
- asprintf(strp, "%u", mUid);
+ return android::base::StringPrintf("%u/%u", mUid, mPid);
}
- } else if (mPid != pid_all) {
- asprintf(strp, "/%u", mPid);
- } else { // NB: mPid == pid_all can not happen if mUid == uid_all
- asprintf(strp, "/");
+ return android::base::StringPrintf("%u", mUid);
}
+ if (mPid != pid_all) {
+ return android::base::StringPrintf("/%u", mPid);
+ }
+ // NB: mPid == pid_all can not happen if mUid == uid_all
+ return std::string("/");
}
PruneList::PruneList() : mWorstUidEnabled(true) {
- mNaughty.clear();
- mNice.clear();
}
PruneList::~PruneList() {
PruneCollection::iterator it;
for (it = mNice.begin(); it != mNice.end();) {
- delete (*it);
it = mNice.erase(it);
}
for (it = mNaughty.begin(); it != mNaughty.end();) {
- delete (*it);
it = mNaughty.erase(it);
}
}
-int PruneList::init(char *str) {
+int PruneList::init(const char *str) {
mWorstUidEnabled = true;
PruneCollection::iterator it;
for (it = mNice.begin(); it != mNice.end();) {
- delete (*it);
it = mNice.erase(it);
}
for (it = mNaughty.begin(); it != mNaughty.end();) {
- delete (*it);
it = mNaughty.erase(it);
}
@@ -142,28 +136,28 @@
// insert sequentially into list
PruneCollection::iterator it = list->begin();
while (it != list->end()) {
- Prune *p = *it;
- int m = uid - p->mUid;
+ Prune &p = *it;
+ int m = uid - p.mUid;
if (m == 0) {
- if (p->mPid == p->pid_all) {
+ if (p.mPid == p.pid_all) {
break;
}
- if ((pid == p->pid_all) && (p->mPid != p->pid_all)) {
+ if ((pid == p.pid_all) && (p.mPid != p.pid_all)) {
it = list->erase(it);
continue;
}
- m = pid - p->mPid;
+ m = pid - p.mPid;
}
if (m <= 0) {
if (m < 0) {
- list->insert(it, new Prune(uid,pid));
+ list->insert(it, Prune(uid,pid));
}
break;
}
++it;
}
if (it == list->end()) {
- list->push_back(new Prune(uid,pid));
+ list->push_back(Prune(uid,pid));
}
if (!*str) {
break;
@@ -173,47 +167,32 @@
return 0;
}
-void PruneList::format(char **strp) {
- if (*strp) {
- free(*strp);
- *strp = NULL;
- }
-
+std::string PruneList::format() {
static const char nice_format[] = " %s";
const char *fmt = nice_format + 1;
- android::String8 string;
+ std::string string;
if (mWorstUidEnabled) {
- string.setTo("~!");
+ string = "~!";
fmt = nice_format;
}
PruneCollection::iterator it;
for (it = mNice.begin(); it != mNice.end(); ++it) {
- char *a = NULL;
- (*it)->format(&a);
-
- string.appendFormat(fmt, a);
+ string += android::base::StringPrintf(fmt, (*it).format().c_str());
fmt = nice_format;
-
- free(a);
}
static const char naughty_format[] = " ~%s";
fmt = naughty_format + (*fmt != ' ');
for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
- char *a = NULL;
- (*it)->format(&a);
-
- string.appendFormat(fmt, a);
+ string += android::base::StringPrintf(fmt, (*it).format().c_str());
fmt = naughty_format;
-
- free(a);
}
- *strp = strdup(string.string());
+ return string;
}
// ToDo: Lists are in sorted order, Prune->cmp() returns + or -
@@ -223,7 +202,7 @@
bool PruneList::naughty(LogBufferElement *element) {
PruneCollection::iterator it;
for (it = mNaughty.begin(); it != mNaughty.end(); ++it) {
- if (!(*it)->cmp(element)) {
+ if (!(*it).cmp(element)) {
return true;
}
}
@@ -233,7 +212,7 @@
bool PruneList::nice(LogBufferElement *element) {
PruneCollection::iterator it;
for (it = mNice.begin(); it != mNice.end(); ++it) {
- if (!(*it)->cmp(element)) {
+ if (!(*it).cmp(element)) {
return true;
}
}
diff --git a/logd/LogWhiteBlackList.h b/logd/LogWhiteBlackList.h
index 5f60801..00e1cad 100644
--- a/logd/LogWhiteBlackList.h
+++ b/logd/LogWhiteBlackList.h
@@ -19,7 +19,8 @@
#include <sys/types.h>
-#include <utils/List.h>
+#include <list>
+#include <string.h>
#include <LogBufferElement.h>
@@ -43,11 +44,10 @@
int cmp(LogBufferElement *e) const { return cmp(e->getUid(), e->getPid()); }
- // *strp is malloc'd, use free to release
- void format(char **strp);
+ std::string format();
};
-typedef android::List<Prune *> PruneCollection;
+typedef std::list<Prune> PruneCollection;
class PruneList {
PruneCollection mNaughty;
@@ -58,7 +58,7 @@
PruneList();
~PruneList();
- int init(char *str);
+ int init(const char *str);
bool naughty(LogBufferElement *element);
bool naughty(void) { return !mNaughty.empty(); }
@@ -66,8 +66,7 @@
bool nice(void) { return !mNice.empty(); }
bool worstUidEnabled() const { return mWorstUidEnabled; }
- // *strp is malloc'd, use free to release
- void format(char **strp);
+ std::string format();
};
#endif // _LOGD_LOG_WHITE_BLACK_LIST_H__
diff --git a/logd/README.property b/logd/README.property
index ad7d0cd..a472efd 100644
--- a/logd/README.property
+++ b/logd/README.property
@@ -10,6 +10,8 @@
default false
ro.build.type string if user, logd.statistics & logd.klogd
default false
+persist.logd.logpersistd string Enable logpersist daemon, "logcatd"
+ turns on logcat -f in logd context
persist.logd.size number 256K default size of the buffer for all
log ids at initial startup, at runtime
use: logcat -b all -G <value>
diff --git a/logd/logd.rc b/logd/logd.rc
new file mode 100644
index 0000000..498baec
--- /dev/null
+++ b/logd/logd.rc
@@ -0,0 +1,9 @@
+service logd /system/bin/logd
+ class core
+ socket logd stream 0666 logd logd
+ socket logdr seqpacket 0666 logd logd
+ socket logdw dgram 0222 logd logd
+
+service logd-reinit /system/bin/logd --reinit
+ oneshot
+ disabled
diff --git a/logd/logpersist b/logd/logpersist
new file mode 100755
index 0000000..215e1e2
--- /dev/null
+++ b/logd/logpersist
@@ -0,0 +1,36 @@
+#! /system/bin/sh
+# logpersist cat start and stop handlers
+data=/data/misc/logd
+property=persist.logd.logpersistd
+service=logcatd
+progname="${0##*/}"
+if [ X"${1}" = "-h" -o X"${1}" = X"--help" ]; then
+ echo "${progname%.*}.cat - dump current ${service%d} logs"
+ echo "${progname%.*}.start - start ${service} service"
+ echo "${progname%.*}.stop [--clear] - stop ${service} service"
+ exit 0
+fi
+case ${progname} in
+*.cat)
+ su 1036 ls "${data}" |
+ tr -d '\r' |
+ sort -ru |
+ sed "s#^#${data}/#" |
+ su 1036 xargs cat
+ ;;
+*.start)
+ su 0 setprop ${property} ${service}
+ getprop ${property}
+ sleep 1
+ ps -t | grep "${data##*/}.*${service%d}"
+ ;;
+*.stop)
+ su 0 stop ${service}
+ su 0 setprop ${property} ""
+ [ X"${1}" != X"-c" -a X"${1}" != X"--clear" ] ||
+ ( sleep 1 ; su 1036,9998 rm -rf "${data}" )
+ ;;
+*)
+ echo "Unexpected command ${0##*/} ${@}" >&2
+ exit 1
+esac
diff --git a/logd/main.cpp b/logd/main.cpp
index 6db819e..9b88983 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -27,16 +27,20 @@
#include <sys/capability.h>
#include <sys/klog.h>
#include <sys/prctl.h>
+#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
+#include <memory>
+
#include <cutils/properties.h>
#include <cutils/sched_policy.h>
#include <cutils/sockets.h>
#include <log/event_tag_map.h>
#include <private/android_filesystem_config.h>
+#include <utils/threads.h>
#include "CommandListener.h"
#include "LogBuffer.h"
@@ -91,6 +95,10 @@
return -1;
}
+ if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
+ return -1;
+ }
+
if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
return -1;
}
@@ -156,6 +164,7 @@
static void *reinit_thread_start(void * /*obj*/) {
prctl(PR_SET_NAME, "logd.daemon");
set_sched_policy(0, SP_BACKGROUND);
+ setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND);
setgid(AID_SYSTEM);
setuid(AID_SYSTEM);
@@ -268,6 +277,45 @@
&& !property_get_bool("ro.config.low_ram", false));
}
+static void readDmesg(LogAudit *al, LogKlog *kl) {
+ if (!al && !kl) {
+ return;
+ }
+
+ int len = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
+ if (len <= 0) {
+ return;
+ }
+
+ len += 1024; // Margin for additional input race or trailing nul
+ std::unique_ptr<char []> buf(new char[len]);
+
+ int rc = klogctl(KLOG_READ_ALL, buf.get(), len);
+ if (rc <= 0) {
+ return;
+ }
+
+ if (rc < len) {
+ len = rc + 1;
+ }
+ buf[len - 1] = '\0';
+
+ if (kl) {
+ kl->synchronize(buf.get());
+ }
+
+ for (char *ptr = NULL, *tok = buf.get();
+ (rc >= 0) && ((tok = log_strtok_r(tok, &ptr)));
+ tok = NULL) {
+ if (al) {
+ rc = al->log(tok);
+ }
+ if (kl) {
+ rc = kl->log(tok);
+ }
+ }
+}
+
// Foreground waits for exit of the main persistent threads
// that are started here. The threads are created to manage
// UNIX domain client sockets for writing, reading and
@@ -403,41 +451,16 @@
kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != NULL);
}
- if (al || kl) {
- int len = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
- if (len > 0) {
- len++;
- char buf[len];
+ readDmesg(al, kl);
- int rc = klogctl(KLOG_READ_ALL, buf, len);
+ // failure is an option ... messages are in dmesg (required by standard)
- buf[len - 1] = '\0';
+ if (kl && kl->startListener()) {
+ delete kl;
+ }
- if ((rc >= 0) && kl) {
- kl->synchronize(buf);
- }
-
- for (char *ptr, *tok = buf;
- (rc >= 0) && ((tok = strtok_r(tok, "\r\n", &ptr)));
- tok = NULL) {
- if (al) {
- rc = al->log(tok);
- }
- if (kl) {
- rc = kl->log(tok);
- }
- }
- }
-
- // failure is an option ... messages are in dmesg (required by standard)
-
- if (kl && kl->startListener()) {
- delete kl;
- }
-
- if (al && al->startListener()) {
- delete al;
- }
+ if (al && al->startListener()) {
+ delete al;
}
TEMP_FAILURE_RETRY(pause());
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/Android.mk b/logwrapper/Android.mk
index 61b4659..ad45b2c 100644
--- a/logwrapper/Android.mk
+++ b/logwrapper/Android.mk
@@ -11,7 +11,7 @@
LOCAL_SHARED_LIBRARIES := libcutils liblog
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Werror -std=gnu99
include $(BUILD_STATIC_LIBRARY)
# ========================================================
@@ -23,7 +23,7 @@
LOCAL_WHOLE_STATIC_LIBRARIES := liblogwrap
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Werror -std=gnu99
include $(BUILD_SHARED_LIBRARY)
# ========================================================
@@ -33,5 +33,5 @@
LOCAL_SRC_FILES:= logwrapper.c
LOCAL_MODULE := logwrapper
LOCAL_STATIC_LIBRARIES := liblog liblogwrap libcutils
-LOCAL_CFLAGS := -Werror
+LOCAL_CFLAGS := -Werror -std=gnu99
include $(BUILD_EXECUTABLE)
diff --git a/logwrapper/include/logwrap/logwrap.h b/logwrapper/include/logwrap/logwrap.h
index 4307a30..89a8fdd 100644
--- a/logwrapper/include/logwrap/logwrap.h
+++ b/logwrapper/include/logwrap/logwrap.h
@@ -19,6 +19,7 @@
#define __LIBS_LOGWRAP_H
#include <stdbool.h>
+#include <stdint.h>
__BEGIN_DECLS
@@ -53,6 +54,9 @@
* the specified log until the child has exited.
* file_path: if log_target has the LOG_FILE bit set, then this parameter
* must be set to the pathname of the file to log to.
+ * opts: set to non-NULL if you want to use one or more of the
+ * FORK_EXECVP_OPTION_* features.
+ * opts_len: the length of the opts array. When opts is NULL, pass 0.
*
* Return value:
* 0 when logwrap successfully run the child process and captured its status
@@ -68,8 +72,30 @@
#define LOG_KLOG 2
#define LOG_FILE 4
+/* Write data to child's stdin. */
+#define FORK_EXECVP_OPTION_INPUT 0
+/* Capture data from child's stdout and stderr. */
+#define FORK_EXECVP_OPTION_CAPTURE_OUTPUT 1
+
+struct AndroidForkExecvpOption {
+ int opt_type;
+ union {
+ struct {
+ const uint8_t* input;
+ size_t input_len;
+ } opt_input;
+ struct {
+ void (*on_output)(const uint8_t* /*output*/,
+ size_t /*output_len*/,
+ void* /* user_pointer */);
+ void* user_pointer;
+ } opt_capture_output;
+ };
+};
+
int android_fork_execvp_ext(int argc, char* argv[], int *status, bool ignore_int_quit,
- int log_target, bool abbreviated, char *file_path);
+ int log_target, bool abbreviated, char *file_path,
+ const struct AndroidForkExecvpOption* opts, size_t opts_len);
/* Similar to above, except abbreviated logging is not available, and if logwrap
* is true, logging is to the Android system log, and if false, there is no
@@ -79,7 +105,8 @@
bool ignore_int_quit, bool logwrap)
{
return android_fork_execvp_ext(argc, argv, status, ignore_int_quit,
- (logwrap ? LOG_ALOG : LOG_NONE), false, NULL);
+ (logwrap ? LOG_ALOG : LOG_NONE), false, NULL,
+ NULL, 0);
}
__END_DECLS
diff --git a/logwrapper/logwrap.c b/logwrapper/logwrap.c
index 3a6276e..39bc8fd 100644
--- a/logwrapper/logwrap.c
+++ b/logwrapper/logwrap.c
@@ -291,7 +291,8 @@
}
static int parent(const char *tag, int parent_read, pid_t pid,
- int *chld_sts, int log_target, bool abbreviated, char *file_path) {
+ int *chld_sts, int log_target, bool abbreviated, char *file_path,
+ const struct AndroidForkExecvpOption* opts, size_t opts_len) {
int status = 0;
char buffer[4096];
struct pollfd poll_fds[] = {
@@ -355,7 +356,15 @@
}
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));
+
+ for (size_t i = 0; sz > 0 && i < opts_len; ++i) {
+ if (opts[i].opt_type == FORK_EXECVP_OPTION_CAPTURE_OUTPUT) {
+ opts[i].opt_capture_output.on_output(
+ (uint8_t*)&buffer[b], sz, opts[i].opt_capture_output.user_pointer);
+ }
+ }
sz += b;
// Log one line at a time
@@ -473,7 +482,8 @@
}
int android_fork_execvp_ext(int argc, char* argv[], int *status, bool ignore_int_quit,
- int log_target, bool abbreviated, char *file_path) {
+ int log_target, bool abbreviated, char *file_path,
+ const struct AndroidForkExecvpOption* opts, size_t opts_len) {
pid_t pid;
int parent_ptty;
int child_ptty;
@@ -490,7 +500,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 +515,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;
@@ -528,7 +538,13 @@
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
close(parent_ptty);
- // redirect stdout and stderr
+ // redirect stdin, stdout and stderr
+ for (size_t i = 0; i < opts_len; ++i) {
+ if (opts[i].opt_type == FORK_EXECVP_OPTION_INPUT) {
+ dup2(child_ptty, 0);
+ break;
+ }
+ }
dup2(child_ptty, 1);
dup2(child_ptty, 2);
close(child_ptty);
@@ -545,8 +561,24 @@
sigaction(SIGQUIT, &ignact, &quitact);
}
+ for (size_t i = 0; i < opts_len; ++i) {
+ if (opts[i].opt_type == FORK_EXECVP_OPTION_INPUT) {
+ size_t left = opts[i].opt_input.input_len;
+ const uint8_t* input = opts[i].opt_input.input;
+ while (left > 0) {
+ ssize_t res =
+ TEMP_FAILURE_RETRY(write(parent_ptty, input, left));
+ if (res < 0) {
+ break;
+ }
+ left -= res;
+ input += res;
+ }
+ }
+ }
+
rc = parent(argv[0], parent_ptty, pid, status, log_target,
- abbreviated, file_path);
+ abbreviated, file_path, opts, opts_len);
}
if (ignore_int_quit) {
diff --git a/logwrapper/logwrapper.c b/logwrapper/logwrapper.c
index 9e0385d..55b71c7 100644
--- a/logwrapper/logwrapper.c
+++ b/logwrapper/logwrapper.c
@@ -81,7 +81,7 @@
}
rc = android_fork_execvp_ext(argc, &argv[0], &status, true,
- log_target, abbreviated, NULL);
+ log_target, abbreviated, NULL, NULL, 0);
if (!rc) {
if (WIFEXITED(status))
rc = WEXITSTATUS(status);
diff --git a/metricsd/Android.mk b/metricsd/Android.mk
new file mode 100644
index 0000000..9fd8eda
--- /dev/null
+++ b/metricsd/Android.mk
@@ -0,0 +1,127 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(HOST_OS),linux)
+
+LOCAL_INIT_SERVICE := metrics_daemon
+
+metrics_cpp_extension := .cc
+libmetrics_sources := \
+ c_metrics_library.cc \
+ metrics_library.cc \
+ serialization/metric_sample.cc \
+ serialization/serialization_utils.cc
+
+metrics_client_sources := \
+ metrics_client.cc
+
+metrics_daemon_sources := \
+ metrics_daemon.cc \
+ metrics_daemon_main.cc \
+ persistent_integer.cc \
+ uploader/metrics_hashes.cc \
+ uploader/metrics_log_base.cc \
+ uploader/metrics_log.cc \
+ uploader/sender_http.cc \
+ uploader/system_profile_cache.cc \
+ uploader/upload_service.cc \
+ serialization/metric_sample.cc \
+ serialization/serialization_utils.cc
+
+metrics_CFLAGS := -Wall \
+ -Wno-char-subscripts \
+ -Wno-missing-field-initializers \
+ -Wno-unused-function \
+ -Wno-unused-parameter \
+ -Werror \
+ -fvisibility=default
+metrics_CPPFLAGS := -Wno-non-virtual-dtor \
+ -Wno-sign-promo \
+ -Wno-strict-aliasing \
+ -fvisibility=default
+metrics_includes := external/gtest/include \
+ $(LOCAL_PATH)/include
+metrics_shared_libraries := libchrome libchromeos
+
+# Shared library for metrics.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmetrics
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SHARED_LIBRARIES := $(metrics_shared_libraries)
+LOCAL_SRC_FILES := $(libmetrics_sources)
+include $(BUILD_SHARED_LIBRARY)
+
+# CLI client for metrics.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_client
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_SHARED_LIBRARIES := $(metrics_shared_libraries) \
+ libmetrics
+LOCAL_SRC_FILES := $(metrics_client_sources)
+include $(BUILD_EXECUTABLE)
+
+# Protobuf library for metrics_daemon.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_daemon_protos
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+generated_sources_dir := $(call local-generated-sources-dir)
+LOCAL_EXPORT_C_INCLUDE_DIRS += \
+ $(generated_sources_dir)/proto/system/core/metricsd
+LOCAL_SRC_FILES := $(call all-proto-files-under,uploader/proto)
+LOCAL_STATIC_LIBRARIES := libprotobuf-cpp-lite
+include $(BUILD_STATIC_LIBRARY)
+
+# metrics daemon.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(LOCAL_INIT_SERVICE)
+LOCAL_C_INCLUDES := $(metrics_includes) \
+ external/libchromeos
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_REQUIRED_MODULES := init.$(LOCAL_INIT_SERVICE).rc
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_SHARED_LIBRARIES := $(metrics_shared_libraries) \
+ libmetrics \
+ libprotobuf-cpp-lite \
+ libchromeos-http \
+ libchromeos-dbus \
+ libdbus
+LOCAL_SRC_FILES := $(metrics_daemon_sources)
+LOCAL_STATIC_LIBRARIES := metrics_daemon_protos
+include $(BUILD_EXECUTABLE)
+
+ifdef INITRC_TEMPLATE
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.$(LOCAL_INIT_SERVICE).rc
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/$(TARGET_COPY_OUT_INITRCD)
+LOCAL_SRC_FILES := init.$(LOCAL_INIT_SERVICE).rc
+include $(BUILD_PREBUILT)
+endif # INITRC_TEMPLATE
+
+endif # HOST_OS == linux
diff --git a/metricsd/OWNERS b/metricsd/OWNERS
new file mode 100644
index 0000000..7f5e50d
--- /dev/null
+++ b/metricsd/OWNERS
@@ -0,0 +1,3 @@
+semenzato@chromium.org
+derat@chromium.org
+bsimonnet@chromium.org
diff --git a/metricsd/README b/metricsd/README
new file mode 100644
index 0000000..d4c9a0e
--- /dev/null
+++ b/metricsd/README
@@ -0,0 +1,150 @@
+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.
+
+================================================================================
+
+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/metricsd/WATCHLISTS b/metricsd/WATCHLISTS
new file mode 100644
index 0000000..a051f35
--- /dev/null
+++ b/metricsd/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/metricsd/c_metrics_library.cc b/metricsd/c_metrics_library.cc
new file mode 100644
index 0000000..0503876
--- /dev/null
+++ b/metricsd/c_metrics_library.cc
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+//
+// 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/metricsd/constants.h b/metricsd/constants.h
new file mode 100644
index 0000000..56dac0d
--- /dev/null
+++ b/metricsd/constants.h
@@ -0,0 +1,29 @@
+//
+// 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.
+//
+
+#ifndef METRICS_CONSTANTS_H_
+#define METRICS_CONSTANTS_H_
+
+namespace metrics {
+static const char kMetricsDirectory[] = "/data/misc/metrics/";
+static const char kMetricsEventsFilePath[] = "/data/misc/metrics/uma-events";
+static const char kMetricsGUIDFilePath[] = "/data/misc/metrics/Sysinfo.GUID";
+static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
+static const char kConsentFilePath[] = "/data/misc/metrics/enabled";
+static const char kDefaultVersion[] = "0.0.0.0";
+} // namespace metrics
+
+#endif // METRICS_CONSTANTS_H_
diff --git a/metricsd/include/metrics/c_metrics_library.h b/metricsd/include/metrics/c_metrics_library.h
new file mode 100644
index 0000000..4e7e666
--- /dev/null
+++ b/metricsd/include/metrics/c_metrics_library.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/include/metrics/metrics_library.h b/metricsd/include/metrics/metrics_library.h
new file mode 100644
index 0000000..4917a7c
--- /dev/null
+++ b/metricsd/include/metrics/metrics_library.h
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+#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
+
+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 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);
+
+ 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);
+
+ // 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLibrary);
+};
+
+#endif // METRICS_METRICS_LIBRARY_H_
diff --git a/metricsd/init.metrics_daemon.rc b/metricsd/init.metrics_daemon.rc
new file mode 100644
index 0000000..73ce673
--- /dev/null
+++ b/metricsd/init.metrics_daemon.rc
@@ -0,0 +1,8 @@
+on boot
+ mkdir /data/misc/metrics 0770 system system
+
+service metrics_daemon /system/bin/metrics_daemon --uploader -nodaemon
+ class late_start
+ user system
+ group system dbus inet
+ seclabel u:r:brillo:s0
diff --git a/metricsd/libmetrics-334380.gyp b/metricsd/libmetrics-334380.gyp
new file mode 100644
index 0000000..9771821
--- /dev/null
+++ b/metricsd/libmetrics-334380.gyp
@@ -0,0 +1,8 @@
+{
+ 'variables': {
+ 'libbase_ver': 334380,
+ },
+ 'includes': [
+ 'libmetrics.gypi',
+ ],
+}
diff --git a/metricsd/libmetrics.gypi b/metricsd/libmetrics.gypi
new file mode 100644
index 0000000..5b90a55
--- /dev/null
+++ b/metricsd/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/metricsd/libmetrics.pc.in b/metricsd/libmetrics.pc.in
new file mode 100644
index 0000000..233f318
--- /dev/null
+++ b/metricsd/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/metricsd/metrics.gyp b/metricsd/metrics.gyp
new file mode 100644
index 0000000..276ec78
--- /dev/null
+++ b/metricsd/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/metricsd/metrics_client.cc b/metricsd/metrics_client.cc
new file mode 100644
index 0000000..57e96c2
--- /dev/null
+++ b/metricsd/metrics_client.cc
@@ -0,0 +1,210 @@
+/*
+ * 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.
+ */
+
+#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 [-t] name sample min max nbuckets\n"
+ " metrics_client -e name sample max\n"
+ " metrics_client -s name sample\n"
+ " metrics_client -v event\n"
+ " metrics_client -u action\n"
+ " metrics_client [-cg]\n"
+ "\n"
+ " default: send metric with integer values \n"
+ " |min| > 0, |min| <= sample < |max|\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) {
+ 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]);
+ }
+
+ 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 secs_to_msecs = false;
+
+ // Parse arguments
+ int flag;
+ while ((flag = getopt(argc, argv, "abcegstuv")) != -1) {
+ switch (flag) {
+ 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);
+ 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/metricsd/metrics_daemon.cc b/metricsd/metrics_daemon.cc
new file mode 100644
index 0000000..069f68e
--- /dev/null
+++ b/metricsd/metrics_daemon.cc
@@ -0,0 +1,1117 @@
+/*
+ * 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.
+ */
+
+#include "metrics_daemon.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <math.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+
+#include <base/bind.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 <dbus/dbus.h>
+#include <dbus/message.h>
+
+#include "constants.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'";
+
+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 = metrics::kDefaultVersion;
+ // The version might not be set for development devices. In this case, use the
+ // zero version.
+ base::SysInfo::GetLsbReleaseValue("BRILLO_VERSION", &version);
+ cached_version_hash = base::Hash(version);
+ if (testing_) {
+ cached_version_hash = 42; // return any plausible value for the hash
+ }
+ return cached_version_hash;
+}
+
+void MetricsDaemon::Init(bool testing,
+ bool uploader_active,
+ bool dbus_enabled,
+ MetricsLibraryInterface* metrics_lib,
+ 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;
+ dbus_enabled_ = dbus_enabled;
+ 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"));
+
+ vmstats_path_ = vmstats_path;
+ scaling_max_freq_path_ = scaling_max_freq_path;
+ cpuinfo_max_freq_path_ = cpuinfo_max_freq_path;
+}
+
+int MetricsDaemon::OnInit() {
+ int return_code = dbus_enabled_ ? chromeos::DBusDaemon::OnInit() :
+ chromeos::Daemon::OnInit();
+ if (return_code != EX_OK)
+ return return_code;
+
+ if (testing_)
+ return EX_OK;
+
+ if (dbus_enabled_) {
+ 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;
+ }
+ }
+
+ if (uploader_active_) {
+ upload_service_.reset(
+ new UploadService(new SystemProfileCache(), metrics_lib_, server_));
+ upload_service_->Init(upload_interval_, metrics_file_);
+ }
+
+ return EX_OK;
+}
+
+void MetricsDaemon::OnShutdown(int* return_code) {
+ if (!testing_ && dbus_enabled_ && 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::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/metricsd/metrics_daemon.h b/metricsd/metrics_daemon.h
new file mode 100644
index 0000000..6f5a3bf
--- /dev/null
+++ b/metricsd/metrics_daemon.h
@@ -0,0 +1,383 @@
+/*
+ * 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.
+ */
+
+#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 "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,
+ bool dbus_enabled,
+ MetricsLibraryInterface* metrics_lib,
+ 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();
+
+ // 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_;
+
+ // Whether or not dbus should be used.
+ // If disabled, we will not collect the frequency of crashes.
+ bool dbus_enabled_;
+
+ // 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 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/metricsd/metrics_daemon_main.cc b/metricsd/metrics_daemon_main.cc
new file mode 100644
index 0000000..c3d5cab
--- /dev/null
+++ b/metricsd/metrics_daemon_main.cc
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#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 "constants.h"
+#include "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";
+
+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");
+
+ // Enable dbus.
+ DEFINE_bool(withdbus, true, "Enable dbus");
+
+ // Upload Service flags.
+ DEFINE_int32(upload_interval_secs,
+ 1800,
+ "Interval at which metrics_daemon sends the metrics. (needs "
+ "-uploader)");
+ DEFINE_string(server,
+ metrics::kMetricsServer,
+ "Server to upload the metrics to. (needs -uploader)");
+ DEFINE_string(metrics_file,
+ metrics::kMetricsEventsFilePath,
+ "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,
+ FLAGS_withdbus,
+ &metrics_lib,
+ "/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/metricsd/metrics_daemon_test.cc b/metricsd/metrics_daemon_test.cc
new file mode 100644
index 0000000..9b5b58e
--- /dev/null
+++ b/metricsd/metrics_daemon_test.cc
@@ -0,0 +1,400 @@
+/*
+ * 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.
+ */
+
+#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_daemon.h"
+#include "metrics_library_mock.h"
+#include "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";
+
+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/metricsd/metrics_library.cc b/metricsd/metrics_library.cc
new file mode 100644
index 0000000..c1998a6
--- /dev/null
+++ b/metricsd/metrics_library.cc
@@ -0,0 +1,184 @@
+/*
+ * 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.
+ */
+
+#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 "constants.h"
+#include "serialization/metric_sample.h"
+#include "serialization/serialization_utils.h"
+
+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_(metrics::kConsentFilePath) {}
+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;
+ cached_enabled_ = stat(consent_file_.c_str(), &stat_buffer) >= 0;
+ }
+ return cached_enabled_;
+}
+
+void MetricsLibrary::Init() {
+ uma_events_file_ = metrics::kMetricsEventsFilePath;
+}
+
+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(),
+ metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendEnumToUMA(const std::string& name, int sample,
+ int max) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::LinearHistogramSample(name, sample, max).get(),
+ metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::SparseHistogramSample(name, sample).get(),
+ metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendUserActionToUMA(const std::string& action) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::UserActionSample(action).get(), metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendCrashToUMA(const char *crash_kind) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::CrashSample(crash_kind).get(), metrics::kMetricsEventsFilePath);
+}
+
+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/metricsd/metrics_library_mock.h b/metricsd/metrics_library_mock.h
new file mode 100644
index 0000000..3de87a9
--- /dev/null
+++ b/metricsd/metrics_library_mock.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/metrics_library_test.cc b/metricsd/metrics_library_test.cc
new file mode 100644
index 0000000..c58e3fb
--- /dev/null
+++ b/metricsd/metrics_library_test.cc
@@ -0,0 +1,226 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/persistent_integer.cc b/metricsd/persistent_integer.cc
new file mode 100644
index 0000000..9fa5c1e
--- /dev/null
+++ b/metricsd/persistent_integer.cc
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#include "persistent_integer.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+
+#include "constants.h"
+#include "metrics/metrics_library.h"
+
+
+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_ = metrics::kMetricsDirectory + 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/metricsd/persistent_integer.h b/metricsd/persistent_integer.h
new file mode 100644
index 0000000..fec001f
--- /dev/null
+++ b/metricsd/persistent_integer.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/persistent_integer_mock.h b/metricsd/persistent_integer_mock.h
new file mode 100644
index 0000000..acc5389
--- /dev/null
+++ b/metricsd/persistent_integer_mock.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef METRICS_PERSISTENT_INTEGER_MOCK_H_
+#define METRICS_PERSISTENT_INTEGER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "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/metricsd/persistent_integer_test.cc b/metricsd/persistent_integer_test.cc
new file mode 100644
index 0000000..19801f9
--- /dev/null
+++ b/metricsd/persistent_integer_test.cc
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <base/compiler_specific.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+
+#include "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/metricsd/serialization/metric_sample.cc b/metricsd/serialization/metric_sample.cc
new file mode 100644
index 0000000..76a47c0
--- /dev/null
+++ b/metricsd/serialization/metric_sample.cc
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#include "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/metricsd/serialization/metric_sample.h b/metricsd/serialization/metric_sample.h
new file mode 100644
index 0000000..5a4e4ae
--- /dev/null
+++ b/metricsd/serialization/metric_sample.h
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/serialization/serialization_utils.cc b/metricsd/serialization/serialization_utils.cc
new file mode 100644
index 0000000..6dd8258
--- /dev/null
+++ b/metricsd/serialization/serialization_utils.cc
@@ -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.
+ */
+
+#include "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 "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/metricsd/serialization/serialization_utils.h b/metricsd/serialization/serialization_utils.h
new file mode 100644
index 0000000..67d4675
--- /dev/null
+++ b/metricsd/serialization/serialization_utils.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/serialization/serialization_utils_unittest.cc b/metricsd/serialization/serialization_utils_unittest.cc
new file mode 100644
index 0000000..7a572de
--- /dev/null
+++ b/metricsd/serialization/serialization_utils_unittest.cc
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+#include "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 "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/metricsd/timer.cc b/metricsd/timer.cc
new file mode 100644
index 0000000..7b00cc0
--- /dev/null
+++ b/metricsd/timer.cc
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+#include "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/metricsd/timer.h b/metricsd/timer.h
new file mode 100644
index 0000000..b36ffff
--- /dev/null
+++ b/metricsd/timer.h
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+// 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/metricsd/timer_mock.h b/metricsd/timer_mock.h
new file mode 100644
index 0000000..8c9e8d8
--- /dev/null
+++ b/metricsd/timer_mock.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#ifndef METRICS_TIMER_MOCK_H_
+#define METRICS_TIMER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "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/metricsd/timer_test.cc b/metricsd/timer_test.cc
new file mode 100644
index 0000000..ab027d4
--- /dev/null
+++ b/metricsd/timer_test.cc
@@ -0,0 +1,464 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+
+#include <base/memory/scoped_ptr.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metrics_library_mock.h"
+#include "timer.h"
+#include "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/metricsd/uploader/metrics_hashes.cc b/metricsd/uploader/metrics_hashes.cc
new file mode 100644
index 0000000..208c560
--- /dev/null
+++ b/metricsd/uploader/metrics_hashes.cc
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "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/base/test_utils.h b/metricsd/uploader/metrics_hashes.h
similarity index 64%
copy from base/test_utils.h
copy to metricsd/uploader/metrics_hashes.h
index 132d3a7..1082b42 100644
--- a/base/test_utils.h
+++ b/metricsd/uploader/metrics_hashes.h
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
+#ifndef METRICS_UPLOADER_METRICS_HASHES_H_
+#define METRICS_UPLOADER_METRICS_HASHES_H_
-class TemporaryFile {
- public:
- TemporaryFile();
- ~TemporaryFile();
+#include <string>
- int fd;
- char filename[1024];
+namespace metrics {
- private:
- void init(const char* tmp_dir);
-};
+// Computes a uint64 hash of a given string based on its MD5 hash. Suitable for
+// metric names.
+uint64_t HashMetricName(const std::string& name);
-#endif // TEST_UTILS_H
+} // namespace metrics
+
+#endif // METRICS_UPLOADER_METRICS_HASHES_H_
diff --git a/metricsd/uploader/metrics_hashes_unittest.cc b/metricsd/uploader/metrics_hashes_unittest.cc
new file mode 100644
index 0000000..b8c2575
--- /dev/null
+++ b/metricsd/uploader/metrics_hashes_unittest.cc
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#include "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/metricsd/uploader/metrics_log.cc b/metricsd/uploader/metrics_log.cc
new file mode 100644
index 0000000..1f16ca1
--- /dev/null
+++ b/metricsd/uploader/metrics_log.cc
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#include "uploader/metrics_log.h"
+
+#include <string>
+
+#include "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);
+}
+
+bool MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) {
+ return profile_setter->Populate(uma_proto());
+}
diff --git a/metricsd/uploader/metrics_log.h b/metricsd/uploader/metrics_log.h
new file mode 100644
index 0000000..5e09070
--- /dev/null
+++ b/metricsd/uploader/metrics_log.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#ifndef METRICS_UPLOADER_METRICS_LOG_H_
+#define METRICS_UPLOADER_METRICS_LOG_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "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.
+ bool 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/metricsd/uploader/metrics_log_base.cc b/metricsd/uploader/metrics_log_base.cc
new file mode 100644
index 0000000..ee325ae
--- /dev/null
+++ b/metricsd/uploader/metrics_log_base.cc
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#include "uploader/metrics_log_base.h"
+
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "uploader/metrics_hashes.h"
+#include "uploader/proto/histogram_event.pb.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "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/metricsd/uploader/metrics_log_base.h b/metricsd/uploader/metrics_log_base.h
new file mode 100644
index 0000000..f4e1995
--- /dev/null
+++ b/metricsd/uploader/metrics_log_base.h
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+// 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 "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/metricsd/uploader/metrics_log_base_unittest.cc b/metricsd/uploader/metrics_log_base_unittest.cc
new file mode 100644
index 0000000..980afd5
--- /dev/null
+++ b/metricsd/uploader/metrics_log_base_unittest.cc
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#include "uploader/metrics_log_base.h"
+
+#include <string>
+
+#include <base/metrics/bucket_ranges.h>
+#include <base/metrics/sample_vector.h>
+#include <gtest/gtest.h>
+
+#include "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/metricsd/uploader/mock/mock_system_profile_setter.h b/metricsd/uploader/mock/mock_system_profile_setter.h
new file mode 100644
index 0000000..c714e9c
--- /dev/null
+++ b/metricsd/uploader/mock/mock_system_profile_setter.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/uploader/mock/sender_mock.cc b/metricsd/uploader/mock/sender_mock.cc
new file mode 100644
index 0000000..bb4dc7d
--- /dev/null
+++ b/metricsd/uploader/mock/sender_mock.cc
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#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/metricsd/uploader/mock/sender_mock.h b/metricsd/uploader/mock/sender_mock.h
new file mode 100644
index 0000000..e79233f
--- /dev/null
+++ b/metricsd/uploader/mock/sender_mock.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#ifndef METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+#define METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "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/metricsd/uploader/proto/README b/metricsd/uploader/proto/README
new file mode 100644
index 0000000..4292a40
--- /dev/null
+++ b/metricsd/uploader/proto/README
@@ -0,0 +1,37 @@
+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.
+
+
+
+
+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/metricsd/uploader/proto/chrome_user_metrics_extension.proto b/metricsd/uploader/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 0000000..a07830f
--- /dev/null
+++ b/metricsd/uploader/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+//
+// 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 "system/core/metricsd/uploader/proto/histogram_event.proto";
+import "system/core/metricsd/uploader/proto/system_profile.proto";
+import "system/core/metricsd/uploader/proto/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/metricsd/uploader/proto/histogram_event.proto b/metricsd/uploader/proto/histogram_event.proto
new file mode 100644
index 0000000..3825063
--- /dev/null
+++ b/metricsd/uploader/proto/histogram_event.proto
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+//
+// 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/metricsd/uploader/proto/system_profile.proto b/metricsd/uploader/proto/system_profile.proto
new file mode 100644
index 0000000..4cab0d9
--- /dev/null
+++ b/metricsd/uploader/proto/system_profile.proto
@@ -0,0 +1,759 @@
+/*
+ * 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.
+ */
+//
+// 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/metricsd/uploader/proto/user_action_event.proto b/metricsd/uploader/proto/user_action_event.proto
new file mode 100644
index 0000000..464f3c8
--- /dev/null
+++ b/metricsd/uploader/proto/user_action_event.proto
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+//
+// 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/base/test_utils.h b/metricsd/uploader/sender.h
similarity index 63%
copy from base/test_utils.h
copy to metricsd/uploader/sender.h
index 132d3a7..369c9c2 100644
--- a/base/test_utils.h
+++ b/metricsd/uploader/sender.h
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-#ifndef TEST_UTILS_H
-#define TEST_UTILS_H
+#ifndef METRICS_UPLOADER_SENDER_H_
+#define METRICS_UPLOADER_SENDER_H_
-class TemporaryFile {
+#include <string>
+
+// Abstract class for a Sender that uploads a metrics message.
+class Sender {
public:
- TemporaryFile();
- ~TemporaryFile();
-
- int fd;
- char filename[1024];
-
- private:
- void init(const char* tmp_dir);
+ virtual ~Sender() {}
+ // Sends a message |content| with its sha1 hash |hash|
+ virtual bool Send(const std::string& content, const std::string& hash) = 0;
};
-#endif // TEST_UTILS_H
+#endif // METRICS_UPLOADER_SENDER_H_
diff --git a/metricsd/uploader/sender_http.cc b/metricsd/uploader/sender_http.cc
new file mode 100644
index 0000000..953afc1
--- /dev/null
+++ b/metricsd/uploader/sender_http.cc
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#include "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/metricsd/uploader/sender_http.h b/metricsd/uploader/sender_http.h
new file mode 100644
index 0000000..6249d90
--- /dev/null
+++ b/metricsd/uploader/sender_http.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef METRICS_UPLOADER_SENDER_HTTP_H_
+#define METRICS_UPLOADER_SENDER_HTTP_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "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/metricsd/uploader/system_profile_cache.cc b/metricsd/uploader/system_profile_cache.cc
new file mode 100644
index 0000000..7dd0323
--- /dev/null
+++ b/metricsd/uploader/system_profile_cache.cc
@@ -0,0 +1,167 @@
+/*
+ * 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.
+ */
+
+#include "uploader/system_profile_cache.h"
+
+#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 <string>
+#include <vector>
+
+#include "constants.h"
+#include "persistent_integer.h"
+#include "uploader/metrics_log_base.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace {
+
+const char kPersistentSessionIdFilename[] = "Sysinfo.SessionId";
+
+} // 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.";
+
+ if (!base::SysInfo::GetLsbReleaseValue("BRILLO_BUILD_TARGET_ID",
+ &profile_.build_target_id)) {
+ LOG(ERROR) << "BRILLO_BUILD_TARGET_ID is not set in /etc/lsb-release.";
+ return false;
+ }
+
+ std::string channel;
+ if (!base::SysInfo::GetLsbReleaseValue("BRILLO_CHANNEL", &channel) ||
+ !base::SysInfo::GetLsbReleaseValue("BRILLO_VERSION", &profile_.version)) {
+ // If the channel or version is missing, the image is not official.
+ // In this case, set the channel to unknown and the version to 0.0.0.0 to
+ // avoid polluting the production data.
+ channel = "";
+ profile_.version = metrics::kDefaultVersion;
+
+ }
+ profile_.client_id =
+ testing_ ? "client_id_test" :
+ GetPersistentGUID(metrics::kMetricsGUIDFilePath);
+ profile_.hardware_class = "unknown";
+ profile_.channel = ProtoChannelFromString(channel);
+
+ // 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.
+ session_id_->Add(1);
+ profile_.session_id = static_cast<int32_t>(session_id_->Get());
+
+ initialized_ = true;
+ return initialized_;
+}
+
+bool SystemProfileCache::InitializeOrCheck() {
+ return initialized_ || Initialize();
+}
+
+bool SystemProfileCache::Populate(
+ metrics::ChromeUserMetricsExtension* metrics_proto) {
+ CHECK(metrics_proto);
+ if (not InitializeOrCheck()) {
+ return false;
+ }
+
+ // 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(9);
+
+ 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_.version);
+ profile_proto->set_channel(profile_.channel);
+ metrics::SystemProfileProto_BrilloDeviceData* device_data =
+ profile_proto->mutable_brillo();
+ device_data->set_build_target_id(profile_.build_target_id);
+
+ return true;
+}
+
+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;
+}
+
+metrics::SystemProfileProto_Channel SystemProfileCache::ProtoChannelFromString(
+ const std::string& channel) {
+ if (channel == "stable") {
+ return metrics::SystemProfileProto::CHANNEL_STABLE;
+ } else if (channel == "dev") {
+ return metrics::SystemProfileProto::CHANNEL_DEV;
+ } else if (channel == "beta") {
+ return metrics::SystemProfileProto::CHANNEL_BETA;
+ } else if (channel == "canary") {
+ return metrics::SystemProfileProto::CHANNEL_CANARY;
+ }
+
+ DLOG(INFO) << "unknown channel: " << channel;
+ return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
+}
diff --git a/metricsd/uploader/system_profile_cache.h b/metricsd/uploader/system_profile_cache.h
new file mode 100644
index 0000000..ac80b47
--- /dev/null
+++ b/metricsd/uploader/system_profile_cache.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#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 "persistent_integer.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+struct SystemProfile {
+ std::string version;
+ std::string hardware_class;
+ std::string client_id;
+ int session_id;
+ metrics::SystemProfileProto::Channel channel;
+ std::string build_target_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.
+ bool Populate(metrics::ChromeUserMetricsExtension* metrics_proto) override;
+
+ // Converts a string representation of the 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();
+
+ 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/metricsd/uploader/system_profile_setter.h b/metricsd/uploader/system_profile_setter.h
new file mode 100644
index 0000000..bd3ff42
--- /dev/null
+++ b/metricsd/uploader/system_profile_setter.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#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 bool Populate(metrics::ChromeUserMetricsExtension* profile_proto) = 0;
+};
+
+#endif // METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
diff --git a/metricsd/uploader/upload_service.cc b/metricsd/uploader/upload_service.cc
new file mode 100644
index 0000000..2335630
--- /dev/null
+++ b/metricsd/uploader/upload_service.cc
@@ -0,0 +1,239 @@
+/*
+ * 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.
+ */
+
+#include "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 "serialization/metric_sample.h"
+#include "serialization/serialization_utils.h"
+#include "uploader/metrics_log.h"
+#include "uploader/sender_http.h"
+#include "uploader/system_profile_setter.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();
+ 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();
+ StageCurrentLog();
+
+ // If a log is available for upload, upload it.
+ if (staged_log_) {
+ 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();
+ if (!staged_log_->PopulateSystemProfile(system_profile_setter_.get())) {
+ LOG(WARNING) << "Error while adding metadata to the log. Discarding the "
+ << "log.";
+ staged_log_.reset();
+ }
+ failed_upload_count_ = 0;
+}
+
+MetricsLog* UploadService::GetOrCreateCurrentLog() {
+ if (!current_log_) {
+ StartNewLog();
+ }
+ return current_log_.get();
+}
diff --git a/metricsd/uploader/upload_service.h b/metricsd/uploader/upload_service.h
new file mode 100644
index 0000000..7f2f413
--- /dev/null
+++ b/metricsd/uploader/upload_service.h
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+
+#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 "uploader/metrics_log.h"
+#include "uploader/sender.h"
+#include "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/metricsd/uploader/upload_service_test.cc b/metricsd/uploader/upload_service_test.cc
new file mode 100644
index 0000000..cbb5277
--- /dev/null
+++ b/metricsd/uploader/upload_service_test.cc
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+
+#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_library_mock.h"
+#include "serialization/metric_sample.h"
+#include "uploader/metrics_log.h"
+#include "uploader/mock/mock_system_profile_setter.h"
+#include "uploader/mock/sender_mock.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/proto/histogram_event.pb.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_cache.h"
+#include "uploader/upload_service.h"
+
+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/mkbootimg/Android.mk b/mkbootimg/Android.mk
index 0c9b0c6..8661d7d 100644
--- a/mkbootimg/Android.mk
+++ b/mkbootimg/Android.mk
@@ -2,12 +2,10 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := mkbootimg.c
-LOCAL_STATIC_LIBRARIES := libmincrypt
-LOCAL_CFLAGS := -Werror
+LOCAL_SRC_FILES := mkbootimg
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE := mkbootimg
-include $(BUILD_HOST_EXECUTABLE)
-
-$(call dist-for-goals,dist_files,$(LOCAL_BUILT_MODULE))
+include $(BUILD_PREBUILT)
diff --git a/mkbootimg/mkbootimg b/mkbootimg/mkbootimg
new file mode 100755
index 0000000..aea2585
--- /dev/null
+++ b/mkbootimg/mkbootimg
@@ -0,0 +1,140 @@
+#!/usr/bin/env python
+# Copyright 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
+from sys import argv, exit, stderr
+from argparse import ArgumentParser, FileType, Action
+from os import fstat
+from struct import pack
+from hashlib import sha1
+
+def filesize(f):
+ if f is None:
+ return 0
+ try:
+ return fstat(f.fileno()).st_size
+ except OSError:
+ return 0
+
+
+def update_sha(sha, f):
+ if f:
+ sha.update(f.read())
+ f.seek(0)
+ sha.update(pack('I', filesize(f)))
+ else:
+ sha.update(pack('I', 0))
+
+
+def pad_file(f, padding):
+ pad = (padding - (f.tell() & (padding - 1))) & (padding - 1)
+ f.write(pack(str(pad) + 'x'))
+
+
+def write_header(args):
+ BOOT_MAGIC = 'ANDROID!'.encode()
+ args.output.write(pack('8s', BOOT_MAGIC))
+ args.output.write(pack('8I',
+ filesize(args.kernel), # size in bytes
+ args.base + args.kernel_offset, # physical load addr
+ filesize(args.ramdisk), # size in bytes
+ args.base + args.ramdisk_offset, # physical load addr
+ filesize(args.second), # size in bytes
+ args.base + args.second_offset, # physical load addr
+ args.base + args.tags_offset, # physical addr for kernel tags
+ args.pagesize)) # flash page size we assume
+ args.output.write(pack('8x')) # future expansion: should be 0
+ args.output.write(pack('16s', args.board.encode())) # asciiz product name
+ args.output.write(pack('512s', args.cmdline[:512].encode()))
+
+ sha = sha1()
+ update_sha(sha, args.kernel)
+ update_sha(sha, args.ramdisk)
+ update_sha(sha, args.second)
+ img_id = pack('32s', sha.digest())
+
+ args.output.write(img_id)
+ args.output.write(pack('1024s', args.cmdline[512:].encode()))
+ pad_file(args.output, args.pagesize)
+ return img_id
+
+
+class ValidateStrLenAction(Action):
+ def __init__(self, option_strings, dest, nargs=None, **kwargs):
+ if 'maxlen' not in kwargs:
+ raise ValueError('maxlen must be set')
+ self.maxlen = int(kwargs['maxlen'])
+ del kwargs['maxlen']
+ super(ValidateStrLenAction, self).__init__(option_strings, dest, **kwargs)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ if len(values) > self.maxlen:
+ raise ValueError('String argument too long: max {0:d}, got {1:d}'.
+ format(self.maxlen, len(values)))
+ setattr(namespace, self.dest, values)
+
+
+def write_padded_file(f_out, f_in, padding):
+ if f_in is None:
+ return
+ f_out.write(f_in.read())
+ pad_file(f_out, padding)
+
+
+def parse_int(x):
+ return int(x, 0)
+
+
+def parse_cmdline():
+ parser = ArgumentParser()
+ parser.add_argument('--kernel', help='path to the kernel', type=FileType('rb'),
+ required=True)
+ parser.add_argument('--ramdisk', help='path to the ramdisk', type=FileType('rb'))
+ parser.add_argument('--second', help='path to the 2nd bootloader', type=FileType('rb'))
+ parser.add_argument('--cmdline', help='extra arguments to be passed on the '
+ 'kernel command line', default='', action=ValidateStrLenAction, maxlen=1536)
+ parser.add_argument('--base', help='base address', type=parse_int, default=0x10000000)
+ parser.add_argument('--kernel_offset', help='kernel offset', type=parse_int, default=0x00008000)
+ parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
+ parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
+ default=0x00f00000)
+ parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
+ parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
+ maxlen=16)
+ parser.add_argument('--pagesize', help='page size', type=parse_int,
+ choices=[2**i for i in range(11,15)], default=2048)
+ parser.add_argument('--id', help='print the image ID on standard output',
+ action='store_true')
+ parser.add_argument('-o', '--output', help='output file name', type=FileType('wb'),
+ required=True)
+ return parser.parse_args()
+
+
+def write_data(args):
+ write_padded_file(args.output, args.kernel, args.pagesize)
+ write_padded_file(args.output, args.ramdisk, args.pagesize)
+ write_padded_file(args.output, args.second, args.pagesize)
+
+
+def main():
+ args = parse_cmdline()
+ img_id = write_header(args)
+ write_data(args)
+ if args.id:
+ print('0x' + ''.join('{:02x}'.format(ord(c)) for c in img_id))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/mkbootimg/mkbootimg.c b/mkbootimg/mkbootimg.c
deleted file mode 100644
index 40e5261..0000000
--- a/mkbootimg/mkbootimg.c
+++ /dev/null
@@ -1,297 +0,0 @@
-/* tools/mkbootimg/mkbootimg.c
-**
-** Copyright 2007, 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdbool.h>
-
-#include "mincrypt/sha.h"
-#include "bootimg.h"
-
-static void *load_file(const char *fn, unsigned *_sz)
-{
- char *data;
- int sz;
- int fd;
-
- data = 0;
- fd = open(fn, O_RDONLY);
- if(fd < 0) return 0;
-
- sz = lseek(fd, 0, SEEK_END);
- if(sz < 0) goto oops;
-
- if(lseek(fd, 0, SEEK_SET) != 0) goto oops;
-
- data = (char*) malloc(sz);
- if(data == 0) goto oops;
-
- if(read(fd, data, sz) != sz) goto oops;
- close(fd);
-
- if(_sz) *_sz = sz;
- return data;
-
-oops:
- close(fd);
- if(data != 0) free(data);
- return 0;
-}
-
-int usage(void)
-{
- fprintf(stderr,"usage: mkbootimg\n"
- " --kernel <filename>\n"
- " --ramdisk <filename>\n"
- " [ --second <2ndbootloader-filename> ]\n"
- " [ --cmdline <kernel-commandline> ]\n"
- " [ --board <boardname> ]\n"
- " [ --base <address> ]\n"
- " [ --pagesize <pagesize> ]\n"
- " [ --id ]\n"
- " -o|--output <filename>\n"
- );
- return 1;
-}
-
-
-
-static unsigned char padding[16384] = { 0, };
-
-static void print_id(const uint8_t *id, size_t id_len) {
- printf("0x");
- for (unsigned i = 0; i < id_len; i++) {
- printf("%02x", id[i]);
- }
- printf("\n");
-}
-
-int write_padding(int fd, unsigned pagesize, unsigned itemsize)
-{
- unsigned pagemask = pagesize - 1;
- ssize_t count;
-
- if((itemsize & pagemask) == 0) {
- return 0;
- }
-
- count = pagesize - (itemsize & pagemask);
-
- if(write(fd, padding, count) != count) {
- return -1;
- } else {
- return 0;
- }
-}
-
-int main(int argc, char **argv)
-{
- boot_img_hdr hdr;
-
- char *kernel_fn = NULL;
- void *kernel_data = NULL;
- char *ramdisk_fn = NULL;
- void *ramdisk_data = NULL;
- char *second_fn = NULL;
- void *second_data = NULL;
- char *cmdline = "";
- char *bootimg = NULL;
- char *board = "";
- uint32_t pagesize = 2048;
- int fd;
- SHA_CTX ctx;
- const uint8_t* sha;
- uint32_t base = 0x10000000U;
- uint32_t kernel_offset = 0x00008000U;
- uint32_t ramdisk_offset = 0x01000000U;
- uint32_t second_offset = 0x00f00000U;
- uint32_t tags_offset = 0x00000100U;
- size_t cmdlen;
-
- argc--;
- argv++;
-
- memset(&hdr, 0, sizeof(hdr));
-
- bool get_id = false;
- while(argc > 0){
- char *arg = argv[0];
- if (!strcmp(arg, "--id")) {
- get_id = true;
- argc -= 1;
- argv += 1;
- } else if(argc >= 2) {
- char *val = argv[1];
- argc -= 2;
- argv += 2;
- if(!strcmp(arg, "--output") || !strcmp(arg, "-o")) {
- bootimg = val;
- } else if(!strcmp(arg, "--kernel")) {
- kernel_fn = val;
- } else if(!strcmp(arg, "--ramdisk")) {
- ramdisk_fn = val;
- } else if(!strcmp(arg, "--second")) {
- second_fn = val;
- } else if(!strcmp(arg, "--cmdline")) {
- cmdline = val;
- } else if(!strcmp(arg, "--base")) {
- base = strtoul(val, 0, 16);
- } else if(!strcmp(arg, "--kernel_offset")) {
- kernel_offset = strtoul(val, 0, 16);
- } else if(!strcmp(arg, "--ramdisk_offset")) {
- ramdisk_offset = strtoul(val, 0, 16);
- } else if(!strcmp(arg, "--second_offset")) {
- second_offset = strtoul(val, 0, 16);
- } else if(!strcmp(arg, "--tags_offset")) {
- tags_offset = strtoul(val, 0, 16);
- } else if(!strcmp(arg, "--board")) {
- board = val;
- } else if(!strcmp(arg,"--pagesize")) {
- pagesize = strtoul(val, 0, 10);
- if ((pagesize != 2048) && (pagesize != 4096)
- && (pagesize != 8192) && (pagesize != 16384)) {
- fprintf(stderr,"error: unsupported page size %d\n", pagesize);
- return -1;
- }
- } else {
- return usage();
- }
- } else {
- return usage();
- }
- }
- hdr.page_size = pagesize;
-
- hdr.kernel_addr = base + kernel_offset;
- hdr.ramdisk_addr = base + ramdisk_offset;
- hdr.second_addr = base + second_offset;
- hdr.tags_addr = base + tags_offset;
-
- if(bootimg == 0) {
- fprintf(stderr,"error: no output filename specified\n");
- return usage();
- }
-
- if(kernel_fn == 0) {
- fprintf(stderr,"error: no kernel image specified\n");
- return usage();
- }
-
- if(ramdisk_fn == 0) {
- fprintf(stderr,"error: no ramdisk image specified\n");
- return usage();
- }
-
- if(strlen(board) >= BOOT_NAME_SIZE) {
- fprintf(stderr,"error: board name too large\n");
- return usage();
- }
-
- strcpy((char *) hdr.name, board);
-
- memcpy(hdr.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE);
-
- cmdlen = strlen(cmdline);
- if(cmdlen > (BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE - 2)) {
- fprintf(stderr,"error: kernel commandline too large\n");
- return 1;
- }
- /* Even if we need to use the supplemental field, ensure we
- * are still NULL-terminated */
- strncpy((char *)hdr.cmdline, cmdline, BOOT_ARGS_SIZE - 1);
- hdr.cmdline[BOOT_ARGS_SIZE - 1] = '\0';
- if (cmdlen >= (BOOT_ARGS_SIZE - 1)) {
- cmdline += (BOOT_ARGS_SIZE - 1);
- strncpy((char *)hdr.extra_cmdline, cmdline, BOOT_EXTRA_ARGS_SIZE);
- }
-
- kernel_data = load_file(kernel_fn, &hdr.kernel_size);
- if(kernel_data == 0) {
- fprintf(stderr,"error: could not load kernel '%s'\n", kernel_fn);
- return 1;
- }
-
- if(!strcmp(ramdisk_fn,"NONE")) {
- ramdisk_data = 0;
- hdr.ramdisk_size = 0;
- } else {
- ramdisk_data = load_file(ramdisk_fn, &hdr.ramdisk_size);
- if(ramdisk_data == 0) {
- fprintf(stderr,"error: could not load ramdisk '%s'\n", ramdisk_fn);
- return 1;
- }
- }
-
- if(second_fn) {
- second_data = load_file(second_fn, &hdr.second_size);
- if(second_data == 0) {
- fprintf(stderr,"error: could not load secondstage '%s'\n", second_fn);
- return 1;
- }
- }
-
- /* put a hash of the contents in the header so boot images can be
- * differentiated based on their first 2k.
- */
- SHA_init(&ctx);
- SHA_update(&ctx, kernel_data, hdr.kernel_size);
- SHA_update(&ctx, &hdr.kernel_size, sizeof(hdr.kernel_size));
- SHA_update(&ctx, ramdisk_data, hdr.ramdisk_size);
- SHA_update(&ctx, &hdr.ramdisk_size, sizeof(hdr.ramdisk_size));
- SHA_update(&ctx, second_data, hdr.second_size);
- SHA_update(&ctx, &hdr.second_size, sizeof(hdr.second_size));
- sha = SHA_final(&ctx);
- memcpy(hdr.id, sha,
- SHA_DIGEST_SIZE > sizeof(hdr.id) ? sizeof(hdr.id) : SHA_DIGEST_SIZE);
-
- fd = open(bootimg, O_CREAT | O_TRUNC | O_WRONLY, 0644);
- if(fd < 0) {
- fprintf(stderr,"error: could not create '%s'\n", bootimg);
- return 1;
- }
-
- if(write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) goto fail;
- if(write_padding(fd, pagesize, sizeof(hdr))) goto fail;
-
- if(write(fd, kernel_data, hdr.kernel_size) != (ssize_t) hdr.kernel_size) goto fail;
- if(write_padding(fd, pagesize, hdr.kernel_size)) goto fail;
-
- if(write(fd, ramdisk_data, hdr.ramdisk_size) != (ssize_t) hdr.ramdisk_size) goto fail;
- if(write_padding(fd, pagesize, hdr.ramdisk_size)) goto fail;
-
- if(second_data) {
- if(write(fd, second_data, hdr.second_size) != (ssize_t) hdr.second_size) goto fail;
- if(write_padding(fd, pagesize, hdr.second_size)) goto fail;
- }
-
- if (get_id) {
- print_id((uint8_t *) hdr.id, sizeof(hdr.id));
- }
-
- return 0;
-
-fail:
- unlink(bootimg);
- close(fd);
- fprintf(stderr,"error: failed writing '%s': %s\n", bootimg,
- strerror(errno));
- return 1;
-}
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 7ab76b8..e1ba2e9 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -13,6 +13,20 @@
include $(BUILD_PREBUILT)
endif
+
+#######################################
+# asan.options
+ifeq (address,$(strip $(SANITIZE_TARGET)))
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := asan.options
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+LOCAL_MODULE_PATH := $(TARGET_OUT)
+
+include $(BUILD_PREBUILT)
+endif
+
#######################################
# init.environ.rc
@@ -21,12 +35,26 @@
LOCAL_MODULE := init.environ.rc
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+EXPORT_GLOBAL_ASAN_OPTIONS :=
+ifeq (address,$(strip $(SANITIZE_TARGET)))
+ EXPORT_GLOBAL_ASAN_OPTIONS := export ASAN_OPTIONS include=/system/asan.options
+ LOCAL_REQUIRED_MODULES := asan.options
+endif
# Put it here instead of in init.rc module definition,
# because init.rc is conditionally included.
#
-# create some directories (some are mount points)
-LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
- sbin dev proc sys system data oem)
+# create some directories (some are mount points) and symlinks
+local_post_install_cmd_base := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
+ sbin dev proc sys system data oem acct cache config storage mnt root); \
+ ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
+ ln -sf /sys/kernel/debug $(TARGET_ROOT_OUT)/d; \
+ ln -sf /storage/self/primary $(TARGET_ROOT_OUT)/sdcard
+ifdef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE
+ LOCAL_POST_INSTALL_CMD := $(local_post_install_cmd_base); mkdir -p $(TARGET_ROOT_OUT)/vendor
+else
+ LOCAL_POST_INSTALL_CMD := $(local_post_install_cmd_base)
+endif
+local_post_install_cmd_base :=
include $(BUILD_SYSTEM)/base_rules.mk
@@ -41,6 +69,7 @@
@mkdir -p $(dir $@)
$(hide) sed -e 's?%BOOTCLASSPATH%?$(PRODUCT_BOOTCLASSPATH)?g' $< >$@
$(hide) sed -i -e 's?%SYSTEMSERVERCLASSPATH%?$(PRODUCT_SYSTEM_SERVER_CLASSPATH)?g' $@
+ $(hide) sed -i -e 's?%EXPORT_GLOBAL_ASAN_OPTIONS%?$(EXPORT_GLOBAL_ASAN_OPTIONS)?g' $@
bcp_md5 :=
bcp_dep :=
diff --git a/rootdir/asan.options b/rootdir/asan.options
new file mode 100644
index 0000000..2f12341
--- /dev/null
+++ b/rootdir/asan.options
@@ -0,0 +1 @@
+allow_user_segv_handler=1:detect_odr_violation=0:alloc_dealloc_mismatch=0:allocator_may_return_null=1
diff --git a/rootdir/init.environ.rc.in b/rootdir/init.environ.rc.in
index 0064790..e8b46eb 100644
--- a/rootdir/init.environ.rc.in
+++ b/rootdir/init.environ.rc.in
@@ -9,3 +9,4 @@
export LOOP_MOUNTPOINT /mnt/obb
export BOOTCLASSPATH %BOOTCLASSPATH%
export SYSTEMSERVERCLASSPATH %SYSTEMSERVERCLASSPATH%
+ %EXPORT_GLOBAL_ASAN_OPTIONS%
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 0966038..cf16f01 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -17,11 +17,11 @@
# Set the security context of /adb_keys if present.
restorecon /adb_keys
- start ueventd
-
- # create mountpoints
+ # Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
mkdir /mnt 0775 root system
+ start ueventd
+
on init
sysclktz 0
@@ -32,8 +32,7 @@
# Link /vendor to /system/vendor for devices without a vendor partition.
symlink /system/vendor /vendor
- # Create cgroup mount point for cpu accounting
- mkdir /acct
+ # Mount cgroup mount point for cpu accounting
mount cgroup none /acct cpuacct
mkdir /acct/uid
@@ -50,11 +49,6 @@
chown root system /sys/fs/cgroup/memory/sw/tasks
chmod 0660 /sys/fs/cgroup/memory/sw/tasks
- mkdir /system
- mkdir /data 0771 system system
- mkdir /cache 0770 system cache
- mkdir /config 0500 root root
-
# See storage config details at http://source.android.com/tech/storage/
mkdir /mnt/shell 0700 shell shell
mkdir /mnt/media_rw 0700 media_rw media_rw
@@ -194,9 +188,9 @@
# We restorecon /cache in case the cache partition has been reset.
restorecon_recursive /cache
- # This may have been created by the recovery system with odd permissions
- chown system cache /cache/recovery
- chmod 0770 /cache/recovery
+ # Create /cache/recovery in case it's not there. It'll also fix the odd
+ # permissions if created by the recovery system.
+ mkdir /cache/recovery 0770 system cache
#change permissions on vmallocinfo so we can grab it from bugreports
chown root log /proc/vmallocinfo
@@ -241,7 +235,6 @@
# create basic filesystem structure
mkdir /data/misc 01771 system misc
- mkdir /data/misc/adb 02750 system shell
mkdir /data/misc/bluedroid 0770 bluetooth net_bt_stack
mkdir /data/misc/bluetooth 0770 system system
mkdir /data/misc/keystore 0700 keystore keystore
@@ -265,6 +258,7 @@
chmod 0660 /data/misc/wifi/wpa_supplicant.conf
mkdir /data/local 0751 root root
mkdir /data/misc/media 0700 media media
+ mkdir /data/misc/boottrace 0771 system shell
# For security reasons, /data/local/tmp should always be empty.
# Do not place files or directories in /data/local/tmp
@@ -297,7 +291,7 @@
# the following directory.
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
@@ -321,7 +315,7 @@
restorecon_recursive /data
# Check any timezone data in /data is newer than the copy in /system, delete if not.
- exec u:r:tzdatacheck:s0 system system -- /system/bin/tzdatacheck /system/usr/share/zoneinfo /data/misc/zoneinfo
+ exec - system system -- /system/bin/tzdatacheck /system/usr/share/zoneinfo /data/misc/zoneinfo
# If there is no fs-post-data action in the init.<device>.rc file, you
# must uncomment this line, otherwise encrypted filesystems
@@ -481,16 +475,6 @@
critical
seclabel u:r:ueventd:s0
-service logd /system/bin/logd
- class core
- socket logd stream 0666 logd logd
- socket logdr seqpacket 0666 logd logd
- socket logdw dgram 0222 logd logd
-
-service logd-reinit /system/bin/logd --reinit
- oneshot
- disabled
-
service healthd /sbin/healthd
class core
critical
@@ -507,145 +491,16 @@
on property:ro.debuggable=1
start console
-# adbd is controlled via property triggers in init.<platform>.usb.rc
-service adbd /sbin/adbd --root_seclabel=u:r:su:s0
- class core
- socket adbd stream 660 system system
- disabled
- seclabel u:r:adbd:s0
-
-# adbd on at boot in emulator
-on property:ro.kernel.qemu=1
- start adbd
-
-service lmkd /system/bin/lmkd
- class core
- critical
- socket lmkd seqpacket 0660 system system
-
-service servicemanager /system/bin/servicemanager
- class core
- user system
- group system
- critical
- onrestart restart healthd
- onrestart restart zygote
- onrestart restart media
- onrestart restart surfaceflinger
- onrestart restart drm
-
-service vold /system/bin/vold
- class core
- socket vold stream 0660 root mount
- ioprio be 2
-
-service netd /system/bin/netd
- class main
- socket netd stream 0660 root system
- socket dnsproxyd stream 0660 root inet
- socket mdns stream 0660 root system
- socket fwmarkd stream 0660 root inet
-
-service debuggerd /system/bin/debuggerd
- class main
-
-service debuggerd64 /system/bin/debuggerd64
- class main
-
-service ril-daemon /system/bin/rild
- class main
- socket rild stream 660 root radio
- socket rild-debug stream 660 radio system
- user root
- group radio cache inet misc audio log
-
-service surfaceflinger /system/bin/surfaceflinger
- class core
- user system
- group graphics drmrpc
- onrestart restart zygote
-
-service drm /system/bin/drmserver
- class main
- user drm
- group drm system inet drmrpc
-
-service media /system/bin/mediaserver
- class main
- user media
- group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm
- ioprio rt 4
-
-# One shot invocation to deal with encrypted volume.
-service defaultcrypto /system/bin/vdc --wait cryptfs mountdefaultencrypted
- disabled
- oneshot
- # vold will set vold.decrypt to trigger_restart_framework (default
- # encryption) or trigger_restart_min_framework (other encryption)
-
-# One shot invocation to encrypt unencrypted volumes
-service encrypt /system/bin/vdc --wait cryptfs enablecrypto inplace default
- disabled
- oneshot
- # vold will set vold.decrypt to trigger_restart_framework (default
- # encryption)
-
-service bootanim /system/bin/bootanimation
- class core
- user graphics
- group graphics audio
- disabled
- oneshot
-
-service installd /system/bin/installd
- class main
- socket installd stream 600 system system
-
service flash_recovery /system/bin/install-recovery.sh
class main
oneshot
-service racoon /system/bin/racoon
- class main
- socket racoon stream 600 system system
- # IKE uses UDP port 500. Racoon will setuid to vpn after binding the port.
- group vpn net_admin inet
- disabled
- oneshot
-
-service mtpd /system/bin/mtpd
- class main
- socket mtpd stream 600 system system
- user vpn
- group vpn net_admin inet net_raw
- disabled
- oneshot
-
-service keystore /system/bin/keystore /data/misc/keystore
- class main
- user keystore
- group keystore drmrpc
-
-service dumpstate /system/bin/dumpstate -s
- class main
- socket dumpstate stream 0660 shell log
- disabled
- oneshot
-
-service mdnsd /system/bin/mdnsd
- class main
- user mdnsr
- group inet net_raw
- socket mdnsd stream 0660 mdnsr inet
- disabled
- oneshot
-
-service pre-recovery /system/bin/uncrypt
+service uncrypt /system/bin/uncrypt
class main
disabled
oneshot
-service perfprofd /system/xbin/perfprofd
- class late_start
- user root
+service pre-recovery /system/bin/uncrypt --reboot
+ class main
+ disabled
oneshot
diff --git a/rootdir/init.trace.rc b/rootdir/init.trace.rc
index cd8d350..cde9c37 100644
--- a/rootdir/init.trace.rc
+++ b/rootdir/init.trace.rc
@@ -1,6 +1,6 @@
## Permissions to allow system-wide tracing to the kernel trace buffer.
##
-on early-boot
+on boot
# Allow writing to the kernel trace log.
chmod 0222 /sys/kernel/debug/tracing/trace_marker
@@ -16,6 +16,16 @@
chown root shell /sys/kernel/debug/tracing/events/power/cpu_idle/enable
chown root shell /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
chown root shell /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
+ chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
+ chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
+ chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
+ chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
+ chown root shell /sys/kernel/debug/tracing/events/binder/binder_transaction/enable
+ chown root shell /sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable
+ chown root shell /sys/kernel/debug/tracing/events/binder/binder_lock/enable
+ chown root shell /sys/kernel/debug/tracing/events/binder/binder_locked/enable
+ chown root shell /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
+
chown root shell /sys/kernel/debug/tracing/tracing_on
chmod 0664 /sys/kernel/debug/tracing/trace_clock
@@ -28,8 +38,25 @@
chmod 0664 /sys/kernel/debug/tracing/events/power/cpu_idle/enable
chmod 0664 /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
chmod 0664 /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
chmod 0664 /sys/kernel/debug/tracing/tracing_on
+ chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_transaction/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_lock/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_locked/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
# Allow only the shell group to read and truncate the kernel trace.
chown root shell /sys/kernel/debug/tracing/trace
chmod 0660 /sys/kernel/debug/tracing/trace
+
+on property:persist.debug.atrace.boottrace=1
+ start boottrace
+
+# Run atrace with the categories written in a file
+service boottrace /system/bin/atrace --async_start -f /data/misc/boottrace/categories
+ disabled
+ oneshot
diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c
index 893c0dc..5b657bb 100644
--- a/sdcard/sdcard.c
+++ b/sdcard/sdcard.c
@@ -264,7 +264,7 @@
* buffer at the same time. This allows us to share the underlying storage. */
union {
__u8 request_buffer[MAX_REQUEST_SIZE];
- __u8 read_buffer[MAX_READ + PAGESIZE];
+ __u8 read_buffer[MAX_READ + PAGE_SIZE];
};
};
@@ -1282,7 +1282,7 @@
__u32 size = req->size;
__u64 offset = req->offset;
int res;
- __u8 *read_buffer = (__u8 *) ((uintptr_t)(handler->read_buffer + PAGESIZE) & ~((uintptr_t)PAGESIZE-1));
+ __u8 *read_buffer = (__u8 *) ((uintptr_t)(handler->read_buffer + PAGE_SIZE) & ~((uintptr_t)PAGE_SIZE-1));
/* Don't access any other fields of hdr or req beyond this point, the read buffer
* overlaps the request buffer and will clobber data in the request. This
@@ -1308,7 +1308,7 @@
struct fuse_write_out out;
struct handle *h = id_to_ptr(req->fh);
int res;
- __u8 aligned_buffer[req->size] __attribute__((__aligned__(PAGESIZE)));
+ __u8 aligned_buffer[req->size] __attribute__((__aligned__(PAGE_SIZE)));
if (req->flags & O_DIRECT) {
memcpy(aligned_buffer, buffer, req->size);
diff --git a/toolbox/Android.mk b/toolbox/Android.mk
index ad99a39..2ae7ed2 100644
--- a/toolbox/Android.mk
+++ b/toolbox/Android.mk
@@ -2,8 +2,7 @@
common_cflags := \
- -std=gnu99 \
- -Werror -Wno-unused-parameter \
+ -Werror -Wno-unused-parameter -Wno-unused-const-variable \
-I$(LOCAL_PATH)/upstream-netbsd/include/ \
-include bsd-compatibility.h \
@@ -43,30 +42,28 @@
getevent \
iftop \
ioctl \
- ionice \
log \
ls \
lsof \
- mount \
nandread \
newfs_msdos \
ps \
prlimit \
- renice \
sendevent \
start \
stop \
top \
uptime \
- watchprops \
ALL_TOOLS = $(BSD_TOOLS) $(OUR_TOOLS)
LOCAL_SRC_FILES := \
+ start_stop.cpp \
toolbox.c \
$(patsubst %,%.c,$(OUR_TOOLS)) \
LOCAL_CFLAGS += $(common_cflags)
+LOCAL_CONLYFLAGS += -std=gnu99
LOCAL_SHARED_LIBRARIES := \
libcutils \
diff --git a/toolbox/ionice.c b/toolbox/ionice.c
deleted file mode 100644
index 7abc261..0000000
--- a/toolbox/ionice.c
+++ /dev/null
@@ -1,58 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <errno.h>
-
-#include <cutils/iosched_policy.h>
-
-static char *classes[] = {"none", "rt", "be", "idle", NULL};
-
-int ionice_main(int argc, char *argv[])
-{
- IoSchedClass clazz = IoSchedClass_NONE;
- int ioprio = 0;
- int pid;
-
- if(argc != 2 && argc != 4) {
- fprintf(stderr, "usage: ionice <pid> [none|rt|be|idle] [prio]\n");
- return 1;
- }
-
- if (!(pid = atoi(argv[1]))) {
- fprintf(stderr, "Invalid pid specified\n");
- return 1;
- }
-
- if (argc == 2) {
- if (android_get_ioprio(pid, &clazz, &ioprio)) {
- fprintf(stderr, "Failed to read priority (%s)\n", strerror(errno));
- return 1;
- }
- fprintf(stdout, "Pid %d, class %s (%d), prio %d\n", pid, classes[clazz], clazz, ioprio);
- return 0;
- }
-
- if (!strcmp(argv[2], "none")) {
- clazz = IoSchedClass_NONE;
- } else if (!strcmp(argv[2], "rt")) {
- clazz = IoSchedClass_RT;
- } else if (!strcmp(argv[2], "be")) {
- clazz = IoSchedClass_BE;
- } else if (!strcmp(argv[2], "idle")) {
- clazz = IoSchedClass_IDLE;
- } else {
- fprintf(stderr, "Unsupported class '%s'\n", argv[2]);
- return 1;
- }
-
- ioprio = atoi(argv[3]);
-
- printf("Setting pid %d i/o class to %d, prio %d\n", pid, clazz, ioprio);
- if (android_set_ioprio(pid, clazz, ioprio)) {
- fprintf(stderr, "Failed to set priority (%s)\n", strerror(errno));
- return 1;
- }
-
- return 0;
-}
diff --git a/toolbox/mount.c b/toolbox/mount.c
deleted file mode 100644
index 66ae8b1..0000000
--- a/toolbox/mount.c
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * mount.c, by rmk
- */
-
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <linux/loop.h>
-
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
-
-#define DEFAULT_LOOP_DEVICE "/dev/block/loop0"
-#define LOOPDEV_MAXLEN 64
-
-struct mount_opts {
- const char str[16];
- unsigned long rwmask;
- unsigned long rwset;
- unsigned long rwnoset;
-};
-
-struct extra_opts {
- char *str;
- char *end;
- int used_size;
- int alloc_size;
-};
-
-/*
- * These options define the function of "mount(2)".
- */
-#define MS_TYPE (MS_REMOUNT|MS_BIND|MS_MOVE)
-
-
-static const struct mount_opts options[] = {
- /* name mask set noset */
- { "async", MS_SYNCHRONOUS, 0, MS_SYNCHRONOUS },
- { "atime", MS_NOATIME, 0, MS_NOATIME },
- { "bind", MS_TYPE, MS_BIND, 0, },
- { "dev", MS_NODEV, 0, MS_NODEV },
- { "diratime", MS_NODIRATIME, 0, MS_NODIRATIME },
- { "dirsync", MS_DIRSYNC, MS_DIRSYNC, 0 },
- { "exec", MS_NOEXEC, 0, MS_NOEXEC },
- { "move", MS_TYPE, MS_MOVE, 0 },
- { "recurse", MS_REC, MS_REC, 0 },
- { "rec", MS_REC, MS_REC, 0 },
- { "remount", MS_TYPE, MS_REMOUNT, 0 },
- { "ro", MS_RDONLY, MS_RDONLY, 0 },
- { "rw", MS_RDONLY, 0, MS_RDONLY },
- { "suid", MS_NOSUID, 0, MS_NOSUID },
- { "sync", MS_SYNCHRONOUS, MS_SYNCHRONOUS, 0 },
- { "verbose", MS_VERBOSE, MS_VERBOSE, 0 },
- { "unbindable", MS_UNBINDABLE, MS_UNBINDABLE, 0 },
- { "private", MS_PRIVATE, MS_PRIVATE, 0 },
- { "slave", MS_SLAVE, MS_SLAVE, 0 },
- { "shared", MS_SHARED, MS_SHARED, 0 },
-};
-
-static void add_extra_option(struct extra_opts *extra, char *s)
-{
- int len = strlen(s);
- int newlen;
-
- if (extra->str)
- len++; /* +1 for ',' */
- newlen = extra->used_size + len;
-
- if (newlen >= extra->alloc_size) {
- char *new;
-
- new = realloc(extra->str, newlen + 1); /* +1 for NUL */
- if (!new)
- return;
-
- extra->str = new;
- extra->end = extra->str + extra->used_size;
- extra->alloc_size = newlen + 1;
- }
-
- if (extra->used_size) {
- *extra->end = ',';
- extra->end++;
- }
- strcpy(extra->end, s);
- extra->used_size += len;
-
-}
-
-static unsigned long
-parse_mount_options(char *arg, unsigned long rwflag, struct extra_opts *extra, int* loop, char *loopdev)
-{
- char *s;
-
- *loop = 0;
- while ((s = strsep(&arg, ",")) != NULL) {
- char *opt = s;
- unsigned int i;
- int res, no = s[0] == 'n' && s[1] == 'o';
-
- if (no)
- s += 2;
-
- if (strncmp(s, "loop=", 5) == 0) {
- *loop = 1;
- strlcpy(loopdev, s + 5, LOOPDEV_MAXLEN);
- continue;
- }
-
- if (strcmp(s, "loop") == 0) {
- *loop = 1;
- strlcpy(loopdev, DEFAULT_LOOP_DEVICE, LOOPDEV_MAXLEN);
- continue;
- }
- for (i = 0, res = 1; i < ARRAY_SIZE(options); i++) {
- res = strcmp(s, options[i].str);
-
- if (res == 0) {
- rwflag &= ~options[i].rwmask;
- if (no)
- rwflag |= options[i].rwnoset;
- else
- rwflag |= options[i].rwset;
- }
- if (res <= 0)
- break;
- }
-
- if (res != 0 && s[0])
- add_extra_option(extra, opt);
- }
-
- return rwflag;
-}
-
-/*
- * Mark the given block device as read-write, using the BLKROSET ioctl.
- */
-static void fs_set_blk_rw(const char *blockdev)
-{
- int fd;
- int OFF = 0;
-
- fd = open(blockdev, O_RDONLY);
- if (fd < 0) {
- // should never happen
- return;
- }
-
- ioctl(fd, BLKROSET, &OFF);
- close(fd);
-}
-
-static char *progname;
-
-static struct extra_opts extra;
-static unsigned long rwflag;
-
-static int
-do_mount(char *dev, char *dir, char *type, unsigned long rwflag, void *data, int loop,
- char *loopdev)
-{
- char *s;
- int error = 0;
-
- if (loop) {
- int file_fd, device_fd;
- int flags;
-
- flags = (rwflag & MS_RDONLY) ? O_RDONLY : O_RDWR;
-
- file_fd = open(dev, flags);
- if (file_fd < 0) {
- perror("open backing file failed");
- return 1;
- }
- device_fd = open(loopdev, flags);
- if (device_fd < 0) {
- perror("open loop device failed");
- close(file_fd);
- return 1;
- }
- if (ioctl(device_fd, LOOP_SET_FD, file_fd) < 0) {
- perror("ioctl LOOP_SET_FD failed");
- close(file_fd);
- close(device_fd);
- return 1;
- }
-
- close(file_fd);
- close(device_fd);
- dev = loopdev;
- }
-
- if ((rwflag & MS_RDONLY) == 0) {
- fs_set_blk_rw(dev);
- }
-
- while ((s = strsep(&type, ",")) != NULL) {
-retry:
- if (mount(dev, dir, s, rwflag, data) == -1) {
- error = errno;
- /*
- * If the filesystem is not found, or the
- * superblock is invalid, try the next.
- */
- if (error == ENODEV || error == EINVAL)
- continue;
-
- /*
- * If we get EACCESS, and we're trying to
- * mount readwrite and this isn't a remount,
- * try read only.
- */
- if (error == EACCES &&
- (rwflag & (MS_REMOUNT|MS_RDONLY)) == 0) {
- rwflag |= MS_RDONLY;
- goto retry;
- }
- break;
- }
- }
-
- if (error) {
- errno = error;
- perror("mount");
- return 255;
- }
-
- return 0;
-}
-
-static int print_mounts()
-{
- FILE* f;
- int length;
- char buffer[100];
-
- f = fopen("/proc/mounts", "r");
- if (!f) {
- fprintf(stdout, "could not open /proc/mounts\n");
- return -1;
- }
-
- do {
- length = fread(buffer, 1, 100, f);
- if (length > 0)
- fwrite(buffer, 1, length, stdout);
- } while (length > 0);
-
- fclose(f);
- return 0;
-}
-
-static int get_mounts_dev_dir(const char *arg, char **dev, char **dir)
-{
- FILE *f;
- char mount_dev[256];
- char mount_dir[256];
- char mount_type[256];
- char mount_opts[256];
- int mount_freq;
- int mount_passno;
- int match;
-
- f = fopen("/proc/mounts", "r");
- if (!f) {
- fprintf(stdout, "could not open /proc/mounts\n");
- return -1;
- }
-
- do {
- match = fscanf(f, "%255s %255s %255s %255s %d %d\n",
- mount_dev, mount_dir, mount_type,
- mount_opts, &mount_freq, &mount_passno);
- mount_dev[255] = 0;
- mount_dir[255] = 0;
- mount_type[255] = 0;
- mount_opts[255] = 0;
- if (match == 6 &&
- (strcmp(arg, mount_dev) == 0 ||
- strcmp(arg, mount_dir) == 0)) {
- *dev = strdup(mount_dev);
- *dir = strdup(mount_dir);
- fclose(f);
- return 0;
- }
- } while (match != EOF);
-
- fclose(f);
- return -1;
-}
-
-int mount_main(int argc, char *argv[])
-{
- char *type = NULL;
- char *dev = NULL;
- char *dir = NULL;
- int c;
- int loop = 0;
- char loopdev[LOOPDEV_MAXLEN];
-
- progname = argv[0];
- rwflag = MS_VERBOSE;
-
- // mount with no arguments is equivalent to "cat /proc/mounts"
- if (argc == 1) return print_mounts();
-
- do {
- c = getopt(argc, argv, "o:rt:w");
- if (c == EOF)
- break;
- switch (c) {
- case 'o':
- rwflag = parse_mount_options(optarg, rwflag, &extra, &loop, loopdev);
- break;
- case 'r':
- rwflag |= MS_RDONLY;
- break;
- case 't':
- type = optarg;
- break;
- case 'w':
- rwflag &= ~MS_RDONLY;
- break;
- case '?':
- fprintf(stderr, "%s: invalid option -%c\n",
- progname, optopt);
- exit(1);
- }
- } while (1);
-
- /*
- * If remount, bind or move was specified, then we don't
- * have a "type" as such. Use the dummy "none" type.
- */
- if (rwflag & MS_TYPE)
- type = "none";
-
- if (optind + 2 == argc) {
- dev = argv[optind];
- dir = argv[optind + 1];
- } else if (optind + 1 == argc && rwflag & MS_REMOUNT) {
- get_mounts_dev_dir(argv[optind], &dev, &dir);
- }
-
- if (dev == NULL || dir == NULL || type == NULL) {
- fprintf(stderr, "Usage: %s [-r] [-w] [-o options] [-t type] "
- "device directory\n", progname);
- exit(1);
- }
-
- return do_mount(dev, dir, type, rwflag, extra.str, loop, loopdev);
- /* We leak dev and dir in some cases, but we're about to exit */
-}
diff --git a/toolbox/renice.c b/toolbox/renice.c
deleted file mode 100644
index 99a06f4..0000000
--- a/toolbox/renice.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (c) 2008, The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google, Inc. nor the names of its contributors
- * may be used to endorse or promote products derived from this
- * software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <sched.h>
-#include <getopt.h>
-
-static void
-usage(const char *s)
-{
- fprintf(stderr, "USAGE: %s [[-r] [-t TYPE] priority pids ...] [-g pid]\n", s);
- exit(EXIT_FAILURE);
-}
-
-void print_prio(pid_t pid)
-{
- int sched;
- struct sched_param sp;
-
- printf("pid %d's priority: %d\n", pid, getpriority(PRIO_PROCESS, pid));
-
- printf("scheduling class: ");
- sched = sched_getscheduler(pid);
- switch (sched) {
- case SCHED_FIFO:
- printf("FIFO\n");
- break;
- case SCHED_RR:
- printf("RR\n");
- break;
- case SCHED_OTHER:
- printf("Normal\n");
- break;
- case -1:
- perror("sched_getscheduler");
- break;
- default:
- printf("Unknown\n");
- }
-
- sched_getparam(pid, &sp);
- printf("RT prio: %d (of %d to %d)\n", sp.sched_priority,
- sched_get_priority_min(sched), sched_get_priority_max(sched));
-}
-
-int get_sched(char *str)
-{
- if (strcasecmp(str, "RR") == 0)
- return SCHED_RR;
- else if (strcasecmp(str, "FIFO") == 0)
- return SCHED_FIFO;
- else if (strcasecmp(str, "NORMAL") == 0)
- return SCHED_OTHER;
- else if (strcasecmp(str, "OTHER") == 0)
- return SCHED_OTHER;
- return SCHED_RR;
-}
-
-int renice_main(int argc, char *argv[])
-{
- int prio;
- int realtime = 0;
- int opt;
- int sched = SCHED_RR;
- char *cmd = argv[0];
-
- do {
- opt = getopt(argc, argv, "rt:g:");
- if (opt == -1)
- break;
- switch (opt) {
- case 'r':
- // do realtime priority adjustment
- realtime = 1;
- break;
- case 't':
- sched = get_sched(optarg);
- break;
- case 'g':
- print_prio(atoi(optarg));
- return 0;
- default:
- usage(cmd);
- }
- } while (1);
-
- argc -= optind;
- argv += optind;
-
- if (argc < 1)
- usage(cmd);
-
- prio = atoi(argv[0]);
- argc--;
- argv++;
-
- if (argc < 1)
- usage(cmd);
-
- while(argc) {
- pid_t pid;
-
- pid = atoi(argv[0]);
- argc--;
- argv++;
-
- if (realtime) {
- struct sched_param sp = { .sched_priority = prio };
- int ret;
-
- ret = sched_setscheduler(pid, sched, &sp);
- if (ret) {
- perror("sched_set_scheduler");
- exit(EXIT_FAILURE);
- }
- } else {
- int ret;
-
- ret = setpriority(PRIO_PROCESS, pid, prio);
- if (ret) {
- perror("setpriority");
- exit(EXIT_FAILURE);
- }
- }
- }
-
- return 0;
-}
diff --git a/toolbox/start.c b/toolbox/start.c
index 6c8a3f2..cca5fef 100644
--- a/toolbox/start.c
+++ b/toolbox/start.c
@@ -1,21 +1 @@
-
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <cutils/properties.h>
-
-int start_main(int argc, char *argv[])
-{
- if(argc > 1) {
- property_set("ctl.start", argv[1]);
- } else {
- /* defaults to starting the common services stopped by stop.c */
- property_set("ctl.start", "netd");
- property_set("ctl.start", "surfaceflinger");
- property_set("ctl.start", "zygote");
- property_set("ctl.start", "zygote_secondary");
- }
-
- return 0;
-}
+/* Needed by Android.mk. Actual code in start_stop.cpp. */
diff --git a/toolbox/start_stop.cpp b/toolbox/start_stop.cpp
new file mode 100644
index 0000000..dc48c0c
--- /dev/null
+++ b/toolbox/start_stop.cpp
@@ -0,0 +1,43 @@
+#include <error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <cutils/properties.h>
+
+static const char* services[] = {
+ "netd",
+ "surfaceflinger",
+ "zygote",
+ "zygote_secondary",
+};
+
+static int start_stop(bool start, int argc, char* argv[]) {
+ if (getuid() != 0) error(1, 0, "must be root");
+ const char* property = start ? "ctl.start" : "ctl.stop";
+ if (argc > 2) {
+ error(1, 0, "usage: %s [SERVICE]\n", argv[0]);
+ } else if (argc == 2) {
+ property_set(property, argv[1]);
+ } else {
+ if (start) {
+ for (size_t i = 0; i < sizeof(services)/sizeof(services[0]); ++i) {
+ property_set(property, services[i]);
+ }
+ } else {
+ for (int i = sizeof(services)/sizeof(services[0]) - 1; i >= 0; --i) {
+ property_set(property, services[i]);
+ }
+ }
+ }
+ return 0;
+}
+
+extern "C" int start_main(int argc, char* argv[]) {
+ return start_stop(true, argc, argv);
+}
+
+extern "C" int stop_main(int argc, char* argv[]) {
+ return start_stop(false, argc, argv);
+}
diff --git a/toolbox/stop.c b/toolbox/stop.c
index 5e3ce3c..cca5fef 100644
--- a/toolbox/stop.c
+++ b/toolbox/stop.c
@@ -1,19 +1 @@
-#include <stdio.h>
-#include <string.h>
-
-#include <cutils/properties.h>
-
-int stop_main(int argc, char *argv[])
-{
- if(argc > 1) {
- property_set("ctl.stop", argv[1]);
- } else{
- /* defaults to stopping the common services */
- property_set("ctl.stop", "zygote_secondary");
- property_set("ctl.stop", "zygote");
- property_set("ctl.stop", "surfaceflinger");
- property_set("ctl.stop", "netd");
- }
-
- return 0;
-}
+/* Needed by Android.mk. Actual code in start_stop.cpp. */
diff --git a/toolbox/watchprops.c b/toolbox/watchprops.c
deleted file mode 100644
index cd62922..0000000
--- a/toolbox/watchprops.c
+++ /dev/null
@@ -1,92 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <errno.h>
-
-#include <cutils/properties.h>
-#include <cutils/hashmap.h>
-
-#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
-#include <sys/_system_properties.h>
-
-static int str_hash(void *key)
-{
- return hashmapHash(key, strlen(key));
-}
-
-static bool str_equals(void *keyA, void *keyB)
-{
- return strcmp(keyA, keyB) == 0;
-}
-
-static void announce(char *name, char *value)
-{
- unsigned char *x;
-
- for(x = (unsigned char *)value; *x; x++) {
- if((*x < 32) || (*x > 127)) *x = '.';
- }
-
- fprintf(stderr,"%10d %s = '%s'\n", (int) time(0), name, value);
-}
-
-static void add_to_watchlist(Hashmap *watchlist, const char *name,
- const prop_info *pi)
-{
- char *key = strdup(name);
- unsigned *value = malloc(sizeof(unsigned));
- if (!key || !value)
- exit(1);
-
- *value = __system_property_serial(pi);
- hashmapPut(watchlist, key, value);
-}
-
-static void populate_watchlist(const prop_info *pi, void *cookie)
-{
- Hashmap *watchlist = cookie;
- char name[PROP_NAME_MAX];
- char value_unused[PROP_VALUE_MAX];
-
- __system_property_read(pi, name, value_unused);
- add_to_watchlist(watchlist, name, pi);
-}
-
-static void update_watchlist(const prop_info *pi, void *cookie)
-{
- Hashmap *watchlist = cookie;
- char name[PROP_NAME_MAX];
- char value[PROP_VALUE_MAX];
- unsigned *serial;
-
- __system_property_read(pi, name, value);
- serial = hashmapGet(watchlist, name);
- if (!serial) {
- add_to_watchlist(watchlist, name, pi);
- announce(name, value);
- } else {
- unsigned tmp = __system_property_serial(pi);
- if (*serial != tmp) {
- *serial = tmp;
- announce(name, value);
- }
- }
-}
-
-int watchprops_main(int argc, char *argv[])
-{
- unsigned serial;
-
- Hashmap *watchlist = hashmapCreate(1024, str_hash, str_equals);
- if (!watchlist)
- exit(1);
-
- __system_property_foreach(populate_watchlist, watchlist);
-
- for(serial = 0;;) {
- serial = __system_property_wait_any(serial);
- __system_property_foreach(update_watchlist, watchlist);
- }
- return 0;
-}