Merge "fastboot: fix set_active for legacy A/B."
diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk
index 607745d..e3bdd43 100644
--- a/debuggerd/Android.mk
+++ b/debuggerd/Android.mk
@@ -52,7 +52,7 @@
include $(BUILD_EXECUTABLE)
-crasher_cppflags := $(common_cppflags) -fstack-protector-all -Wno-free-nonheap-object -Wno-date-time
+crasher_cppflags := $(common_cppflags) -O0 -fstack-protector-all -Wno-free-nonheap-object
include $(CLEAR_VARS)
LOCAL_SRC_FILES := crasher.cpp
@@ -65,7 +65,7 @@
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := optional
LOCAL_CPPFLAGS := $(crasher_cppflags)
-LOCAL_SHARED_LIBRARIES := libcutils liblog
+LOCAL_SHARED_LIBRARIES := libbase liblog
# The arm emulator has VFP but not VFPv3-D32.
ifeq ($(ARCH_ARM_HAVE_VFP_D32),true)
@@ -91,7 +91,6 @@
LOCAL_MODULE_TAGS := optional
LOCAL_CPPFLAGS := $(crasher_cppflags) -DSTATIC_CRASHER
LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_SHARED_LIBRARIES := libcutils liblog
# The arm emulator has VFP but not VFPv3-D32.
ifeq ($(ARCH_ARM_HAVE_VFP_D32),true)
@@ -103,7 +102,7 @@
LOCAL_MODULE_STEM_64 := static_crasher64
LOCAL_MULTILIB := both
-LOCAL_STATIC_LIBRARIES := libdebuggerd_client libcutils liblog
+LOCAL_STATIC_LIBRARIES := libdebuggerd_client libbase liblog
include $(BUILD_EXECUTABLE)
diff --git a/debuggerd/crasher.cpp b/debuggerd/crasher.cpp
index b0e8b17..e650f22 100644
--- a/debuggerd/crasher.cpp
+++ b/debuggerd/crasher.cpp
@@ -17,47 +17,43 @@
#define LOG_TAG "crasher"
#include <assert.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
-#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/cdefs.h>
#include <sys/mman.h>
-#include <sys/ptrace.h>
-#include <sys/socket.h>
-#include <sys/wait.h>
#include <unistd.h>
+// We test both kinds of logging.
#include <android/log.h>
-#include <cutils/sockets.h>
+#include <android-base/logging.h>
#if defined(STATIC_CRASHER)
#include "debuggerd/client.h"
#endif
-#ifndef __unused
-#define __unused __attribute__((__unused__))
-#endif
+#define noinline __attribute__((__noinline__))
-extern const char* __progname;
+// Avoid name mangling so that stacks are more readable.
+extern "C" {
-extern "C" void crash1(void);
-extern "C" void crashnostack(void);
+void crash1(void);
+void crashnostack(void);
-static int do_action(const char* arg);
+int do_action(const char* arg);
-static void maybe_abort() {
+noinline void maybe_abort() {
if (time(0) != 42) {
abort();
}
}
-static char* smash_stack_dummy_buf;
-__attribute__ ((noinline)) static void smash_stack_dummy_function(volatile int* plen) {
+char* smash_stack_dummy_buf;
+noinline void smash_stack_dummy_function(volatile int* plen) {
smash_stack_dummy_buf[*plen] = 0;
}
@@ -65,8 +61,8 @@
// compiler generates the proper stack guards around this function.
// Assign local array address to global variable to force stack guards.
// Use another noinline function to corrupt the stack.
-__attribute__ ((noinline)) static int smash_stack(volatile int* plen) {
- printf("%s: deliberately corrupting stack...\n", __progname);
+noinline int smash_stack(volatile int* plen) {
+ printf("%s: deliberately corrupting stack...\n", getprogname());
char buf[128];
smash_stack_dummy_buf = buf;
@@ -75,91 +71,107 @@
return 0;
}
-#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winfinite-recursion"
-#endif
-static void* global = 0; // So GCC doesn't optimize the tail recursion out of overflow_stack.
+void* global = 0; // So GCC doesn't optimize the tail recursion out of overflow_stack.
-__attribute__((noinline)) static void overflow_stack(void* p) {
+noinline void overflow_stack(void* p) {
void* buf[1];
buf[0] = p;
global = buf;
overflow_stack(&buf);
}
-#if defined(__clang__)
#pragma clang diagnostic pop
-#endif
-static void *noisy(void *x)
-{
- char c = (uintptr_t) x;
- for(;;) {
- usleep(250*1000);
- write(2, &c, 1);
- if(c == 'C') *((volatile unsigned*) 0) = 42;
- }
- return NULL;
+noinline void* thread_callback(void* raw_arg) {
+ const char* arg = reinterpret_cast<const char*>(raw_arg);
+ return reinterpret_cast<void*>(static_cast<uintptr_t>(do_action(arg)));
}
-static int ctest()
-{
- pthread_t thr;
- pthread_attr_t attr;
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- pthread_create(&thr, &attr, noisy, (void*) 'A');
- pthread_create(&thr, &attr, noisy, (void*) 'B');
- pthread_create(&thr, &attr, noisy, (void*) 'C');
- for(;;) ;
- return 0;
-}
-
-static void* thread_callback(void* raw_arg)
-{
- return (void*) (uintptr_t) do_action((const char*) raw_arg);
-}
-
-static int do_action_on_thread(const char* arg)
-{
+noinline int do_action_on_thread(const char* arg) {
pthread_t t;
- pthread_create(&t, NULL, thread_callback, (void*) arg);
- void* result = NULL;
+ pthread_create(&t, nullptr, thread_callback, const_cast<char*>(arg));
+ void* result = nullptr;
pthread_join(t, &result);
- return (int) (uintptr_t) result;
+ return reinterpret_cast<uintptr_t>(result);
}
-__attribute__((noinline)) static int crash3(int a) {
- *((int*) 0xdead) = a;
+noinline int crash3(int a) {
+ *reinterpret_cast<int*>(0xdead) = a;
return a*4;
}
-__attribute__((noinline)) static int crash2(int a) {
+noinline int crash2(int a) {
a = crash3(a) + 2;
return a*3;
}
-__attribute__((noinline)) static int crash(int a) {
+noinline int crash(int a) {
a = crash2(a) + 1;
return a*2;
}
-static void abuse_heap() {
+noinline void abuse_heap() {
char buf[16];
- free((void*) buf); // GCC is smart enough to warn about this, but we're doing it deliberately.
+ free(buf); // GCC is smart enough to warn about this, but we're doing it deliberately.
}
-static void sigsegv_non_null() {
+noinline void sigsegv_non_null() {
int* a = (int *)(&do_action);
*a = 42;
}
-static int do_action(const char* arg)
-{
- fprintf(stderr, "%s: init pid=%d tid=%d\n", __progname, getpid(), gettid());
+noinline void fprintf_null() {
+ fprintf(nullptr, "oops");
+}
+noinline void readdir_null() {
+ readdir(nullptr);
+}
+
+noinline int strlen_null() {
+ char* sneaky_null = nullptr;
+ return strlen(sneaky_null);
+}
+
+static int usage() {
+ fprintf(stderr, "usage: %s KIND\n", getprogname());
+ fprintf(stderr, "\n");
+ fprintf(stderr, "where KIND is:\n");
+ fprintf(stderr, " smash-stack overwrite a -fstack-protector guard\n");
+ fprintf(stderr, " stack-overflow recurse until the stack overflows\n");
+ fprintf(stderr, " heap-corruption cause a libc abort by corrupting the heap\n");
+ fprintf(stderr, " heap-usage cause a libc abort by abusing a heap function\n");
+ fprintf(stderr, " nostack crash with a NULL stack pointer\n");
+ fprintf(stderr, " abort call abort()\n");
+ fprintf(stderr, " assert call assert() without a function\n");
+ fprintf(stderr, " assert2 call assert() with a function\n");
+ fprintf(stderr, " exit call exit(1)\n");
+ fprintf(stderr, " fortify fail a _FORTIFY_SOURCE check\n");
+ fprintf(stderr, " LOG_ALWAYS_FATAL call liblog LOG_ALWAYS_FATAL\n");
+ fprintf(stderr, " LOG_ALWAYS_FATAL_IF call liblog LOG_ALWAYS_FATAL_IF\n");
+ fprintf(stderr, " LOG-FATAL call libbase LOG(FATAL)\n");
+ fprintf(stderr, " SIGFPE cause a SIGFPE\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");
+ fprintf(stderr, " SIGTRAP cause a SIGTRAP\n");
+ fprintf(stderr, " fprintf-NULL pass a null pointer to fprintf\n");
+ fprintf(stderr, " readdir-NULL pass a null pointer to readdir\n");
+ fprintf(stderr, " strlen-NULL pass a null pointer to strlen\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "prefix any of the above with 'thread-' to run on a new thread\n");
+ fprintf(stderr, "prefix any of the above with 'exhaustfd-' to exhaust\n");
+ fprintf(stderr, "all available file descriptors before crashing.\n");
+ fprintf(stderr, "prefix any of the above with 'wait-' to wait until input is received on stdin\n");
+
+ return EXIT_FAILURE;
+}
+
+noinline int do_action(const char* arg) {
+ // Prefixes.
if (!strncmp(arg, "wait-", strlen("wait-"))) {
char buf[1];
TEMP_FAILURE_RETRY(read(STDIN_FILENO, buf, sizeof(buf)));
@@ -172,82 +184,66 @@
return do_action(arg + strlen("exhaustfd-"));
} else if (!strncmp(arg, "thread-", strlen("thread-"))) {
return do_action_on_thread(arg + strlen("thread-"));
- } else if (!strcmp(arg, "SIGSEGV-non-null")) {
+ }
+
+ // Actions.
+ if (!strcasecmp(arg, "SIGSEGV-non-null")) {
sigsegv_non_null();
- } else if (!strcmp(arg, "smash-stack")) {
+ } else if (!strcasecmp(arg, "smash-stack")) {
volatile int len = 128;
return smash_stack(&len);
- } else if (!strcmp(arg, "stack-overflow")) {
- overflow_stack(NULL);
- } else if (!strcmp(arg, "nostack")) {
+ } else if (!strcasecmp(arg, "stack-overflow")) {
+ overflow_stack(nullptr);
+ } else if (!strcasecmp(arg, "nostack")) {
crashnostack();
- } else if (!strcmp(arg, "ctest")) {
- return ctest();
- } else if (!strcmp(arg, "exit")) {
+ } else if (!strcasecmp(arg, "exit")) {
exit(1);
- } else if (!strcmp(arg, "crash") || !strcmp(arg, "SIGSEGV")) {
+ } else if (!strcasecmp(arg, "crash") || !strcmp(arg, "SIGSEGV")) {
return crash(42);
- } else if (!strcmp(arg, "abort")) {
+ } else if (!strcasecmp(arg, "abort")) {
maybe_abort();
- } else if (!strcmp(arg, "assert")) {
+ } else if (!strcasecmp(arg, "assert")) {
__assert("some_file.c", 123, "false");
- } else if (!strcmp(arg, "assert2")) {
+ } else if (!strcasecmp(arg, "assert2")) {
__assert2("some_file.c", 123, "some_function", "false");
- } else if (!strcmp(arg, "fortify")) {
+ } else if (!strcasecmp(arg, "fortify")) {
char buf[10];
__read_chk(-1, buf, 32, 10);
while (true) pause();
- } else if (!strcmp(arg, "LOG_ALWAYS_FATAL")) {
+ } else if (!strcasecmp(arg, "LOG(FATAL)")) {
+ LOG(FATAL) << "hello " << 123;
+ } else if (!strcasecmp(arg, "LOG_ALWAYS_FATAL")) {
LOG_ALWAYS_FATAL("hello %s", "world");
- } else if (!strcmp(arg, "LOG_ALWAYS_FATAL_IF")) {
+ } else if (!strcasecmp(arg, "LOG_ALWAYS_FATAL_IF")) {
LOG_ALWAYS_FATAL_IF(true, "hello %s", "world");
- } else if (!strcmp(arg, "SIGFPE")) {
+ } else if (!strcasecmp(arg, "SIGFPE")) {
raise(SIGFPE);
return EXIT_SUCCESS;
- } else if (!strcmp(arg, "SIGTRAP")) {
+ } else if (!strcasecmp(arg, "SIGTRAP")) {
raise(SIGTRAP);
return EXIT_SUCCESS;
- } else if (!strcmp(arg, "heap-usage")) {
+ } else if (!strcasecmp(arg, "fprintf-NULL")) {
+ fprintf_null();
+ } else if (!strcasecmp(arg, "readdir-NULL")) {
+ readdir_null();
+ } else if (!strcasecmp(arg, "strlen-NULL")) {
+ return strlen_null();
+ } else if (!strcasecmp(arg, "heap-usage")) {
abuse_heap();
- } else if (!strcmp(arg, "SIGSEGV-unmapped")) {
- char* map = reinterpret_cast<char*>(mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0));
+ } else if (!strcasecmp(arg, "SIGSEGV-unmapped")) {
+ char* map = reinterpret_cast<char*>(mmap(nullptr, sizeof(int), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0));
munmap(map, sizeof(int));
map[0] = '8';
+ } else {
+ return usage();
}
- fprintf(stderr, "%s OP\n", __progname);
- fprintf(stderr, "where OP is:\n");
- fprintf(stderr, " smash-stack overwrite a stack-guard canary\n");
- fprintf(stderr, " stack-overflow recurse until the stack overflows\n");
- fprintf(stderr, " heap-corruption cause a libc abort by corrupting the heap\n");
- fprintf(stderr, " heap-usage cause a libc abort by abusing a heap function\n");
- fprintf(stderr, " nostack crash with a NULL stack pointer\n");
- fprintf(stderr, " ctest (obsoleted by thread-crash?)\n");
- fprintf(stderr, " exit call exit(1)\n");
- fprintf(stderr, " abort call abort()\n");
- fprintf(stderr, " assert call assert() without a function\n");
- fprintf(stderr, " assert2 call assert() with a function\n");
- fprintf(stderr, " fortify fail a _FORTIFY_SOURCE check\n");
- 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, " 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");
- fprintf(stderr, " SIGTRAP cause a SIGTRAP\n");
- fprintf(stderr, "prefix any of the above with 'thread-' to not run\n");
- fprintf(stderr, "on the process' main thread.\n");
- fprintf(stderr, "prefix any of the above with 'exhaustfd-' to exhaust\n");
- fprintf(stderr, "all available file descriptors before crashing.\n");
- fprintf(stderr, "prefix any of the above with 'wait-' to wait until input is received on stdin\n");
-
+ fprintf(stderr, "%s: exiting normally!\n", getprogname());
return EXIT_SUCCESS;
}
-int main(int argc, char **argv)
-{
- fprintf(stderr, "%s: built at " __TIME__ "!@\n", __progname);
-
+int main(int argc, char** argv) {
#if defined(STATIC_CRASHER)
debuggerd_callbacks_t callbacks = {
.get_abort_message = []() {
@@ -265,11 +261,10 @@
debuggerd_init(&callbacks);
#endif
- if (argc > 1) {
- return do_action(argv[1]);
- } else {
- crash1();
- }
+ if (argc == 1) crash1();
+ else if (argc == 2) return do_action(argv[1]);
- return 0;
+ return usage();
}
+
+};
diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c
index f682216..7389f59 100644
--- a/fs_mgr/fs_mgr.c
+++ b/fs_mgr/fs_mgr.c
@@ -213,6 +213,73 @@
le32_to_cpu(es->s_r_blocks_count_lo);
}
+static int do_quota(char *blk_device, char *fs_type, struct fstab_rec *rec)
+{
+ int force_check = 0;
+ if (!strcmp(fs_type, "ext4")) {
+ /*
+ * Some system images do not have tune2fs for licensing reasons
+ * Detect these and skip reserve blocks.
+ */
+ if (access(TUNE2FS_BIN, X_OK)) {
+ ERROR("Not running %s on %s (executable not in system image)\n",
+ TUNE2FS_BIN, blk_device);
+ } else {
+ char* arg1 = NULL;
+ char* arg2 = NULL;
+ int status = 0;
+ int ret = 0;
+ int fd = TEMP_FAILURE_RETRY(open(blk_device, O_RDONLY | O_CLOEXEC));
+ if (fd >= 0) {
+ struct ext4_super_block sb;
+ ret = read_super_block(fd, &sb);
+ if (ret < 0) {
+ ERROR("Can't read '%s' super block: %s\n", blk_device, strerror(errno));
+ goto out;
+ }
+
+ int has_quota = (sb.s_feature_ro_compat
+ & cpu_to_le32(EXT4_FEATURE_RO_COMPAT_QUOTA)) != 0;
+ int want_quota = fs_mgr_is_quota(rec) != 0;
+
+ if (has_quota == want_quota) {
+ INFO("Requested quota status is match on %s\n", blk_device);
+ goto out;
+ } else if (want_quota) {
+ INFO("Enabling quota on %s\n", blk_device);
+ arg1 = "-Oquota";
+ arg2 = "-Qusrquota,grpquota";
+ force_check = 1;
+ } else {
+ INFO("Disabling quota on %s\n", blk_device);
+ arg1 = "-Q^usrquota,^grpquota";
+ arg2 = "-O^quota";
+ }
+ } else {
+ ERROR("Failed to open '%s': %s\n", blk_device, strerror(errno));
+ return force_check;
+ }
+
+ char *tune2fs_argv[] = {
+ TUNE2FS_BIN,
+ arg1,
+ arg2,
+ blk_device,
+ };
+ ret = android_fork_execvp_ext(ARRAY_SIZE(tune2fs_argv), tune2fs_argv,
+ &status, true, LOG_KLOG | LOG_FILE,
+ true, NULL, 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", TUNE2FS_BIN);
+ }
+ out:
+ close(fd);
+ }
+ }
+ return force_check;
+}
+
static void do_reserved_size(char *blk_device, char *fs_type, struct fstab_rec *rec)
{
/* Check for the types of filesystems we know how to check */
@@ -417,7 +484,10 @@
continue;
}
- if (fstab->recs[i].fs_mgr_flags & MF_CHECK) {
+ int force_check = do_quota(fstab->recs[i].blk_device, fstab->recs[i].fs_type,
+ &fstab->recs[i]);
+
+ if ((fstab->recs[i].fs_mgr_flags & MF_CHECK) || force_check) {
check_fs(fstab->recs[i].blk_device, fstab->recs[i].fs_type,
fstab->recs[i].mount_point);
}
@@ -787,7 +857,10 @@
wait_for_file(n_blk_device, WAIT_TIMEOUT);
}
- if (fstab->recs[i].fs_mgr_flags & MF_CHECK) {
+ int force_check = do_quota(fstab->recs[i].blk_device, fstab->recs[i].fs_type,
+ &fstab->recs[i]);
+
+ if ((fstab->recs[i].fs_mgr_flags & MF_CHECK) || force_check) {
check_fs(n_blk_device, fstab->recs[i].fs_type,
fstab->recs[i].mount_point);
}
diff --git a/fs_mgr/fs_mgr_fstab.c b/fs_mgr/fs_mgr_fstab.c
index f25d10c..41fb746 100644
--- a/fs_mgr/fs_mgr_fstab.c
+++ b/fs_mgr/fs_mgr_fstab.c
@@ -62,30 +62,31 @@
};
static struct flag_list fs_mgr_flags[] = {
- { "wait", MF_WAIT },
- { "check", MF_CHECK },
- { "encryptable=",MF_CRYPT },
- { "forceencrypt=",MF_FORCECRYPT },
- { "fileencryption=",MF_FILEENCRYPTION },
- { "forcefdeorfbe=",MF_FORCEFDEORFBE },
- { "nonremovable",MF_NONREMOVABLE },
- { "voldmanaged=",MF_VOLDMANAGED},
- { "length=", MF_LENGTH },
- { "recoveryonly",MF_RECOVERYONLY },
- { "swapprio=", MF_SWAPPRIO },
- { "zramsize=", MF_ZRAMSIZE },
- { "max_comp_streams=", MF_MAX_COMP_STREAMS },
- { "verifyatboot", MF_VERIFYATBOOT },
- { "verify", MF_VERIFY },
- { "noemulatedsd", MF_NOEMULATEDSD },
- { "notrim", MF_NOTRIM },
- { "formattable", MF_FORMATTABLE },
- { "slotselect", MF_SLOTSELECT },
- { "nofail", MF_NOFAIL },
- { "latemount", MF_LATEMOUNT },
- { "reservedsize=", MF_RESERVEDSIZE },
- { "defaults", 0 },
- { 0, 0 },
+ { "wait", MF_WAIT },
+ { "check", MF_CHECK },
+ { "encryptable=", MF_CRYPT },
+ { "forceencrypt=", MF_FORCECRYPT },
+ { "fileencryption=", MF_FILEENCRYPTION },
+ { "forcefdeorfbe=", MF_FORCEFDEORFBE },
+ { "nonremovable", MF_NONREMOVABLE },
+ { "voldmanaged=", MF_VOLDMANAGED},
+ { "length=", MF_LENGTH },
+ { "recoveryonly", MF_RECOVERYONLY },
+ { "swapprio=", MF_SWAPPRIO },
+ { "zramsize=", MF_ZRAMSIZE },
+ { "max_comp_streams=", MF_MAX_COMP_STREAMS },
+ { "verifyatboot", MF_VERIFYATBOOT },
+ { "verify", MF_VERIFY },
+ { "noemulatedsd", MF_NOEMULATEDSD },
+ { "notrim", MF_NOTRIM },
+ { "formattable", MF_FORMATTABLE },
+ { "slotselect", MF_SLOTSELECT },
+ { "nofail", MF_NOFAIL },
+ { "latemount", MF_LATEMOUNT },
+ { "reservedsize=", MF_RESERVEDSIZE },
+ { "quota", MF_QUOTA },
+ { "defaults", 0 },
+ { 0, 0 },
};
#define EM_SOFTWARE 1
@@ -587,3 +588,8 @@
{
return fstab->fs_mgr_flags & MF_LATEMOUNT;
}
+
+int fs_mgr_is_quota(struct fstab_rec *fstab)
+{
+ return fstab->fs_mgr_flags & MF_QUOTA;
+}
diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h
index 4632521..db86afa 100644
--- a/fs_mgr/fs_mgr_priv.h
+++ b/fs_mgr/fs_mgr_priv.h
@@ -65,29 +65,30 @@
*
*/
-#define MF_WAIT 0x1
-#define MF_CHECK 0x2
-#define MF_CRYPT 0x4
-#define MF_NONREMOVABLE 0x8
-#define MF_VOLDMANAGED 0x10
-#define MF_LENGTH 0x20
-#define MF_RECOVERYONLY 0x40
-#define MF_SWAPPRIO 0x80
-#define MF_ZRAMSIZE 0x100
-#define MF_VERIFY 0x200
-#define MF_FORCECRYPT 0x400
-#define MF_NOEMULATEDSD 0x800 /* no emulated sdcard daemon, sd card is the only
- external storage */
-#define MF_NOTRIM 0x1000
-#define MF_FILEENCRYPTION 0x2000
-#define MF_FORMATTABLE 0x4000
-#define MF_SLOTSELECT 0x8000
-#define MF_FORCEFDEORFBE 0x10000
-#define MF_LATEMOUNT 0x20000
-#define MF_NOFAIL 0x40000
-#define MF_VERIFYATBOOT 0x80000
+#define MF_WAIT 0x1
+#define MF_CHECK 0x2
+#define MF_CRYPT 0x4
+#define MF_NONREMOVABLE 0x8
+#define MF_VOLDMANAGED 0x10
+#define MF_LENGTH 0x20
+#define MF_RECOVERYONLY 0x40
+#define MF_SWAPPRIO 0x80
+#define MF_ZRAMSIZE 0x100
+#define MF_VERIFY 0x200
+#define MF_FORCECRYPT 0x400
+#define MF_NOEMULATEDSD 0x800 /* no emulated sdcard daemon, sd card is the only
+ external storage */
+#define MF_NOTRIM 0x1000
+#define MF_FILEENCRYPTION 0x2000
+#define MF_FORMATTABLE 0x4000
+#define MF_SLOTSELECT 0x8000
+#define MF_FORCEFDEORFBE 0x10000
+#define MF_LATEMOUNT 0x20000
+#define MF_NOFAIL 0x40000
+#define MF_VERIFYATBOOT 0x80000
#define MF_MAX_COMP_STREAMS 0x100000
-#define MF_RESERVEDSIZE 0x200000
+#define MF_RESERVEDSIZE 0x200000
+#define MF_QUOTA 0x400000
#define DM_BUF_SIZE 4096
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 8ecc93c..ef7fdd3 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -123,6 +123,7 @@
int fs_mgr_is_formattable(struct fstab_rec *fstab);
int fs_mgr_is_nofail(struct fstab_rec *fstab);
int fs_mgr_is_latemount(struct fstab_rec *fstab);
+int fs_mgr_is_quota(struct fstab_rec *fstab);
int fs_mgr_swapon_all(struct fstab *fstab);
int fs_mgr_do_format(struct fstab_rec *fstab, bool reserve_footer);
diff --git a/include/cutils/multiuser.h b/include/cutils/multiuser.h
index 7e7f815..4f23776 100644
--- a/include/cutils/multiuser.h
+++ b/include/cutils/multiuser.h
@@ -23,19 +23,19 @@
extern "C" {
#endif
-// NOTE: keep in sync with android.os.UserId
-
-#define MULTIUSER_APP_PER_USER_RANGE 100000
-#define MULTIUSER_FIRST_SHARED_APPLICATION_GID 50000
-#define MULTIUSER_FIRST_APPLICATION_UID 10000
-
typedef uid_t userid_t;
typedef uid_t appid_t;
extern userid_t multiuser_get_user_id(uid_t uid);
extern appid_t multiuser_get_app_id(uid_t uid);
-extern uid_t multiuser_get_uid(userid_t userId, appid_t appId);
-extern appid_t multiuser_get_shared_app_gid(uid_t uid);
+
+extern uid_t multiuser_get_uid(userid_t user_id, appid_t app_id);
+
+extern gid_t multiuser_get_cache_gid(userid_t user_id, appid_t app_id);
+extern gid_t multiuser_get_shared_gid(userid_t user_id, appid_t app_id);
+
+/* TODO: switch callers over to multiuser_get_shared_gid() */
+extern gid_t multiuser_get_shared_app_gid(uid_t uid);
#ifdef __cplusplus
}
diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h
index 3031562..f7cf9b8 100644
--- a/include/private/android_filesystem_config.h
+++ b/include/private/android_filesystem_config.h
@@ -157,15 +157,21 @@
#define AID_MISC 9998 /* access to misc storage */
#define AID_NOBODY 9999
-#define AID_APP 10000 /* first app user */
+#define AID_APP 10000 /* TODO: switch users over to AID_APP_START */
+#define AID_APP_START 10000 /* first app user */
+#define AID_APP_END 19999 /* last app user */
-#define AID_ISOLATED_START 99000 /* start of uids for fully isolated sandboxed processes */
-#define AID_ISOLATED_END 99999 /* end of uids for fully isolated sandboxed processes */
-
-#define AID_USER 100000 /* offset for uid ranges for each user */
+#define AID_CACHE_GID_START 20000 /* start of gids for apps to mark cached data */
+#define AID_CACHE_GID_END 29999 /* end of gids for apps to mark cached data */
#define AID_SHARED_GID_START 50000 /* start of gids for apps in each user to share */
-#define AID_SHARED_GID_END 59999 /* start of gids for apps in each user to share */
+#define AID_SHARED_GID_END 59999 /* end of gids for apps in each user to share */
+
+#define AID_ISOLATED_START 99000 /* start of uids for fully isolated sandboxed processes */
+#define AID_ISOLATED_END 99999 /* end of uids for fully isolated sandboxed processes */
+
+#define AID_USER 100000 /* TODO: switch users over to AID_USER_OFFSET */
+#define AID_USER_OFFSET 100000 /* offset for uid ranges for each user */
#if !defined(EXCLUDE_FS_CONFIG_STRUCTURES)
/*
diff --git a/init/bootchart.cpp b/init/bootchart.cpp
index 8fb55f0..7a8a3d5 100644
--- a/init/bootchart.cpp
+++ b/init/bootchart.cpp
@@ -34,29 +34,25 @@
#include <vector>
#include <android-base/file.h>
+#include <android-base/stringprintf.h>
-#define LOG_ROOT "/data/bootchart"
-#define LOG_STAT LOG_ROOT"/proc_stat.log"
-#define LOG_PROCS LOG_ROOT"/proc_ps.log"
-#define LOG_DISK LOG_ROOT"/proc_diskstats.log"
-#define LOG_HEADER LOG_ROOT"/header"
-#define LOG_ACCT LOG_ROOT"/kernel_pacct"
+using android::base::StringPrintf;
-#define LOG_STARTFILE LOG_ROOT"/start"
-#define LOG_STOPFILE LOG_ROOT"/stop"
+static constexpr const char* LOG_STAT = "/data/bootchart/proc_stat.log";
+static constexpr const char* LOG_PROC = "/data/bootchart/proc_ps.log";
+static constexpr const char* LOG_DISK = "/data/bootchart/proc_diskstats.log";
+static constexpr const char* LOG_HEADER = "/data/bootchart/header";
// Polling period in ms.
-static const int BOOTCHART_POLLING_MS = 200;
-
-// Max polling time in seconds.
-static const int BOOTCHART_MAX_TIME_SEC = 10*60;
+static constexpr int BOOTCHART_POLLING_MS = 200;
static long long g_last_bootchart_time;
-static int g_remaining_samples;
-static FILE* log_stat;
-static FILE* log_procs;
-static FILE* log_disks;
+static bool g_bootcharting = false;
+
+static FILE* g_stat_log;
+static FILE* g_proc_log;
+static FILE* g_disk_log;
static long long get_uptime_jiffies() {
std::string uptime;
@@ -99,12 +95,12 @@
fclose(out);
}
-static void do_log_uptime(FILE* log) {
+static void log_uptime(FILE* log) {
fprintf(log, "%lld\n", get_uptime_jiffies());
}
-static void do_log_file(FILE* log, const char* procfile) {
- do_log_uptime(log);
+static void log_file(FILE* log, const char* procfile) {
+ log_uptime(log);
std::string content;
if (android::base::ReadFileToString(procfile, &content)) {
@@ -112,161 +108,115 @@
}
}
-static void do_log_procs(FILE* log) {
- do_log_uptime(log);
+static void log_processes() {
+ log_uptime(g_proc_log);
std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir("/proc"), closedir);
struct dirent* entry;
while ((entry = readdir(dir.get())) != NULL) {
// Only match numeric values.
- char* end;
- int pid = strtol(entry->d_name, &end, 10);
- if (end != NULL && end > entry->d_name && *end == 0) {
- char filename[32];
+ int pid = atoi(entry->d_name);
+ if (pid == 0) continue;
- // /proc/<pid>/stat only has truncated task names, so get the full
- // name from /proc/<pid>/cmdline.
- snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid);
- std::string cmdline;
- android::base::ReadFileToString(filename, &cmdline);
- const char* full_name = cmdline.c_str(); // So we stop at the first NUL.
-
- // Read process stat line.
- snprintf(filename, sizeof(filename), "/proc/%d/stat", pid);
- std::string stat;
- if (android::base::ReadFileToString(filename, &stat)) {
- if (!cmdline.empty()) {
- // Substitute the process name with its real name.
- size_t open = stat.find('(');
- size_t close = stat.find_last_of(')');
- if (open != std::string::npos && close != std::string::npos) {
- stat.replace(open + 1, close - open - 1, full_name);
- }
- }
- fputs(stat.c_str(), log);
- }
- }
- }
-
- fputc('\n', log);
-}
-
-static int bootchart_init() {
- int timeout = 0;
-
- std::string start;
- android::base::ReadFileToString(LOG_STARTFILE, &start);
- if (!start.empty()) {
- timeout = atoi(start.c_str());
- } else {
- // When running with emulator, androidboot.bootchart=<timeout>
- // might be passed by as kernel parameters to specify the bootchart
- // timeout. this is useful when using -wipe-data since the /data
- // partition is fresh.
+ // /proc/<pid>/stat only has truncated task names, so get the full
+ // name from /proc/<pid>/cmdline.
std::string cmdline;
- const char* s;
- android::base::ReadFileToString("/proc/cmdline", &cmdline);
-#define KERNEL_OPTION "androidboot.bootchart="
- if ((s = strstr(cmdline.c_str(), KERNEL_OPTION)) != NULL) {
- timeout = atoi(s + sizeof(KERNEL_OPTION) - 1);
+ android::base::ReadFileToString(StringPrintf("/proc/%d/cmdline", pid), &cmdline);
+ const char* full_name = cmdline.c_str(); // So we stop at the first NUL.
+
+ // Read process stat line.
+ std::string stat;
+ if (android::base::ReadFileToString(StringPrintf("/proc/%d/stat", pid), &stat)) {
+ if (!cmdline.empty()) {
+ // Substitute the process name with its real name.
+ size_t open = stat.find('(');
+ size_t close = stat.find_last_of(')');
+ if (open != std::string::npos && close != std::string::npos) {
+ stat.replace(open + 1, close - open - 1, full_name);
+ }
+ }
+ fputs(stat.c_str(), g_proc_log);
}
}
- if (timeout == 0)
+
+ fputc('\n', g_proc_log);
+}
+
+static int do_bootchart_start() {
+ // We don't care about the content, but we do care that /data/bootchart/enabled actually exists.
+ std::string start;
+ if (!android::base::ReadFileToString("/data/bootchart/enabled", &start)) {
+ LOG(VERBOSE) << "Not bootcharting";
return 0;
+ }
- if (timeout > BOOTCHART_MAX_TIME_SEC)
- timeout = BOOTCHART_MAX_TIME_SEC;
-
- int count = (timeout*1000 + BOOTCHART_POLLING_MS-1)/BOOTCHART_POLLING_MS;
-
- log_stat = fopen(LOG_STAT, "we");
- if (log_stat == NULL) {
+ // Open log files.
+ std::unique_ptr<FILE, decltype(&fclose)> stat_log(fopen(LOG_STAT, "we"), fclose);
+ if (!stat_log) {
+ PLOG(ERROR) << "Bootcharting couldn't open " << LOG_STAT;
return -1;
}
- log_procs = fopen(LOG_PROCS, "we");
- if (log_procs == NULL) {
- fclose(log_stat);
+ std::unique_ptr<FILE, decltype(&fclose)> proc_log(fopen(LOG_PROC, "we"), fclose);
+ if (!proc_log) {
+ PLOG(ERROR) << "Bootcharting couldn't open " << LOG_PROC;
return -1;
}
- log_disks = fopen(LOG_DISK, "we");
- if (log_disks == NULL) {
- fclose(log_stat);
- fclose(log_procs);
+ std::unique_ptr<FILE, decltype(&fclose)> disk_log(fopen(LOG_DISK, "we"), fclose);
+ if (!disk_log) {
+ PLOG(ERROR) << "Bootcharting couldn't open " << LOG_DISK;
return -1;
}
- // Create kernel process accounting file.
- close(open(LOG_ACCT, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644));
- acct(LOG_ACCT);
-
+ LOG(INFO) << "Bootcharting started";
+ g_stat_log = stat_log.release();
+ g_proc_log = proc_log.release();
+ g_disk_log = disk_log.release();
+ g_bootcharting = true;
log_header();
- return count;
-}
-
-int do_bootchart_init(const std::vector<std::string>& args) {
- g_remaining_samples = bootchart_init();
- if (g_remaining_samples < 0) {
- PLOG(ERROR) << "Bootcharting initialization failed";
- } else if (g_remaining_samples > 0) {
- LOG(INFO) << "Bootcharting started (will run for "
- << ((g_remaining_samples * BOOTCHART_POLLING_MS) / 1000) << " s).";
- } else {
- LOG(VERBOSE) << "Not bootcharting.";
- }
- return 0;
-}
-
-static int bootchart_step() {
- do_log_file(log_stat, "/proc/stat");
- do_log_file(log_disks, "/proc/diskstats");
- do_log_procs(log_procs);
-
- // Stop if /data/bootchart/stop contains 1.
- std::string stop;
- if (android::base::ReadFileToString(LOG_STOPFILE, &stop) && stop == "1") {
- return -1;
- }
return 0;
}
-/* called to get time (in ms) used by bootchart */
-static long long bootchart_gettime() {
- return 10LL*get_uptime_jiffies();
+static void do_bootchart_step() {
+ log_file(g_stat_log, "/proc/stat");
+ log_file(g_disk_log, "/proc/diskstats");
+ log_processes();
}
-static void bootchart_finish() {
- unlink(LOG_STOPFILE);
- fclose(log_stat);
- fclose(log_disks);
- fclose(log_procs);
- acct(NULL);
+static int do_bootchart_stop() {
+ if (!g_bootcharting) return 0;
+
LOG(INFO) << "Bootcharting finished";
+ g_bootcharting = false;
+ fclose(g_stat_log);
+ fclose(g_disk_log);
+ fclose(g_proc_log);
+ return 0;
+}
+
+int do_bootchart(const std::vector<std::string>& args) {
+ if (args[1] == "start") return do_bootchart_start();
+ return do_bootchart_stop();
}
void bootchart_sample(int* timeout) {
// Do we have any more bootcharting to do?
- if (g_remaining_samples <= 0) {
- return;
- }
+ if (!g_bootcharting) return;
- long long current_time = bootchart_gettime();
+ long long current_time = 10LL * get_uptime_jiffies();
int elapsed_time = current_time - g_last_bootchart_time;
if (elapsed_time >= BOOTCHART_POLLING_MS) {
- // Count missed samples.
while (elapsed_time >= BOOTCHART_POLLING_MS) {
elapsed_time -= BOOTCHART_POLLING_MS;
- g_remaining_samples--;
}
- // Count may be negative, take a sample anyway.
+
g_last_bootchart_time = current_time;
- if (bootchart_step() < 0 || g_remaining_samples <= 0) {
- bootchart_finish();
- g_remaining_samples = 0;
- }
+ do_bootchart_step();
}
- if (g_remaining_samples > 0) {
+
+ // Schedule another?
+ if (g_bootcharting) {
int remaining_time = BOOTCHART_POLLING_MS - elapsed_time;
if (*timeout < 0 || *timeout > remaining_time) {
*timeout = remaining_time;
diff --git a/init/bootchart.h b/init/bootchart.h
index 47eda7a..1e8d0f8 100644
--- a/init/bootchart.h
+++ b/init/bootchart.h
@@ -20,7 +20,7 @@
#include <string>
#include <vector>
-int do_bootchart_init(const std::vector<std::string>& args);
+int do_bootchart(const std::vector<std::string>& args);
void bootchart_sample(int* timeout);
#endif /* _BOOTCHART_H */
diff --git a/init/builtins.cpp b/init/builtins.cpp
index cf8b274..812ac3c 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1028,7 +1028,7 @@
BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
static const Map builtin_functions = {
- {"bootchart_init", {0, 0, do_bootchart_init}},
+ {"bootchart", {1, 1, do_bootchart}},
{"chmod", {2, 2, do_chmod}},
{"chown", {2, 3, do_chown}},
{"class_reset", {1, 1, do_class_reset}},
diff --git a/init/grab-bootchart.sh b/init/grab-bootchart.sh
index d6082aa..c4ff6df 100755
--- a/init/grab-bootchart.sh
+++ b/init/grab-bootchart.sh
@@ -11,7 +11,7 @@
LOGROOT=/data/bootchart
TARBALL=bootchart.tgz
-FILES="header proc_stat.log proc_ps.log proc_diskstats.log kernel_pacct"
+FILES="header proc_stat.log proc_ps.log proc_diskstats.log"
for f in $FILES; do
adb "${@}" pull $LOGROOT/$f $TMPDIR/$f 2>&1 > /dev/null
diff --git a/init/readme.txt b/init/readme.txt
index 6f40d6b..530b392 100644
--- a/init/readme.txt
+++ b/init/readme.txt
@@ -253,9 +253,10 @@
Commands
--------
-bootchart_init
- Start bootcharting if configured (see below).
- This is included in the default init.rc.
+bootchart [start|stop]
+ Start/stop bootcharting. These are present in the default init.rc files,
+ but bootcharting is only active if the file /data/bootchart/enabled exists;
+ otherwise bootchart start/stop are no-ops.
chmod <octal-mode> <path>
Change file access permissions.
@@ -471,19 +472,11 @@
On the emulator, use the -bootchart <timeout> option to boot with bootcharting
activated for <timeout> seconds.
-On a device, create /data/bootchart/start with a command like the following:
+On a device:
- adb shell 'echo $TIMEOUT > /data/bootchart/start'
+ adb shell 'touch /data/bootchart/enabled'
-Where the value of $TIMEOUT corresponds to the desired bootcharted period in
-seconds. Bootcharting will stop after that many seconds have elapsed.
-You can also stop the bootcharting at any moment by doing the following:
-
- adb shell 'echo 1 > /data/bootchart/stop'
-
-Note that /data/bootchart/stop is deleted automatically by init at the end of
-the bootcharting. This is not the case with /data/bootchart/start, so don't
-forget to delete it when you're done collecting data.
+Don't forget to delete this file when you're done collecting data!
The log files are written to /data/bootchart/. A script is provided to
retrieve them and create a bootchart.tgz file that can be used with the
diff --git a/libbacktrace/BacktraceOffline.cpp b/libbacktrace/BacktraceOffline.cpp
index 9e95563..5e54328 100644
--- a/libbacktrace/BacktraceOffline.cpp
+++ b/libbacktrace/BacktraceOffline.cpp
@@ -34,6 +34,7 @@
#include <vector>
#include <android-base/file.h>
+#include <android-base/macros.h>
#include <backtrace/Backtrace.h>
#include <backtrace/BacktraceMap.h>
#include <ziparchive/zip_archive.h>
@@ -534,6 +535,10 @@
default:
result = false;
}
+#else
+ UNUSED(reg);
+ UNUSED(value);
+ result = false;
#endif
return result;
}
diff --git a/libcutils/fs_config.c b/libcutils/fs_config.c
index 99f97d1..a99810b 100644
--- a/libcutils/fs_config.c
+++ b/libcutils/fs_config.c
@@ -149,6 +149,10 @@
"system/bin/run-as" },
{ 00700, AID_SYSTEM, AID_SHELL, CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
"system/bin/inputflinger" },
+ { 00750, AID_SYSTEM, AID_SHELL, CAP_MASK_LONG(CAP_SETUID) |
+ CAP_MASK_LONG(CAP_SETGID) |
+ CAP_MASK_LONG(CAP_SYS_PTRACE),
+ "system/bin/storaged" },
/* Support FIFO scheduling mode in SurfaceFlinger. */
{ 00755, AID_SYSTEM, AID_GRAPHICS, CAP_MASK_LONG(CAP_SYS_NICE),
diff --git a/libcutils/multiuser.c b/libcutils/multiuser.c
index 0f4427b..0ef337d 100644
--- a/libcutils/multiuser.c
+++ b/libcutils/multiuser.c
@@ -15,21 +15,36 @@
*/
#include <cutils/multiuser.h>
+#include <private/android_filesystem_config.h>
userid_t multiuser_get_user_id(uid_t uid) {
- return uid / MULTIUSER_APP_PER_USER_RANGE;
+ return uid / AID_USER_OFFSET;
}
appid_t multiuser_get_app_id(uid_t uid) {
- return uid % MULTIUSER_APP_PER_USER_RANGE;
+ return uid % AID_USER_OFFSET;
}
-uid_t multiuser_get_uid(userid_t userId, appid_t appId) {
- return userId * MULTIUSER_APP_PER_USER_RANGE + (appId % MULTIUSER_APP_PER_USER_RANGE);
+uid_t multiuser_get_uid(userid_t user_id, appid_t app_id) {
+ return (user_id * AID_USER_OFFSET) + (app_id % AID_USER_OFFSET);
}
-appid_t multiuser_get_shared_app_gid(uid_t id) {
- return MULTIUSER_FIRST_SHARED_APPLICATION_GID + (id % MULTIUSER_APP_PER_USER_RANGE)
- - MULTIUSER_FIRST_APPLICATION_UID;
+gid_t multiuser_get_cache_gid(userid_t user_id, appid_t app_id) {
+ if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
+ return multiuser_get_uid(user_id, (app_id - AID_APP_START) + AID_CACHE_GID_START);
+ } else {
+ return -1;
+ }
+}
+gid_t multiuser_get_shared_gid(userid_t user_id, appid_t app_id) {
+ if (app_id >= AID_APP_START && app_id <= AID_APP_END) {
+ return multiuser_get_uid(user_id, (app_id - AID_APP_START) + AID_SHARED_GID_START);
+ } else {
+ return -1;
+ }
+}
+
+gid_t multiuser_get_shared_app_gid(uid_t uid) {
+ return multiuser_get_shared_gid(multiuser_get_user_id(uid), multiuser_get_app_id(uid));
}
diff --git a/libcutils/tests/Android.bp b/libcutils/tests/Android.bp
index abe3c21..0b0dc09 100644
--- a/libcutils/tests/Android.bp
+++ b/libcutils/tests/Android.bp
@@ -26,7 +26,8 @@
"trace-dev_test.cpp",
"test_str_parms.cpp",
"android_get_control_socket_test.cpp",
- "android_get_control_file_test.cpp"
+ "android_get_control_file_test.cpp",
+ "multiuser_test.cpp"
],
},
diff --git a/libcutils/tests/multiuser_test.cpp b/libcutils/tests/multiuser_test.cpp
new file mode 100644
index 0000000..2307ea8
--- /dev/null
+++ b/libcutils/tests/multiuser_test.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 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 <cutils/multiuser.h>
+#include <gtest/gtest.h>
+
+TEST(MultiuserTest, TestMerge) {
+ EXPECT_EQ(0, multiuser_get_uid(0, 0));
+ EXPECT_EQ(1000, multiuser_get_uid(0, 1000));
+ EXPECT_EQ(10000, multiuser_get_uid(0, 10000));
+ EXPECT_EQ(50000, multiuser_get_uid(0, 50000));
+ EXPECT_EQ(1000000, multiuser_get_uid(10, 0));
+ EXPECT_EQ(1001000, multiuser_get_uid(10, 1000));
+ EXPECT_EQ(1010000, multiuser_get_uid(10, 10000));
+ EXPECT_EQ(1050000, multiuser_get_uid(10, 50000));
+}
+
+TEST(MultiuserTest, TestSplitUser) {
+ EXPECT_EQ(0, multiuser_get_user_id(0));
+ EXPECT_EQ(0, multiuser_get_user_id(1000));
+ EXPECT_EQ(0, multiuser_get_user_id(10000));
+ EXPECT_EQ(0, multiuser_get_user_id(50000));
+ EXPECT_EQ(10, multiuser_get_user_id(1000000));
+ EXPECT_EQ(10, multiuser_get_user_id(1001000));
+ EXPECT_EQ(10, multiuser_get_user_id(1010000));
+ EXPECT_EQ(10, multiuser_get_user_id(1050000));
+}
+
+TEST(MultiuserTest, TestSplitApp) {
+ EXPECT_EQ(0, multiuser_get_app_id(0));
+ EXPECT_EQ(1000, multiuser_get_app_id(1000));
+ EXPECT_EQ(10000, multiuser_get_app_id(10000));
+ EXPECT_EQ(50000, multiuser_get_app_id(50000));
+ EXPECT_EQ(0, multiuser_get_app_id(1000000));
+ EXPECT_EQ(1000, multiuser_get_app_id(1001000));
+ EXPECT_EQ(10000, multiuser_get_app_id(1010000));
+ EXPECT_EQ(50000, multiuser_get_app_id(1050000));
+}
+
+TEST(MultiuserTest, TestCache) {
+ EXPECT_EQ(-1, multiuser_get_cache_gid(0, 0));
+ EXPECT_EQ(-1, multiuser_get_cache_gid(0, 1000));
+ EXPECT_EQ(20000, multiuser_get_cache_gid(0, 10000));
+ EXPECT_EQ(-1, multiuser_get_cache_gid(0, 50000));
+ EXPECT_EQ(1020000, multiuser_get_cache_gid(10, 10000));
+}
+
+TEST(MultiuserTest, TestShared) {
+ EXPECT_EQ(-1, multiuser_get_shared_gid(0, 0));
+ EXPECT_EQ(-1, multiuser_get_shared_gid(0, 1000));
+ EXPECT_EQ(50000, multiuser_get_shared_gid(0, 10000));
+ EXPECT_EQ(-1, multiuser_get_shared_gid(0, 50000));
+ EXPECT_EQ(1050000, multiuser_get_shared_gid(10, 10000));
+}
diff --git a/liblog/event_tag_map.cpp b/liblog/event_tag_map.cpp
index e8e0335..1155422 100644
--- a/liblog/event_tag_map.cpp
+++ b/liblog/event_tag_map.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2007-2016 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.
@@ -24,362 +24,106 @@
#include <string.h>
#include <sys/mman.h>
-#include <android/log.h>
+#include <experimental/string_view>
+#include <functional>
+#include <unordered_map>
+
#include <log/event_tag_map.h>
#include "log_portability.h"
#define OUT_TAG "EventTagMap"
-/*
- * Single entry.
- */
-typedef struct EventTag {
- uint32_t tagIndex;
- char* tagStr;
- size_t tagLen;
- char* fmtStr;
- size_t fmtLen;
-} EventTag;
+typedef std::experimental::string_view MapString;
-/*
- * Map.
- */
-struct EventTagMap {
- /* memory-mapped source file; we get strings from here */
- void* mapAddr;
- size_t mapLen;
+typedef std::pair<MapString, MapString> TagFmt;
- /* array of event tags, sorted numerically by tag index */
- EventTag* tagArray;
- int numTags;
+template <> struct _LIBCPP_TYPE_VIS_ONLY std::hash<TagFmt>
+ : public std::unary_function<const TagFmt&, size_t> {
+ _LIBCPP_INLINE_VISIBILITY
+ size_t operator()(const TagFmt& __t) const _NOEXCEPT {
+ // Tag is typically unique. Will cost us an extra 100ns for the
+ // unordered_map lookup if we instead did a hash that combined
+ // both of tag and fmt members, e.g.:
+ //
+ // return std::hash<MapString>()(__t.first) ^
+ // std::hash<MapString>()(__t.second);
+ return std::hash<MapString>()(__t.first);
+ }
};
-/* fwd */
-static int processFile(EventTagMap* map);
-static int countMapLines(const EventTagMap* map);
-static int parseMapLines(EventTagMap* map);
-static int scanTagLine(char** pData, EventTag* tag, int lineNum);
-static int sortTags(EventTagMap* map);
+// Map
+struct EventTagMap {
+ // memory-mapped source file; we get strings from here
+ void* mapAddr;
+ size_t mapLen;
-/*
- * Open the map file and allocate a structure to manage it.
- *
- * We create a private mapping because we want to terminate the log tag
- * strings with '\0'.
- */
-LIBLOG_ABI_PUBLIC EventTagMap* android_openEventTagMap(const char* fileName)
-{
- EventTagMap* newTagMap;
- off_t end;
- int save_errno;
- const char* tagfile = fileName ? fileName : EVENT_TAG_MAP_FILE;
+private:
+ std::unordered_map<uint32_t, TagFmt> Idx2TagFmt;
- int fd = open(tagfile, O_RDONLY | O_CLOEXEC);
- if (fd < 0) {
- save_errno = errno;
- fprintf(stderr, "%s: unable to open map '%s': %s\n",
- OUT_TAG, tagfile, strerror(save_errno));
- goto fail_errno;
- }
+public:
+ EventTagMap() : mapAddr(NULL), mapLen(0) { }
- end = lseek(fd, 0L, SEEK_END);
- save_errno = errno;
- (void) lseek(fd, 0L, SEEK_SET);
- if (end < 0) {
- fprintf(stderr, "%s: unable to seek map '%s' %s\n",
- OUT_TAG, tagfile, strerror(save_errno));
- goto fail_close;
- }
-
- newTagMap = (EventTagMap*)calloc(1, sizeof(EventTagMap));
- if (newTagMap == NULL) {
- save_errno = errno;
- goto fail_close;
- }
-
- newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
- save_errno = errno;
- close(fd);
- fd = -1;
- if ((newTagMap->mapAddr == MAP_FAILED) || (newTagMap->mapAddr == NULL)) {
- fprintf(stderr, "%s: mmap(%s) failed: %s\n",
- OUT_TAG, tagfile, strerror(save_errno));
- goto fail_free;
- }
-
- newTagMap->mapLen = end;
-
- if (processFile(newTagMap) != 0) goto fail_unmap;
-
- return newTagMap;
-
-fail_unmap:
- munmap(newTagMap->mapAddr, newTagMap->mapLen);
- save_errno = EINVAL;
-fail_free:
- free(newTagMap);
-fail_close:
- close(fd);
-fail_errno:
- errno = save_errno;
-fail:
- return NULL;
-}
-
-/*
- * Close the map.
- */
-LIBLOG_ABI_PUBLIC void android_closeEventTagMap(EventTagMap* map)
-{
- if (map == NULL) return;
-
- munmap(map->mapAddr, map->mapLen);
- free(map->tagArray);
- free(map);
-}
-
-/*
- * Look up an entry in the map.
- *
- * The entries are sorted by tag number, so we can do a binary search.
- */
-LIBLOG_ABI_PUBLIC const char* android_lookupEventTag_len(const EventTagMap* map,
- size_t *len,
- unsigned int tag)
-{
- int lo = 0;
- int hi = map->numTags - 1;
-
- while (lo <= hi) {
- int mid = (lo + hi) / 2;
- int cmp = map->tagArray[mid].tagIndex - tag;
-
- if (cmp < 0) {
- /* tag is bigger */
- lo = mid + 1;
- } else if (cmp > 0) {
- /* tag is smaller */
- hi = mid - 1;
- } else {
- /* found */
- if (len) *len = map->tagArray[mid].tagLen;
- /*
- * b/31456426 to check if gTest can detect copy-on-write issue
- * add the following line to break us:
- * map->tagArray[mid].tagStr[map->tagArray[mid].tagLen] = '\0';
- * or explicitly use deprecated android_lookupEventTag().
- */
- return map->tagArray[mid].tagStr;
+ ~EventTagMap() {
+ Idx2TagFmt.clear();
+ if (mapAddr) {
+ munmap(mapAddr, mapLen);
+ mapAddr = 0;
}
}
- errno = ENOENT;
- if (len) *len = 0;
- return NULL;
-}
+ bool emplaceUnique(uint32_t tag, const TagFmt& tagfmt, bool verbose = false);
+ const TagFmt* find(uint32_t tag) const;
+};
-/*
- * Look up an entry in the map.
- *
- * The entries are sorted by tag number, so we can do a binary search.
- */
-LIBLOG_ABI_PUBLIC const char* android_lookupEventFormat_len(
- const EventTagMap* map, size_t *len, unsigned int tag)
-{
- int lo = 0;
- int hi = map->numTags - 1;
-
- while (lo <= hi) {
- int mid = (lo + hi) / 2;
- int cmp = map->tagArray[mid].tagIndex - tag;
-
- if (cmp < 0) {
- /* tag is bigger */
- lo = mid + 1;
- } else if (cmp > 0) {
- /* tag is smaller */
- hi = mid - 1;
- } else {
- /* found */
- if (len) *len = map->tagArray[mid].fmtLen;
- return map->tagArray[mid].fmtStr;
+bool EventTagMap::emplaceUnique(uint32_t tag, const TagFmt& tagfmt, bool verbose) {
+ std::unordered_map<uint32_t, TagFmt>::const_iterator it;
+ it = Idx2TagFmt.find(tag);
+ if (it != Idx2TagFmt.end()) {
+ if (verbose) {
+ fprintf(stderr,
+ OUT_TAG ": duplicate tag entries %" PRIu32
+ ":%.*s:%.*s and %" PRIu32 ":%.*s:%.*s)\n",
+ it->first,
+ (int)it->second.first.length(), it->second.first.data(),
+ (int)it->second.second.length(), it->second.second.data(),
+ tag,
+ (int)tagfmt.first.length(), tagfmt.first.data(),
+ (int)tagfmt.second.length(), tagfmt.second.data());
}
+ return false;
}
- errno = ENOENT;
- if (len) *len = 0;
- return NULL;
+ Idx2TagFmt.emplace(std::make_pair(tag, tagfmt));
+ return true;
}
-LIBLOG_ABI_PUBLIC const char* android_lookupEventTag(const EventTagMap* map,
- unsigned int tag)
-{
- size_t len;
- const char* tagStr = android_lookupEventTag_len(map, &len, tag);
+const TagFmt* EventTagMap::find(uint32_t tag) const {
+ std::unordered_map<uint32_t, TagFmt>::const_iterator it;
+ it = Idx2TagFmt.find(tag);
+ if (it == Idx2TagFmt.end()) return NULL;
+ return &(it->second);
+}
+
+// Scan one tag line.
+//
+// "*pData" should be pointing to the first digit in the tag number. On
+// successful return, it will be pointing to the last character in the
+// tag line (i.e. the character before the start of the next line).
+//
+// Returns 0 on success, nonzero on failure.
+static int scanTagLine(EventTagMap* map, char** pData, int lineNum) {
char* cp;
-
- if (!tagStr) return tagStr;
- cp = (char*)tagStr;
- cp += len;
- if (*cp) *cp = '\0'; /* Trigger copy on write :-( */
- return tagStr;
-}
-
-/*
- * Crunch through the file, parsing the contents and creating a tag index.
- */
-static int processFile(EventTagMap* map)
-{
- /* get a tag count */
- map->numTags = countMapLines(map);
- if (map->numTags < 0) {
- errno = ENOENT;
- return -1;
- }
-
- /* allocate storage for the tag index array */
- map->tagArray = (EventTag*)calloc(1, sizeof(EventTag) * map->numTags);
- if (map->tagArray == NULL) return -1;
-
- /* parse the file, null-terminating tag strings */
- if (parseMapLines(map) != 0) return -1;
-
- /* sort the tags and check for duplicates */
- if (sortTags(map) != 0) return -1;
-
- return 0;
-}
-
-/*
- * Run through all lines in the file, determining whether they're blank,
- * comments, or possibly have a tag entry.
- *
- * This is a very "loose" scan. We don't try to detect syntax errors here.
- * The later pass is more careful, but the number of tags found there must
- * match the number of tags found here.
- *
- * Returns the number of potential tag entries found.
- */
-static int countMapLines(const EventTagMap* map)
-{
- const char* cp = (const char*) map->mapAddr;
- const char* endp = cp + map->mapLen;
- int numTags = 0;
- int unknown = 1;
-
- while (cp < endp) {
- if (*cp == '\n') {
- unknown = 1;
- } else if (unknown) {
- if (isdigit(*cp)) {
- /* looks like a tag to me */
- numTags++;
- unknown = 0;
- } else if (isspace(*cp)) {
- /* might be leading whitespace before tag num, keep going */
- } else {
- /* assume comment; second pass can complain in detail */
- unknown = 0;
- }
- } else {
- /* we've made up our mind; just scan to end of line */
- }
- cp++;
- }
-
- return numTags;
-}
-
-/*
- * Parse the tags out of the file.
- */
-static int parseMapLines(EventTagMap* map)
-{
- int tagNum, lineStart, lineNum;
- char* cp = (char*) map->mapAddr;
- char* endp = cp + map->mapLen;
-
- /* insist on EOL at EOF; simplifies parsing and null-termination */
- if (*(endp - 1) != '\n') {
- fprintf(stderr, "%s: map file missing EOL on last line\n", OUT_TAG);
- errno = EINVAL;
- return -1;
- }
-
- tagNum = 0;
- lineStart = 1;
- lineNum = 1;
- while (cp < endp) {
- if (*cp == '\n') {
- lineStart = 1;
- lineNum++;
- } else if (lineStart) {
- if (*cp == '#') {
- /* comment; just scan to end */
- lineStart = 0;
- } else if (isdigit(*cp)) {
- /* looks like a tag; scan it out */
- if (tagNum >= map->numTags) {
- fprintf(stderr,
- "%s: more tags than expected (%d)\n", OUT_TAG, tagNum);
- errno = EMFILE;
- return -1;
- }
- if (scanTagLine(&cp, &map->tagArray[tagNum], lineNum) != 0) {
- return -1;
- }
- tagNum++;
- lineNum++; // we eat the '\n'
- /* leave lineStart==1 */
- } else if (isspace(*cp)) {
- /* looks like leading whitespace; keep scanning */
- } else {
- fprintf(stderr,
- "%s: unexpected chars (0x%02x) in tag number on line %d\n",
- OUT_TAG, *cp, lineNum);
- errno = EINVAL;
- return -1;
- }
- } else {
- /* this is a blank or comment line */
- }
- cp++;
- }
-
- if (tagNum != map->numTags) {
- fprintf(stderr, "%s: parsed %d tags, expected %d\n",
- OUT_TAG, tagNum, map->numTags);
- errno = EINVAL;
- return -1;
- }
-
- return 0;
-}
-
-/*
- * Scan one tag line.
- *
- * "*pData" should be pointing to the first digit in the tag number. On
- * successful return, it will be pointing to the last character in the
- * tag line (i.e. the character before the start of the next line).
- *
- * Returns 0 on success, nonzero on failure.
- */
-static int scanTagLine(char** pData, EventTag* tag, int lineNum)
-{
- char* cp;
-
unsigned long val = strtoul(*pData, &cp, 10);
if (cp == *pData) {
- fprintf(stderr, "%s: malformed tag number on line %d\n", OUT_TAG, lineNum);
+ fprintf(stderr, OUT_TAG ": malformed tag number on line %d\n", lineNum);
errno = EINVAL;
return -1;
}
- tag->tagIndex = val;
- if (tag->tagIndex != val) {
- fprintf(stderr, "%s: tag number too large on line %d\n", OUT_TAG, lineNum);
+ uint32_t tagIndex = val;
+ if (tagIndex != val) {
+ fprintf(stderr, OUT_TAG ": tag number too large on line %d\n", lineNum);
errno = ERANGE;
return -1;
}
@@ -388,76 +132,192 @@
}
if (*cp == '\n') {
- fprintf(stderr, "%s: missing tag string on line %d\n", OUT_TAG, lineNum);
+ fprintf(stderr, OUT_TAG ": missing tag string on line %d\n", lineNum);
errno = EINVAL;
return -1;
}
- tag->tagStr = cp;
-
- /* Determine whether "c" is a valid tag char. */
- while (isalnum(*++cp) || (*cp == '_')) {
- }
- tag->tagLen = cp - tag->tagStr;
+ const char* tag = cp;
+ // Determine whether "c" is a valid tag char.
+ while (isalnum(*++cp) || (*cp == '_')) { }
+ size_t tagLen = cp - tag;
if (!isspace(*cp)) {
- fprintf(stderr, "%s: invalid tag chars on line %d\n", OUT_TAG, lineNum);
+ fprintf(stderr, OUT_TAG ": invalid tag chars on line %d\n", lineNum);
errno = EINVAL;
return -1;
}
while (isspace(*cp) && (*cp != '\n')) ++cp;
+ const char* fmt = NULL;
+ size_t fmtLen = 0;
if (*cp != '#') {
- tag->fmtStr = cp;
+ fmt = cp;
while ((*cp != '\n') && (*cp != '#')) ++cp;
- while ((cp > tag->fmtStr) && isspace(*(cp - 1))) --cp;
- tag->fmtLen = cp - tag->fmtStr;
+ while ((cp > fmt) && isspace(*(cp - 1))) --cp;
+ fmtLen = cp - fmt;
}
while (*cp != '\n') ++cp;
+#ifdef DEBUG
+ fprintf(stderr, "%d: %p: %.*s\n", lineNum, tag, (int)(cp - *pData), *pData);
+#endif
*pData = cp;
- return 0;
+ if (map->emplaceUnique(tagIndex, TagFmt(std::make_pair(
+ MapString(tag, tagLen), MapString(fmt, fmtLen))), true)) {
+ return 0;
+ }
+ errno = EMLINK;
+ return -1;
}
-/*
- * Compare two EventTags.
- */
-static int compareEventTags(const void* v1, const void* v2)
-{
- const EventTag* tag1 = (const EventTag*) v1;
- const EventTag* tag2 = (const EventTag*) v2;
+// Parse the tags out of the file.
+static int parseMapLines(EventTagMap* map) {
+ char* cp = static_cast<char*>(map->mapAddr);
+ size_t len = map->mapLen;
+ char* endp = cp + len;
- return tag1->tagIndex - tag2->tagIndex;
-}
+ // insist on EOL at EOF; simplifies parsing and null-termination
+ if (!len || (*(endp - 1) != '\n')) {
+#ifdef DEBUG
+ fprintf(stderr, OUT_TAG ": map file missing EOL on last line\n");
+#endif
+ errno = EINVAL;
+ return -1;
+ }
-/*
- * Sort the EventTag array so we can do fast lookups by tag index. After
- * the sort we do a quick check for duplicate tag indices.
- *
- * Returns 0 on success.
- */
-static int sortTags(EventTagMap* map)
-{
- int i;
-
- qsort(map->tagArray, map->numTags, sizeof(EventTag), compareEventTags);
-
- for (i = 1; i < map->numTags; i++) {
- if (map->tagArray[i].tagIndex == map->tagArray[i - 1].tagIndex) {
- fprintf(stderr,
- "%s: duplicate tag entries (%" PRIu32 ":%.*s:%.*s and %" PRIu32 ":%.*s:%.*s)\n",
- OUT_TAG,
- map->tagArray[i].tagIndex,
- (int)map->tagArray[i].tagLen, map->tagArray[i].tagStr,
- (int)map->tagArray[i].fmtLen, map->tagArray[i].fmtStr,
- map->tagArray[i - 1].tagIndex,
- (int)map->tagArray[i - 1].tagLen, map->tagArray[i - 1].fmtStr,
- (int)map->tagArray[i - 1].fmtLen, map->tagArray[i - 1].fmtStr);
- errno = EMLINK;
- return -1;
+ bool lineStart = true;
+ int lineNum = 1;
+ while (cp < endp) {
+ if (*cp == '\n') {
+ lineStart = true;
+ lineNum++;
+ } else if (lineStart) {
+ if (*cp == '#') {
+ // comment; just scan to end
+ lineStart = false;
+ } else if (isdigit(*cp)) {
+ // looks like a tag; scan it out
+ if (scanTagLine(map, &cp, lineNum) != 0) {
+ return -1;
+ }
+ lineNum++; // we eat the '\n'
+ // leave lineStart==true
+ } else if (isspace(*cp)) {
+ // looks like leading whitespace; keep scanning
+ } else {
+ fprintf(stderr,
+ OUT_TAG ": unexpected chars (0x%02x) in tag number on line %d\n",
+ *cp, lineNum);
+ errno = EINVAL;
+ return -1;
+ }
+ } else {
+ // this is a blank or comment line
}
+ cp++;
}
return 0;
}
+
+// Open the map file and allocate a structure to manage it.
+//
+// We create a private mapping because we want to terminate the log tag
+// strings with '\0'.
+LIBLOG_ABI_PUBLIC EventTagMap* android_openEventTagMap(const char* fileName) {
+ int save_errno;
+
+ const char* tagfile = fileName ? fileName : EVENT_TAG_MAP_FILE;
+ int fd = open(tagfile, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ save_errno = errno;
+ fprintf(stderr, OUT_TAG ": unable to open map '%s': %s\n",
+ tagfile, strerror(save_errno));
+ errno = save_errno;
+ return NULL;
+ }
+ off_t end = lseek(fd, 0L, SEEK_END);
+ save_errno = errno;
+ (void)lseek(fd, 0L, SEEK_SET);
+ if (end < 0) {
+ fprintf(stderr, OUT_TAG ": unable to seek map '%s' %s\n",
+ tagfile, strerror(save_errno));
+ close(fd);
+ errno = save_errno;
+ return NULL;
+ }
+
+ EventTagMap* newTagMap = new EventTagMap;
+ if (newTagMap == NULL) {
+ save_errno = errno;
+ close(fd);
+ errno = save_errno;
+ return NULL;
+ }
+
+ newTagMap->mapAddr = mmap(NULL, end, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ save_errno = errno;
+ close(fd);
+ fd = -1;
+ if ((newTagMap->mapAddr == MAP_FAILED) || (newTagMap->mapAddr == NULL)) {
+ fprintf(stderr, OUT_TAG ": mmap(%s) failed: %s\n",
+ tagfile, strerror(save_errno));
+ delete newTagMap;
+ errno = save_errno;
+ return NULL;
+ }
+
+ newTagMap->mapLen = end;
+
+ if (parseMapLines(newTagMap) != 0) {
+ delete newTagMap;
+ return NULL;
+ }
+
+ return newTagMap;
+}
+
+// Close the map.
+LIBLOG_ABI_PUBLIC void android_closeEventTagMap(EventTagMap* map) {
+ if (map) delete map;
+}
+
+// Look up an entry in the map.
+LIBLOG_ABI_PUBLIC const char* android_lookupEventTag_len(const EventTagMap* map,
+ size_t *len,
+ unsigned int tag) {
+ if (len) *len = 0;
+ const TagFmt* str = map->find(tag);
+ if (!str) return NULL;
+ if (len) *len = str->first.length();
+ return str->first.data();
+}
+
+// Look up an entry in the map.
+LIBLOG_ABI_PUBLIC const char* android_lookupEventFormat_len(
+ const EventTagMap* map, size_t *len, unsigned int tag) {
+ if (len) *len = 0;
+ const TagFmt* str = map->find(tag);
+ if (!str) return NULL;
+ if (len) *len = str->second.length();
+ return str->second.data();
+}
+
+// This function is deprecated and replaced with android_lookupEventTag_len
+// since it will cause the map to change from Shared and backed by a file,
+// to Private Dirty and backed up by swap, albeit highly compressible. By
+// deprecating this function everywhere, we save 100s of MB of memory space.
+LIBLOG_ABI_PUBLIC const char* android_lookupEventTag(const EventTagMap* map,
+ unsigned int tag) {
+ size_t len;
+ const char* tagStr = android_lookupEventTag_len(map, &len, tag);
+
+ if (!tagStr) return tagStr;
+ char* cp = const_cast<char*>(tagStr);
+ cp += len;
+ if (*cp) *cp = '\0'; // Trigger copy on write :-( and why deprecated.
+ return tagStr;
+}
diff --git a/logd/LogStatistics.cpp b/logd/LogStatistics.cpp
index bab87ba..3c8bd75 100644
--- a/logd/LogStatistics.cpp
+++ b/logd/LogStatistics.cpp
@@ -182,7 +182,7 @@
}
// Parse /data/system/packages.list
- uid_t userId = uid % AID_USER;
+ uid_t userId = uid % AID_USER_OFFSET;
const char *name = android::uidToName(userId);
if (!name && (userId > (AID_SHARED_GID_START - AID_APP))) {
name = android::uidToName(userId - (AID_SHARED_GID_START - AID_APP));
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 370b9ad..ccbad4b 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -367,7 +367,7 @@
# Start bootcharting as soon as possible after the data partition is
# mounted to collect more data.
mkdir /data/bootchart 0755 shell shell
- bootchart_init
+ bootchart start
# Avoid predictable entropy pool. Carry over entropy from previous boot.
copy /data/system/entropy.dat /dev/urandom
@@ -625,6 +625,9 @@
on property:sys.powerctl=*
powerctl ${sys.powerctl}
+on property:sys.boot_completed=1
+ bootchart stop
+
# system server cannot write to /proc/sys files,
# and chown/chmod does not work for /proc/sys/ entries.
# So proxy writes through init.
diff --git a/run-as/run-as.cpp b/run-as/run-as.cpp
index aec51f4..e7b8cc2 100644
--- a/run-as/run-as.cpp
+++ b/run-as/run-as.cpp
@@ -165,12 +165,12 @@
if (setegid(old_egid) == -1) error(1, errno, "couldn't restore egid");
// Verify that user id is not too big.
- if ((UID_MAX - info.uid) / AID_USER < (uid_t)userId) {
+ if ((UID_MAX - info.uid) / AID_USER_OFFSET < (uid_t)userId) {
error(1, 0, "user id too big: %d", userId);
}
// Calculate user app ID.
- uid_t userAppId = (AID_USER * userId) + info.uid;
+ uid_t userAppId = (AID_USER_OFFSET * userId) + info.uid;
// Reject system packages.
if (userAppId < AID_APP) {
diff --git a/storaged/Android.mk b/storaged/Android.mk
new file mode 100644
index 0000000..db97040
--- /dev/null
+++ b/storaged/Android.mk
@@ -0,0 +1,34 @@
+# Copyright 2016 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+
+LIBSTORAGED_SHARED_LIBRARIES := libbinder libbase libutils libcutils liblog libsysutils libcap
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := storaged.cpp \
+ storaged_service.cpp \
+ storaged_utils.cpp \
+ EventLogTags.logtags
+LOCAL_MODULE := libstoraged
+LOCAL_CFLAGS := -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include external/googletest/googletest/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SHARED_LIBRARIES := $(LIBSTORAGED_SHARED_LIBRARIES)
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := storaged
+LOCAL_INIT_RC := storaged.rc
+LOCAL_SRC_FILES := main.cpp
+# libstoraged is an internal static library, only main.cpp and storaged_test.cpp should be using it
+LOCAL_STATIC_LIBRARIES := libstoraged
+LOCAL_SHARED_LIBRARIES := $(LIBSTORAGED_SHARED_LIBRARIES)
+LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
+LOCAL_C_INCLUDES := external/googletest/googletest/include
+
+include $(BUILD_EXECUTABLE)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/storaged/EventLogTags.logtags b/storaged/EventLogTags.logtags
new file mode 100644
index 0000000..2e25d4a
--- /dev/null
+++ b/storaged/EventLogTags.logtags
@@ -0,0 +1,39 @@
+# The entries in this file map a sparse set of log tag numbers to tag names.
+# This is installed on the device, in /system/etc, and parsed by logcat.
+#
+# Tag numbers are decimal integers, from 0 to 2^31. (Let's leave the
+# negative values alone for now.)
+#
+# Tag names are one or more ASCII letters and numbers or underscores, i.e.
+# "[A-Z][a-z][0-9]_". Do not include spaces or punctuation (the former
+# impacts log readability, the latter makes regex searches more annoying).
+#
+# Tag numbers and names are separated by whitespace. Blank lines and lines
+# starting with '#' are ignored.
+#
+# Optionally, after the tag names can be put a description for the value(s)
+# of the tag. Description are in the format
+# (<name>|data type[|data unit])
+# Multiple values are separated by commas.
+#
+# The data type is a number from the following values:
+# 1: int
+# 2: long
+# 3: string
+# 4: list
+# 5: float
+#
+# The data unit is a number taken from the following list:
+# 1: Number of objects
+# 2: Number of bytes
+# 3: Number of milliseconds
+# 4: Number of allocations
+# 5: Id
+# 6: Percent
+# Default value for data of type int/long is 2 (bytes).
+#
+# TODO: generate ".java" and ".h" files with integer constants from this file.
+
+2732 storaged_disk_stats (type|3),(start_time|2|3),(end_time|2|3),(read_ios|2|1),(read_merges|2|1),(read_sectors|2|1),(read_ticks|2|3),(write_ios|2|1),(write_merges|2|1),(write_sectors|2|1),(write_ticks|2|3),(o_in_flight|2|1),(io_ticks|2|3),(io_in_queue|2|1)
+
+2733 storaged_emmc_info (mmc_ver|3),(eol|1),(lifetime_a|1),(lifetime_b|1)
\ No newline at end of file
diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h
new file mode 100644
index 0000000..eb827cf
--- /dev/null
+++ b/storaged/include/storaged.h
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2016 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 _STORAGED_H_
+#define _STORAGED_H_
+
+#define DEBUG
+
+#include <queue>
+#include <semaphore.h>
+#include <stdint.h>
+#include <string>
+#include <syslog.h>
+#include <unordered_map>
+#include <vector>
+
+#define FRIEND_TEST(test_case_name, test_name) \
+friend class test_case_name##_##test_name##_Test
+
+/* For debug */
+#ifdef DEBUG
+#define debuginfo(fmt, ...) \
+ do {printf("%s():\t" fmt "\t[%s:%d]\n", __FUNCTION__, ##__VA_ARGS__, __FILE__, __LINE__);} \
+ while(0)
+#else
+#define debuginfo(...)
+#endif
+
+#define KMSG_PRIORITY(PRI) \
+ '<', \
+ '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) / 10, \
+ '0' + LOG_MAKEPRI(LOG_DAEMON, LOG_PRI(PRI)) % 10, \
+ '>'
+
+static char kmsg_error_prefix[] = { KMSG_PRIORITY(LOG_ERR),
+ 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', '\0' };
+
+static char kmsg_info_prefix[] = { KMSG_PRIORITY(LOG_INFO),
+ 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', '\0' };
+
+static char kmsg_warning_prefix[] = { KMSG_PRIORITY(LOG_WARNING),
+ 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', '\0' };
+
+// number of attributes diskstats has
+#define DISK_STATS_SIZE ( 11 )
+// maximum size limit of a stats file
+#define DISK_STATS_FILE_MAX_SIZE ( 256 )
+#define DISK_STATS_IO_IN_FLIGHT_IDX ( 8 )
+struct disk_stats {
+ /* It will be extremely unlikely for any of the following entries to overflow.
+ * For read_bytes(which will be greater than any of the following entries), it
+ * will take 27 years to overflow uint64_t at the reading rate of 20GB/s, which
+ * is the peak memory transfer rate for current memory.
+ * The diskstats entries (first 11) need to be at top in this structure _after_
+ * compiler's optimization.
+ */
+ uint64_t read_ios; // number of read I/Os processed
+ uint64_t read_merges; // number of read I/Os merged with in-queue I/Os
+ uint64_t read_sectors; // number of sectors read
+ uint64_t read_ticks; // total wait time for read requests
+ uint64_t write_ios; // number of write I/Os processed
+ uint64_t write_merges; // number of write I/Os merged with in-queue I/Os
+ uint64_t write_sectors; // number of sectors written
+ uint64_t write_ticks; // total wait time for write requests
+ uint64_t io_in_flight; // number of I/Os currently in flight
+ uint64_t io_ticks; // total time this block device has been active
+ uint64_t io_in_queue; // total wait time for all requests
+
+ uint64_t start_time; // monotonic time accounting starts
+ uint64_t end_time; // monotonic time accounting ends
+ uint32_t counter; // private counter for accumulate calculations
+ double io_avg; // average io_in_flight for accumulate calculations
+};
+
+#define MMC_VER_STR_LEN ( 8 ) // maximum length of the MMC version string
+// minimum size of a ext_csd file
+#define EXT_CSD_FILE_MIN_SIZE ( 1024 )
+struct emmc_info {
+ int eol; // pre-eol (end of life) information
+ int lifetime_a; // device life time estimation (type A)
+ int lifetime_b; // device life time estimation (type B)
+ char mmc_ver[MMC_VER_STR_LEN]; // device version string
+};
+
+struct disk_perf {
+ uint32_t read_perf; // read speed (kbytes/s)
+ uint32_t read_ios; // read I/Os per second
+ uint32_t write_perf; // write speed (kbytes/s)
+ uint32_t write_ios; // write I/Os per second
+ uint32_t queue; // I/Os in queue
+};
+
+#define CMD_MAX_LEN ( 64 )
+struct task_info {
+ uint32_t pid; // task id
+ uint64_t rchar; // characters read
+ uint64_t wchar; // characters written
+ uint64_t syscr; // read syscalls
+ uint64_t syscw; // write syscalls
+ uint64_t read_bytes; // bytes read (from storage layer)
+ uint64_t write_bytes; // bytes written (to storage layer)
+ uint64_t cancelled_write_bytes; // cancelled write byte by truncate
+
+ uint64_t starttime; // start time of task
+
+ char cmd[CMD_MAX_LEN]; // filename of the executable
+};
+
+class lock_t {
+ sem_t* mSem;
+public:
+ lock_t(sem_t* sem) {
+ mSem = sem;
+ sem_wait(mSem);
+ }
+ ~lock_t() {
+ sem_post(mSem);
+ }
+};
+
+class tasks_t {
+private:
+ FRIEND_TEST(storaged_test, tasks_t);
+ sem_t mSem;
+ // hashmap for all running tasks w/ pid as key
+ std::unordered_map<uint32_t, struct task_info> mRunning;
+ // hashmap for all tasks that have been killed (categorized by cmd) w/ cmd as key
+ std::unordered_map<std::string, struct task_info> mOld;
+ std::unordered_map<std::uint32_t, struct task_info> get_running_tasks();
+public:
+ tasks_t() {
+ sem_init(&mSem, 0, 1); // TODO: constructor don't have a return value, what if sem_init fails
+ }
+
+ ~tasks_t() {
+ sem_destroy(&mSem);
+ }
+
+ void update_running_tasks(void);
+ std::vector<struct task_info> get_tasks(void);
+};
+
+class stream_stats {
+private:
+ double mSum;
+ double mSquareSum;
+ uint32_t mCnt;
+public:
+ stream_stats() : mSum(0), mSquareSum(0), mCnt(0) {};
+ ~stream_stats() {};
+ double get_mean() {
+ return mSum / mCnt;
+ }
+ double get_std() {
+ return sqrt(mSquareSum / mCnt - mSum * mSum / (mCnt * mCnt));
+ }
+ void add(uint32_t num) {
+ mSum += (double)num;
+ mSquareSum += (double)num * (double)num;
+ mCnt++;
+ }
+ void evict(uint32_t num) {
+ if (mSum < num || mSquareSum < (double)num * (double)num) return;
+ mSum -= (double)num;
+ mSquareSum -= (double)num * (double)num;
+ mCnt--;
+ }
+};
+
+#define MMC_DISK_STATS_PATH "/sys/block/mmcblk0/stat"
+#define SDA_DISK_STATS_PATH "/sys/block/sda/stat"
+#define EMMC_ECSD_PATH "/d/mmc0/mmc0:0001/ext_csd"
+class disk_stats_monitor {
+private:
+ FRIEND_TEST(storaged_test, disk_stats_monitor);
+ const char* DISK_STATS_PATH;
+ struct disk_stats mPrevious;
+ struct disk_stats mAccumulate;
+ bool mStall;
+ std::queue<struct disk_perf> mBuffer;
+ struct {
+ stream_stats read_perf; // read speed (bytes/s)
+ stream_stats read_ios; // read I/Os per second
+ stream_stats write_perf; // write speed (bytes/s)
+ stream_stats write_ios; // write I/O per second
+ stream_stats queue; // I/Os in queue
+ } mStats;
+ bool mValid;
+ const uint32_t mWindow;
+ const double mSigma;
+ struct disk_perf mMean;
+ struct disk_perf mStd;
+
+ void update_mean();
+ void update_std();
+ void add(struct disk_perf* perf);
+ void evict(struct disk_perf* perf);
+ bool detect(struct disk_perf* perf);
+
+ void update(struct disk_stats* stats);
+
+public:
+ disk_stats_monitor(uint32_t window_size = 5, double sigma = 1.0) :
+ mStall(false),
+ mValid(false),
+ mWindow(window_size),
+ mSigma(sigma) {
+ memset(&mPrevious, 0, sizeof(mPrevious));
+ memset(&mMean, 0, sizeof(mMean));
+ memset(&mStd, 0, sizeof(mStd));
+
+ if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
+ DISK_STATS_PATH = MMC_DISK_STATS_PATH;
+ } else {
+ DISK_STATS_PATH = SDA_DISK_STATS_PATH;
+ }
+ }
+ void update(void);
+};
+
+class disk_stats_publisher {
+private:
+ FRIEND_TEST(storaged_test, disk_stats_publisher);
+ const char* DISK_STATS_PATH;
+ struct disk_stats mAccumulate;
+ struct disk_stats mPrevious;
+public:
+ disk_stats_publisher(void) {
+ memset(&mAccumulate, 0, sizeof(struct disk_stats));
+ memset(&mPrevious, 0, sizeof(struct disk_stats));
+
+ if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
+ DISK_STATS_PATH = MMC_DISK_STATS_PATH;
+ } else {
+ DISK_STATS_PATH = SDA_DISK_STATS_PATH;
+ }
+ }
+
+ ~disk_stats_publisher(void) {}
+ void publish(void);
+ void update(void);
+};
+
+class emmc_info_t {
+private:
+ struct emmc_info mInfo;
+ bool mValid;
+ int mFdEmmc;
+public:
+ emmc_info_t(void) :
+ mValid(false),
+ mFdEmmc(-1) {
+ memset(&mInfo, 0, sizeof(struct emmc_info));
+ }
+ ~emmc_info_t(void) {}
+
+ void publish(void);
+ void update(void);
+ void set_emmc_fd(int fd) {
+ mFdEmmc = fd;
+ }
+};
+
+// Periodic chores intervals in seconds
+#define DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT ( 20 )
+#define DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH ( 60 )
+#define DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH ( 60 * 2 )
+
+struct storaged_config {
+ int periodic_chores_interval_unit;
+ int periodic_chores_interval_disk_stats_publish;
+ int periodic_chores_interval_emmc_info_publish;
+ bool proc_taskio_readable; // are /proc/[pid]/{io, comm, cmdline, stat} all readable
+ bool emmc_available; // whether eMMC est_csd file is readable
+ bool diskstats_available; // whether diskstats is accessible
+};
+
+class storaged_t {
+private:
+ time_t mTimer;
+ storaged_config mConfig;
+ disk_stats_publisher mDiskStats;
+ disk_stats_monitor mDsm;
+ emmc_info_t mEmmcInfo;
+ tasks_t mTasks;
+ time_t mStarttime;
+public:
+ storaged_t(void);
+ ~storaged_t() {}
+ void event(void);
+ void pause(void) {
+ sleep(mConfig.periodic_chores_interval_unit);
+ }
+ void set_unit_interval(int unit) {
+ mConfig.periodic_chores_interval_unit = unit;
+ }
+ void set_diskstats_interval(int disk_stats) {
+ mConfig.periodic_chores_interval_disk_stats_publish = disk_stats;
+ }
+ void set_emmc_interval(int emmc_info) {
+ mConfig.periodic_chores_interval_emmc_info_publish = emmc_info;
+ }
+ std::vector<struct task_info> get_tasks(void) {
+ // There could be a race when get_tasks() and the main thread is updating at the same time
+ // While update_running_tasks() is updating the critical sections at the end of the function
+ // all together atomically, the final state of task_t can only be either the main thread's
+ // update or this update. Since the race can only occur when both threads are updating
+ // "simultaneously", either final state is acceptable.
+ mTasks.update_running_tasks();
+ return mTasks.get_tasks();
+ }
+
+ void set_privileged_fds(int fd_emmc) {
+ mEmmcInfo.set_emmc_fd(fd_emmc);
+ }
+
+ time_t get_starttime(void) {
+ return mStarttime;
+ }
+};
+
+// Eventlog tag
+// The content must match the definition in EventLogTags.logtags
+#define EVENTLOGTAG_DISKSTATS ( 2732 )
+#define EVENTLOGTAG_EMMCINFO ( 2733 )
+
+#endif /* _STORAGED_H_ */
diff --git a/storaged/include/storaged_service.h b/storaged/include/storaged_service.h
new file mode 100644
index 0000000..64a9c81
--- /dev/null
+++ b/storaged/include/storaged_service.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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 _STORAGED_SERVICE_H_
+#define _STORAGED_SERVICE_H_
+
+#include <vector>
+
+#include <binder/IInterface.h>
+#include <binder/IBinder.h>
+
+#include "storaged.h"
+
+using namespace android;
+
+// Interface
+class IStoraged : public IInterface {
+public:
+ enum {
+ DUMPTASKS = IBinder::FIRST_CALL_TRANSACTION,
+ };
+ // Request the service to run the test function
+ virtual std::vector<struct task_info> dump_tasks(const char* option) = 0;
+
+ DECLARE_META_INTERFACE(Storaged);
+};
+
+// Client
+class BpStoraged : public BpInterface<IStoraged> {
+public:
+ BpStoraged(const sp<IBinder>& impl) : BpInterface<IStoraged>(impl){};
+ virtual std::vector<struct task_info> dump_tasks(const char* option);
+};
+
+// Server
+class BnStoraged : public BnInterface<IStoraged> {
+ virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);
+};
+
+class Storaged : public BnStoraged {
+ virtual std::vector<struct task_info> dump_tasks(const char* option);
+};
+
+sp<IStoraged> get_storaged_service();
+
+#endif /* _STORAGED_SERVICE_H_ */
\ No newline at end of file
diff --git a/storaged/include/storaged_utils.h b/storaged/include/storaged_utils.h
new file mode 100644
index 0000000..b0e5146
--- /dev/null
+++ b/storaged/include/storaged_utils.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 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 _STORAGED_UTILS_H_
+#define _STORAGED_UTILS_H_
+
+#include <stdint.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "storaged.h"
+
+// Diskstats
+bool parse_disk_stats(const char* disk_stats_path, struct disk_stats* stats);
+struct disk_perf get_disk_perf(struct disk_stats* stats);
+struct disk_stats get_inc_disk_stats(struct disk_stats* prev, struct disk_stats* curr);
+void add_disk_stats(struct disk_stats* src, struct disk_stats* dst);
+bool parse_emmc_ecsd(int ext_csd_fd, struct emmc_info* info);
+
+// Task I/O
+bool parse_task_info(uint32_t pid, struct task_info* info);
+void sort_running_tasks_info(std::vector<struct task_info> &tasks);
+
+// Logging
+void log_console_running_tasks_info(std::vector<struct task_info> tasks);
+void log_kernel_disk_stats(struct disk_stats* stats, const char* type);
+void log_kernel_disk_perf(struct disk_perf* perf, const char* type);
+void log_kernel_emmc_info(struct emmc_info* info);
+
+void log_event_disk_stats(struct disk_stats* stats, const char* type);
+void log_event_emmc_info(struct emmc_info* info_);
+#endif /* _STORAGED_UTILS_H_ */
\ No newline at end of file
diff --git a/storaged/main.cpp b/storaged/main.cpp
new file mode 100644
index 0000000..bd1166a
--- /dev/null
+++ b/storaged/main.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2016 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 LOG_TAG "storaged"
+#define KLOG_LEVEL 6
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include <android-base/macros.h>
+#include <android-base/stringprintf.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <binder/IPCThreadState.h>
+#include <cutils/android_get_control_file.h>
+#include <cutils/klog.h>
+#include <cutils/sched_policy.h>
+#include <private/android_filesystem_config.h>
+
+#include <storaged.h>
+#include <storaged_service.h>
+#include <storaged_utils.h>
+
+storaged_t storaged;
+
+static int drop_privs() {
+ // privilege setting
+ struct sched_param param;
+ memset(¶m, 0, sizeof(param));
+
+ if (set_sched_policy(0, SP_BACKGROUND) < 0) return -1;
+
+ if (sched_setscheduler((pid_t) 0, SCHED_BATCH, ¶m) < 0) return -1;
+
+ if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) return -1;
+
+ if (prctl(PR_SET_KEEPCAPS, 1) < 0) return -1;
+
+ std::unique_ptr<struct _cap_struct, int(*)(void *)> caps(cap_init(), cap_free);
+ if (cap_clear(caps.get()) < 0) return -1;
+ cap_value_t cap_value[] = {
+ CAP_SETGID,
+ CAP_SETUID,
+ CAP_SYS_PTRACE // allow access to proc/<pid>/io as non-root user
+ };
+ if (cap_set_flag(caps.get(), CAP_PERMITTED,
+ arraysize(cap_value), cap_value,
+ CAP_SET) < 0) return -1;
+ if (cap_set_flag(caps.get(), CAP_EFFECTIVE,
+ arraysize(cap_value), cap_value,
+ CAP_SET) < 0) return -1;
+ if (cap_set_proc(caps.get()) < 0)
+ return -1;
+
+ gid_t groups[] = { AID_READPROC };
+
+ if (setgroups(sizeof(groups) / sizeof(groups[0]), groups) == -1) return -1;
+
+ if (setgid(AID_SYSTEM) != 0) return -1;
+
+ if (setuid(AID_SYSTEM) != 0) return -1;
+
+ if (cap_set_flag(caps.get(), CAP_PERMITTED, 2, cap_value, CAP_CLEAR) < 0) return -1;
+ if (cap_set_flag(caps.get(), CAP_EFFECTIVE, 2, cap_value, CAP_CLEAR) < 0) return -1;
+ if (cap_set_proc(caps.get()) < 0)
+ return -1;
+
+ return 0;
+}
+
+// Function of storaged's main thread
+extern int fd_dmesg;
+void* storaged_main(void* s) {
+ storaged_t* storaged = (storaged_t*)s;
+
+ if (fd_dmesg >= 0) {
+ static const char start_message[] = {KMSG_PRIORITY(LOG_INFO),
+ 's', 't', 'o', 'r', 'a', 'g', 'e', 'd', ':', ' ', 'S', 't', 'a', 'r', 't', '\n'};
+ write(fd_dmesg, start_message, sizeof(start_message));
+ }
+
+ for (;;) {
+ storaged->event();
+ storaged->pause();
+ }
+ return NULL;
+}
+
+static void help_message(void) {
+ printf("usage: storaged [OPTION]\n");
+ printf(" -d --dump Dump task I/O usage to stdout\n");
+ printf(" -s --start Start storaged (default)\n");
+ printf(" --emmc=INTERVAL Set publish interval of emmc lifetime information (in days)\n");
+ printf(" --diskstats=INTERVAL Set publish interval of diskstats (in hours)\n");
+ printf(" --unit=INTERVAL Set storaged's refresh interval (in seconds)\n");
+ fflush(stdout);
+}
+
+#define HOUR_TO_SEC ( 3600 )
+#define DAY_TO_SEC ( 3600 * 24 )
+
+int main(int argc, char** argv) {
+ klog_set_level(KLOG_LEVEL);
+ int flag_main_service = 0;
+ int flag_dump_task = 0;
+ int flag_config = 0;
+ int unit_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT;
+ int diskstats_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH;
+ int emmc_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH;
+ int fd_emmc = -1;
+ int opt;
+
+ for (;;) {
+ int opt_idx = 0;
+ static struct option long_options[] = {
+ {"start", no_argument, 0, 's'},
+ {"kill", no_argument, 0, 'k'},
+ {"dump", no_argument, 0, 'd'},
+ {"help", no_argument, 0, 'h'},
+ {"unit", required_argument, 0, 0 },
+ {"diskstats", required_argument, 0, 0 },
+ {"emmc", required_argument, 0, 0 }
+ };
+ opt = getopt_long(argc, argv, ":skdh0", long_options, &opt_idx);
+ if (opt == -1) {
+ break;
+ }
+
+ switch (opt) {
+ case 0:
+ printf("option %s", long_options[opt_idx].name);
+ if (optarg) {
+ printf(" with arg %s", optarg);
+ if (strcmp(long_options[opt_idx].name, "unit") == 0) {
+ unit_interval = atoi(optarg);
+ if (unit_interval == 0) {
+ fprintf(stderr, "Invalid argument. Option %s requires an integer argument greater than 0.\n",
+ long_options[opt_idx].name);
+ help_message();
+ return -1;
+ }
+ } else if (strcmp(long_options[opt_idx].name, "diskstats") == 0) {
+ diskstats_interval = atoi(optarg) * HOUR_TO_SEC;
+ if (diskstats_interval == 0) {
+ fprintf(stderr, "Invalid argument. Option %s requires an integer argument greater than 0.\n",
+ long_options[opt_idx].name);
+ help_message();
+ return -1;
+ }
+
+ } else if (strcmp(long_options[opt_idx].name, "emmc") == 0) {
+ emmc_interval = atoi(optarg) * DAY_TO_SEC;
+ if (diskstats_interval == 0) {
+ fprintf(stderr, "Invalid argument. Option %s requires an integer argument greater than 0.\n",
+ long_options[opt_idx].name);
+ help_message();
+ return -1;
+ }
+ }
+ flag_config = 1;
+ } else {
+ fprintf(stderr, "Invalid argument. Option %s requires an argument.\n",
+ long_options[opt_idx].name);
+ help_message();
+ return -1;
+ }
+ printf("\n");
+ break;
+ case 's':
+ flag_main_service = 1;
+ break;
+ case 'd':
+ flag_dump_task = 1;
+ break;
+ case 'h':
+ help_message();
+ return 0;
+ case '?':
+ default:
+ fprintf(stderr, "no supported option\n");
+ help_message();
+ return -1;
+ }
+ }
+
+ if (argc == 1) {
+ flag_main_service = 1;
+ }
+
+ if (flag_main_service && flag_dump_task) {
+ fprintf(stderr, "Invalid arguments. Option \"start\" and \"dump\" cannot be used together.\n");
+ help_message();
+ return -1;
+ }
+
+ if (flag_config && flag_dump_task) {
+ fprintf(stderr, "Invalid arguments. Cannot set configs in \'dump\' option.\n");
+ help_message();
+ return -1;
+ }
+
+ if (flag_main_service) { // start main thread
+ static const char dev_kmsg[] = "/dev/kmsg";
+ fd_dmesg = android_get_control_file(dev_kmsg);
+ if (fd_dmesg < 0)
+ fd_dmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY));
+
+ static const char mmc0_ext_csd[] = "/d/mmc0/mmc0:0001/ext_csd";
+ fd_emmc = android_get_control_file(mmc0_ext_csd);
+ if (fd_emmc < 0)
+ fd_emmc = TEMP_FAILURE_RETRY(open(mmc0_ext_csd, O_RDONLY));
+
+ if (drop_privs() != 0) {
+ return -1;
+ }
+
+ storaged.set_privileged_fds(fd_emmc);
+
+ if (flag_config) {
+ storaged.set_unit_interval(unit_interval);
+ storaged.set_diskstats_interval(diskstats_interval);
+ storaged.set_emmc_interval(emmc_interval);
+ }
+
+ // Start the main thread of storaged
+ pthread_t storaged_main_thread;
+ if (pthread_create(&storaged_main_thread, NULL, storaged_main, &storaged)) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s Failed to create main thread\n", kmsg_error_prefix);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return -1;
+ }
+
+ defaultServiceManager()->addService(String16("storaged"), new Storaged());
+ android::ProcessState::self()->startThreadPool();
+ IPCThreadState::self()->joinThreadPool();
+ pthread_join(storaged_main_thread, NULL);
+
+ close(fd_dmesg);
+ close(fd_emmc);
+
+ return 0;
+ }
+
+ if (flag_dump_task) {
+ sp<IStoraged> storaged_service = get_storaged_service();
+ if (storaged_service == NULL) {
+ fprintf(stderr, "Cannot find storaged service.\nMaybe run storaged --start first?\n");
+ return -1;
+ }
+ std::vector<struct task_info> res = storaged_service->dump_tasks(NULL);
+
+ if (res.size() == 0) {
+ fprintf(stderr, "Task I/O is not readable in this version of kernel.\n");
+ return 0;
+ }
+
+ time_t starttime = storaged.get_starttime();
+
+ if (starttime == (time_t)-1) {
+ fprintf(stderr, "Unknown start time\n");
+ } else {
+ char* time_str = ctime(&starttime);
+ printf("Application I/O was collected by storaged since %s", time_str);
+ }
+
+ sort_running_tasks_info(res);
+ log_console_running_tasks_info(res);
+
+
+ return 0;
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
new file mode 100644
index 0000000..1402208
--- /dev/null
+++ b/storaged/storaged.cpp
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2016 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 LOG_TAG "storaged"
+
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#include <android-base/logging.h>
+
+#include <storaged.h>
+#include <storaged_utils.h>
+
+/* disk_stats_publisher */
+void disk_stats_publisher::publish(void) {
+ // Logging
+ log_kernel_disk_stats(&mAccumulate, "regular");
+ struct disk_perf perf = get_disk_perf(&mAccumulate);
+ log_kernel_disk_perf(&perf, "regular");
+ log_event_disk_stats(&mAccumulate, "regular");
+ // Reset global structures
+ memset(&mAccumulate, 0, sizeof(struct disk_stats));
+}
+
+void disk_stats_publisher::update(void) {
+ struct disk_stats curr;
+ if (parse_disk_stats(DISK_STATS_PATH, &curr)) {
+ struct disk_stats inc = get_inc_disk_stats(&mPrevious, &curr);
+ add_disk_stats(&inc, &mAccumulate);
+ #ifdef DEBUG
+// log_kernel_disk_stats(&mPrevious, "prev stats");
+// log_kernel_disk_stats(&curr, "curr stats");
+// log_kernel_disk_stats(&inc, "inc stats");
+// log_kernel_disk_stats(&mAccumulate, "accumulated stats");
+ #endif
+ mPrevious = curr;
+ }
+}
+
+/* disk_stats_monitor */
+void disk_stats_monitor::update_mean() {
+ CHECK(mValid);
+ mMean.read_perf = (uint32_t)mStats.read_perf.get_mean();
+ mMean.read_ios = (uint32_t)mStats.read_ios.get_mean();
+ mMean.write_perf = (uint32_t)mStats.write_perf.get_mean();
+ mMean.write_ios = (uint32_t)mStats.write_ios.get_mean();
+ mMean.queue = (uint32_t)mStats.queue.get_mean();
+}
+
+void disk_stats_monitor::update_std() {
+ CHECK(mValid);
+ mStd.read_perf = (uint32_t)mStats.read_perf.get_std();
+ mStd.read_ios = (uint32_t)mStats.read_ios.get_std();
+ mStd.write_perf = (uint32_t)mStats.write_perf.get_std();
+ mStd.write_ios = (uint32_t)mStats.write_ios.get_std();
+ mStd.queue = (uint32_t)mStats.queue.get_std();
+}
+
+void disk_stats_monitor::add(struct disk_perf* perf) {
+ mStats.read_perf.add(perf->read_perf);
+ mStats.read_ios.add(perf->read_ios);
+ mStats.write_perf.add(perf->write_perf);
+ mStats.write_ios.add(perf->write_ios);
+ mStats.queue.add(perf->queue);
+}
+
+void disk_stats_monitor::evict(struct disk_perf* perf) {
+ mStats.read_perf.evict(perf->read_perf);
+ mStats.read_ios.evict(perf->read_ios);
+ mStats.write_perf.evict(perf->write_perf);
+ mStats.write_ios.evict(perf->write_ios);
+ mStats.queue.evict(perf->queue);
+}
+
+bool disk_stats_monitor::detect(struct disk_perf* perf) {
+ return ((double)perf->queue >= (double)mMean.queue + mSigma * (double)mStd.queue) &&
+ ((double)perf->read_perf < (double)mMean.read_perf - mSigma * (double)mStd.read_perf) &&
+ ((double)perf->write_perf < (double)mMean.write_perf - mSigma * (double)mStd.write_perf);
+}
+
+void disk_stats_monitor::update(struct disk_stats* stats) {
+ struct disk_stats inc = get_inc_disk_stats(&mPrevious, stats);
+ struct disk_perf perf = get_disk_perf(&inc);
+ // Update internal data structures
+ if (LIKELY(mValid)) {
+ CHECK_EQ(mBuffer.size(), mWindow);
+
+ if (UNLIKELY(detect(&perf))) {
+ mStall = true;
+ add_disk_stats(&inc, &mAccumulate);
+ #ifdef DEBUG
+ log_kernel_disk_perf(&mMean, "stalled_mean");
+ log_kernel_disk_perf(&mStd, "stalled_std");
+ #endif
+ } else {
+ if (mStall) {
+ log_kernel_disk_stats(&mAccumulate, "stalled");
+ struct disk_perf acc_perf = get_disk_perf(&mAccumulate);
+ log_kernel_disk_perf(&acc_perf, "stalled");
+
+ log_event_disk_stats(&mAccumulate, "stalled");
+ mStall = false;
+ memset(&mAccumulate, 0, sizeof(mAccumulate));
+ }
+ }
+
+ evict(&mBuffer.front());
+ mBuffer.pop();
+ add(&perf);
+ mBuffer.push(perf);
+
+ update_mean();
+ update_std();
+
+ } else { /* mValid == false */
+ CHECK_LT(mBuffer.size(), mWindow);
+ add(&perf);
+ mBuffer.push(perf);
+ if (mBuffer.size() == mWindow) {
+ mValid = true;
+ update_mean();
+ update_std();
+ }
+ }
+
+ mPrevious = *stats;
+}
+
+void disk_stats_monitor::update(void) {
+ struct disk_stats curr;
+ if (LIKELY(parse_disk_stats(DISK_STATS_PATH, &curr))) {
+ update(&curr);
+ }
+}
+
+/* emmc_info_t */
+void emmc_info_t::publish(void) {
+ if (mValid) {
+ log_kernel_emmc_info(&mInfo);
+ log_event_emmc_info(&mInfo);
+ }
+}
+
+void emmc_info_t::update(void) {
+ if (mFdEmmc >= 0) {
+ mValid = parse_emmc_ecsd(mFdEmmc, &mInfo);
+ }
+}
+
+/* storaged_t */
+storaged_t::storaged_t(void) {
+ mConfig.emmc_available = (access(EMMC_ECSD_PATH, R_OK) >= 0);
+
+ if (access(MMC_DISK_STATS_PATH, R_OK) < 0 && access(SDA_DISK_STATS_PATH, R_OK) < 0) {
+ mConfig.diskstats_available = false;
+ } else {
+ mConfig.diskstats_available = true;
+ }
+
+ mConfig.proc_taskio_readable = true;
+ const char* test_paths[] = {"/proc/1/io", "/proc/1/comm", "/proc/1/cmdline", "/proc/1/stat"};
+ for (uint i = 0; i < sizeof(test_paths) / sizeof(const char*); ++i) {
+ if (access(test_paths[i], R_OK) < 0) {
+ mConfig.proc_taskio_readable = false;
+ break;
+ }
+ }
+
+ mConfig.periodic_chores_interval_unit = DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT;
+ mConfig.periodic_chores_interval_disk_stats_publish = DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH;
+ mConfig.periodic_chores_interval_emmc_info_publish = DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH;
+
+ mStarttime = time(NULL);
+}
+
+void storaged_t::event(void) {
+ if (mConfig.diskstats_available) {
+ mDiskStats.update();
+ mDsm.update();
+ if (mTimer && (mTimer % mConfig.periodic_chores_interval_disk_stats_publish) == 0) {
+ mDiskStats.publish();
+ }
+ }
+
+ if (mConfig.proc_taskio_readable) {
+ mTasks.update_running_tasks();
+ }
+
+ if (mConfig.emmc_available && mTimer &&
+ (mTimer % mConfig.periodic_chores_interval_emmc_info_publish) == 0) {
+ mEmmcInfo.update();
+ mEmmcInfo.publish();
+ }
+
+ mTimer += mConfig.periodic_chores_interval_unit;
+}
\ No newline at end of file
diff --git a/storaged/storaged.rc b/storaged/storaged.rc
new file mode 100644
index 0000000..f72521c
--- /dev/null
+++ b/storaged/storaged.rc
@@ -0,0 +1,5 @@
+service storaged /system/bin/storaged
+ class main
+ file /d/mmc0/mmc0:0001/ext_csd r
+ file /dev/kmsg w
+ group root readproc
diff --git a/storaged/storaged_service.cpp b/storaged/storaged_service.cpp
new file mode 100644
index 0000000..aa38ceb
--- /dev/null
+++ b/storaged/storaged_service.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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 <vector>
+
+#include <binder/IBinder.h>
+#include <binder/IInterface.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+#include <storaged.h>
+#include <storaged_service.h>
+
+extern storaged_t storaged;
+
+std::vector<struct task_info> BpStoraged::dump_tasks(const char* /*option*/) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IStoraged::getInterfaceDescriptor());
+
+ remote()->transact(DUMPTASKS, data, &reply);
+
+ uint32_t res_size = reply.readInt32();
+ std::vector<struct task_info> res(res_size);
+ for (auto&& task : res) {
+ reply.read(&task, sizeof(task));
+ }
+ return res;
+}
+
+IMPLEMENT_META_INTERFACE(Storaged, "Storaged");
+
+status_t BnStoraged::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ data.checkInterface(this);
+
+ switch(code) {
+ case DUMPTASKS: {
+ std::vector<struct task_info> res = dump_tasks(NULL);
+
+ reply->writeInt32(res.size());
+ for (auto task : res) {
+ reply->write(&task, sizeof(task));
+ }
+ return NO_ERROR;
+ }
+ break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+std::vector<struct task_info> Storaged::dump_tasks(const char* /* option */) {
+ return storaged.get_tasks();
+}
+
+sp<IStoraged> get_storaged_service() {
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (sm == NULL) return NULL;
+
+ sp<IBinder> binder = sm->getService(String16("storaged"));
+ if (binder == NULL) return NULL;
+
+ sp<IStoraged> storaged = interface_cast<IStoraged>(binder);
+
+ return storaged;
+}
\ No newline at end of file
diff --git a/storaged/storaged_utils.cpp b/storaged/storaged_utils.cpp
new file mode 100644
index 0000000..9c0b625
--- /dev/null
+++ b/storaged/storaged_utils.cpp
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2016 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 LOG_TAG "storaged"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <linux/time.h>
+
+#include <dirent.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sstream>
+#include <string>
+#include <unordered_map>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/logging.h>
+#include <log/log.h>
+#include <log/log_event_list.h>
+#include <cutils/klog.h>
+
+#include <storaged.h>
+#include <storaged_utils.h>
+
+#include <time.h>
+
+#define SECTOR_SIZE ( 512 )
+#define SEC_TO_MSEC ( 1000 )
+#define MSEC_TO_USEC ( 1000 )
+#define USEC_TO_NSEC ( 1000 )
+
+int fd_dmesg = -1;
+
+bool parse_disk_stats(const char* disk_stats_path, struct disk_stats* stats) {
+ // Get time
+ struct timespec ts;
+ // Use monotonic to exclude suspend time so that we measure IO bytes/sec
+ // when system is running.
+ int ret = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (ret < 0) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s clock_gettime() failed with errno %d\n",
+ kmsg_error_prefix, ret);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+
+ std::string buffer;
+ if (!android::base::ReadFileToString(disk_stats_path, &buffer)) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s %s: ReadFileToString failed.\n", kmsg_error_prefix, disk_stats_path);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+
+ // Regular diskstats entries
+ std::stringstream ss(buffer);
+ for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
+ ss >> *((uint64_t*)stats + i);
+ }
+ // Other entries
+ stats->start_time = 0;
+ stats->end_time = (uint64_t)ts.tv_sec * SEC_TO_MSEC +
+ ts.tv_nsec / (MSEC_TO_USEC * USEC_TO_NSEC);
+ stats->counter = 1;
+ stats->io_avg = (double)stats->io_in_flight;
+ return true;
+}
+
+struct disk_perf get_disk_perf(struct disk_stats* stats) {
+ struct disk_perf perf;
+ memset(&perf, 0, sizeof(struct disk_perf)); // initialize
+
+ if (stats->io_ticks) {
+ if (stats->read_ticks) {
+ unsigned long long divisor = stats->read_ticks * stats->io_ticks;
+ perf.read_perf = ((unsigned long long)SECTOR_SIZE *
+ stats->read_sectors *
+ stats->io_in_queue +
+ (divisor >> 1)) /
+ divisor;
+ perf.read_ios = ((unsigned long long)SEC_TO_MSEC *
+ stats->read_ios *
+ stats->io_in_queue +
+ (divisor >> 1)) /
+ divisor;
+ }
+ if (stats->write_ticks) {
+ unsigned long long divisor = stats->write_ticks * stats->io_ticks;
+ perf.write_perf = ((unsigned long long)SECTOR_SIZE *
+ stats->write_sectors *
+ stats->io_in_queue +
+ (divisor >> 1)) /
+ divisor;
+ perf.write_ios = ((unsigned long long)SEC_TO_MSEC *
+ stats->write_ios *
+ stats->io_in_queue +
+ (divisor >> 1)) /
+ divisor;
+ }
+ perf.queue = (stats->io_in_queue + (stats->io_ticks >> 1)) /
+ stats->io_ticks;
+ }
+ return perf;
+}
+
+struct disk_stats get_inc_disk_stats(struct disk_stats* prev, struct disk_stats* curr) {
+ struct disk_stats inc;
+ for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
+ if (i == DISK_STATS_IO_IN_FLIGHT_IDX) {
+ continue;
+ }
+
+ *((uint64_t*)&inc + i) =
+ *((uint64_t*)curr + i) - *((uint64_t*)prev + i);
+ }
+ // io_in_flight is exception
+ inc.io_in_flight = curr->io_in_flight;
+
+ inc.start_time = prev->end_time;
+ inc.end_time = curr->end_time;
+ inc.io_avg = curr->io_avg;
+ inc.counter = 1;
+
+ return inc;
+}
+
+// Add src to dst
+void add_disk_stats(struct disk_stats* src, struct disk_stats* dst) {
+ if (dst->end_time != 0 && dst->end_time != src->start_time && fd_dmesg >= 0) {
+ std::string warning_message = android::base::StringPrintf(
+ "%s Two dis-continuous periods of diskstats are added. "
+ "dst end with %jd, src start with %jd\n",
+ kmsg_warning_prefix, dst->end_time, src->start_time);
+
+ write(fd_dmesg, warning_message.c_str(), warning_message.length());
+ }
+
+ for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
+ if (i == DISK_STATS_IO_IN_FLIGHT_IDX) {
+ continue;
+ }
+
+ *((uint64_t*)dst + i) += *((uint64_t*)src + i);
+ }
+
+ dst->io_in_flight = src->io_in_flight;
+ if (dst->counter + src->counter) {
+ dst->io_avg = ((dst->io_avg * dst->counter) + (src->io_avg * src->counter)) /
+ (dst->counter + src->counter);
+ }
+ dst->counter += src->counter;
+ dst->end_time = src->end_time;
+ if (dst->start_time == 0) {
+ dst->start_time = src->start_time;
+ }
+}
+
+bool parse_emmc_ecsd(int ext_csd_fd, struct emmc_info* info) {
+ CHECK(ext_csd_fd >= 0);
+ struct hex {
+ char str[2];
+ };
+ // List of interesting offsets
+ static const size_t EXT_CSD_REV_IDX = 192 * sizeof(hex);
+ static const size_t EXT_PRE_EOL_INFO_IDX = 267 * sizeof(hex);
+ static const size_t EXT_DEVICE_LIFE_TIME_EST_A_IDX = 268 * sizeof(hex);
+ static const size_t EXT_DEVICE_LIFE_TIME_EST_B_IDX = 269 * sizeof(hex);
+
+ // Read file
+ CHECK(lseek(ext_csd_fd, 0, SEEK_SET) == 0);
+ std::string buffer;
+ if (!android::base::ReadFdToString(ext_csd_fd, &buffer)) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s ReadFdToString failed.\n", kmsg_error_prefix);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+
+ if (buffer.length() < EXT_CSD_FILE_MIN_SIZE) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s EMMC ext csd file has truncated content. File length: %d\n",
+ kmsg_error_prefix, (int)buffer.length());
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+
+ std::string sub;
+ std::stringstream ss;
+ // Parse EXT_CSD_REV
+ int ext_csd_rev = -1;
+ sub = buffer.substr(EXT_CSD_REV_IDX, sizeof(hex));
+ ss << sub;
+ ss >> std::hex >> ext_csd_rev;
+ if (ext_csd_rev < 0) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s Failure on parsing EXT_CSD_REV.\n", kmsg_error_prefix);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+ ss.clear();
+
+ static const char* ver_str[] = {
+ "4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0"
+ };
+
+ strncpy(info->mmc_ver,
+ (ext_csd_rev < (int)(sizeof(ver_str) / sizeof(ver_str[0]))) ?
+ ver_str[ext_csd_rev] :
+ "Unknown",
+ MMC_VER_STR_LEN);
+
+ if (ext_csd_rev < 7) {
+ return 0;
+ }
+
+ // Parse EXT_PRE_EOL_INFO
+ info->eol = -1;
+ sub = buffer.substr(EXT_PRE_EOL_INFO_IDX, sizeof(hex));
+ ss << sub;
+ ss >> std::hex >> info->eol;
+ if (info->eol < 0) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s Failure on parsing EXT_PRE_EOL_INFO.\n", kmsg_error_prefix);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+ ss.clear();
+
+ // Parse DEVICE_LIFE_TIME_EST
+ info->lifetime_a = -1;
+ sub = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_A_IDX, sizeof(hex));
+ ss << sub;
+ ss >> std::hex >> info->lifetime_a;
+ if (info->lifetime_a < 0) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s Failure on parsing EXT_DEVICE_LIFE_TIME_EST_TYP_A.\n", kmsg_error_prefix);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+ ss.clear();
+
+ info->lifetime_b = -1;
+ sub = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_B_IDX, sizeof(hex));
+ ss << sub;
+ ss >> std::hex >> info->lifetime_b;
+ if (info->lifetime_b < 0) {
+ if (fd_dmesg >= 0) {
+ std::string error_message = android::base::StringPrintf(
+ "%s Failure on parsing EXT_DEVICE_LIFE_TIME_EST_TYP_B.\n", kmsg_error_prefix);
+ write(fd_dmesg, error_message.c_str(), error_message.length());
+ }
+ return false;
+ }
+ ss.clear();
+
+ return true;
+}
+
+#define PROC_DIR "/proc/"
+#define PROC_STAT_STARTTIME_IDX ( 22 ) // This index is 1 based according to the linux proc man page
+bool parse_task_info(uint32_t pid, struct task_info* info) {
+ std::string buffer;
+ std::string pid_str = std::to_string(pid);
+ info->pid = pid;
+
+ // Get task I/O
+ std::string task_io_path = android::base::StringPrintf(PROC_DIR "%s/io", pid_str.c_str());
+ if (!android::base::ReadFileToString(task_io_path, &buffer)) return false;
+
+ std::stringstream ss(buffer);
+ std::string title;
+
+ ss >> title >> info->rchar
+ >> title >> info->wchar
+ >> title >> info->syscr
+ >> title >> info->syscw
+ >> title >> info->read_bytes
+ >> title >> info->write_bytes
+ >> title >> info->cancelled_write_bytes;
+ ss.clear();
+
+ // Get cmd string
+ std::string task_cmdline_path = android::base::StringPrintf(PROC_DIR "%u/cmdline", pid);
+ if (!android::base::ReadFileToString(task_cmdline_path, &buffer)) return false;
+ strcpy(info->cmd, android::base::Trim(buffer).c_str());
+
+ if (info->cmd[0] == '\0') {
+ std::string task_comm_path = android::base::StringPrintf(PROC_DIR "%u/comm", pid);
+ if (!android::base::ReadFileToString(task_comm_path, &buffer)) return false;
+ strcpy(info->cmd, android::base::Trim(buffer).c_str());
+ }
+
+ // Get task start time
+ std::string task_stat_path = android::base::StringPrintf(PROC_DIR "%u/stat", pid);
+ if (!android::base::ReadFileToString(task_stat_path, &buffer)) return false;
+
+ std::vector<std::string> stat_parts = android::base::Split(buffer, " ");
+ info->starttime = atoll(stat_parts[PROC_STAT_STARTTIME_IDX - 1].c_str());
+
+ return true;
+}
+
+static bool is_pid(char* d_name) {
+ if (!d_name || d_name[0] == '\0') return false;
+ char* c = d_name;
+ while (*c) {
+ if (!isdigit(*c)) return false;
+ ++c;
+ }
+ return true;
+}
+
+static bool cmp_task_info(struct task_info i, struct task_info j) {
+ if (i.write_bytes + i.read_bytes != j.write_bytes + j.read_bytes) {
+ return i.write_bytes + i.read_bytes > j.write_bytes + j.read_bytes;
+ }
+ if (i.wchar + i.rchar != j.wchar + j.rchar) {
+ return i.wchar + i.rchar > j.wchar + j.rchar;
+ }
+ if (i.syscw + i.syscr != j.syscw + j.syscr) {
+ return i.syscw + i.syscr > j.syscw + j.syscr;
+ }
+
+ return strcmp(i.cmd, j.cmd) < 0;
+}
+
+std::unordered_map<uint32_t, struct task_info> tasks_t::get_running_tasks() {
+ std::unordered_map<uint32_t, struct task_info> retval;
+ std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(PROC_DIR), closedir);
+ CHECK(dir != NULL);
+ struct dirent* dp;
+
+ for (;;) {
+ if ((dp = readdir(dir.get())) == NULL) break;
+ if (!is_pid(dp->d_name)) continue;
+
+ uint32_t pid = atol(dp->d_name);
+ struct task_info info;
+ if (parse_task_info(pid, &info)) {
+ retval[pid] = info;
+ }
+ }
+ return retval;
+}
+
+static void add_task_info(struct task_info* src, struct task_info* dst) {
+ CHECK(strcmp(src->cmd, dst->cmd) == 0);
+
+ dst->pid = 0;
+ dst->rchar += src->rchar;
+ dst->wchar += src->wchar;
+ dst->syscr += src->syscr;
+ dst->syscw += src->syscw;
+ dst->read_bytes += src->read_bytes;
+ dst->write_bytes += src->write_bytes;
+ dst->cancelled_write_bytes += src->cancelled_write_bytes;
+ dst->starttime = 0;
+}
+
+void tasks_t::update_running_tasks(void) {
+ std::unordered_map<uint32_t, struct task_info> tasks_latest = get_running_tasks();
+ std::unordered_map<std::string, struct task_info> tasks_old = mOld;
+
+ for (auto t : mRunning) {
+ uint32_t pid = t.first;
+ // old task on mRunning still exist on tasks_latest
+ if (tasks_latest.find(pid) != tasks_latest.end() &&
+ tasks_latest[pid].starttime == t.second.starttime) {
+ continue;
+ } else {
+ // This branch will handle 2 cases:
+ // - Task get killed between the 2 samplings
+ // - Task get killed and its pid is reused
+ std::string cmd = t.second.cmd;
+ struct task_info info = t.second;
+
+ if (tasks_old.find(cmd) == tasks_old.end()) {
+ tasks_old[cmd] = info;
+ } else {
+ add_task_info(&info, &tasks_old[cmd]);
+ }
+ }
+ }
+ { // update critical area
+ // this is really fast!
+ std::unique_ptr<lock_t> lock(new lock_t(&mSem));
+ mRunning = tasks_latest;
+ mOld = tasks_old;
+ }
+
+}
+
+std::vector<struct task_info> tasks_t::get_tasks(void) {
+ std::unique_ptr<lock_t> lock(new lock_t(&mSem));
+ std::unordered_map<std::string, struct task_info> tasks_map = mOld;
+
+ for (auto i : mRunning) {
+ std::string cmd = i.second.cmd;
+ if (tasks_map.find(cmd) == tasks_map.end()) {
+ tasks_map[cmd] = i.second;
+ } else {
+ add_task_info(&i.second, &tasks_map[cmd]);
+ }
+ }
+
+ std::vector<struct task_info> retval(tasks_map.size());
+ int idx = 0;
+ for (auto i : tasks_map) {
+ retval[idx++] = i.second;
+ }
+
+ return retval;
+}
+
+void sort_running_tasks_info(std::vector<struct task_info> &tasks) {
+ std::sort(tasks.begin(), tasks.end(), cmp_task_info);
+}
+
+/* Logging functions */
+void log_console_running_tasks_info(std::vector<struct task_info> tasks) {
+// Sample Output:
+// Application Read Write Read Write Read Write Cancelled
+// Name Characters Characters Syscalls Syscalls Bytes Bytes Writebytes
+// ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
+// zygote64 37688308 3388467 7607 4363 314519552 5373952 8192
+// system_server 95874193 2216913 74613 52257 213078016 7237632 16384
+// zygote 506279 1726194 921 263 128114688 1765376 0
+// /vendor/bin/qcks 75415632 75154382 21672 25036 63627264 29974528 10485760
+// /init 86658523 5107871 82113 8633 91015168 1245184 0
+
+ // Title
+ printf(" Application Read Write Read Write Read Write Cancelled\n"
+ " Name Characters Characters Syscalls Syscalls Bytes Bytes Writebytes\n"
+ " ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n");
+
+ for (struct task_info task : tasks) {
+ printf("%50s%15ju%15ju%15ju%15ju%15ju%15ju%15ju\n",
+ task.cmd, task.rchar, task.wchar, task.syscr, task.syscw,
+ task.read_bytes, task.write_bytes, task.cancelled_write_bytes);
+ }
+ fflush(stdout);
+}
+
+void log_kernel_disk_stats(struct disk_stats* stats, const char* type) {
+ // skip if the input structure are all zeros
+ if (stats == NULL) return;
+ struct disk_stats zero_cmp;
+ memset(&zero_cmp, 0, sizeof(zero_cmp));
+ if (memcmp(&zero_cmp, stats, sizeof(struct disk_stats)) == 0) return;
+
+ if (fd_dmesg >= 0) {
+ std::string info_message = android::base::StringPrintf(
+ "%s diskstats %s: %ju %ju %ju %ju %ju %ju %ju %ju %ju %ju %.1f %ju %ju\n",
+ kmsg_info_prefix, type, stats->start_time, stats->end_time,
+ stats->read_ios, stats->read_merges,
+ stats->read_sectors, stats->read_ticks,
+ stats->write_ios, stats->write_merges,
+ stats->write_sectors, stats->write_ticks,
+ stats->io_avg, stats->io_ticks,
+ stats->io_in_queue);
+
+ write(fd_dmesg, info_message.c_str(), info_message.length());
+ }
+}
+
+void log_kernel_disk_perf(struct disk_perf* perf, const char* type) {
+ // skip if the input structure are all zeros
+ if (perf == NULL) return;
+ struct disk_perf zero_cmp;
+ memset(&zero_cmp, 0, sizeof(zero_cmp));
+ if (memcmp(&zero_cmp, perf, sizeof(struct disk_perf)) == 0) return;
+
+ if (fd_dmesg >= 0) {
+ std::string info_message = android::base::StringPrintf(
+ "%s perf(ios) %s rd:%luKB/s(%lu/s) wr:%luKB/s(%lu/s) q:%lu\n",
+ kmsg_info_prefix, type,
+ (unsigned long)perf->read_perf, (unsigned long)perf->read_ios,
+ (unsigned long)perf->write_perf, (unsigned long)perf->write_ios,
+ (unsigned long)perf->queue);
+
+ write(fd_dmesg, info_message.c_str(), info_message.length());
+ }
+}
+
+void log_kernel_emmc_info(struct emmc_info* info) {
+ // skip if the input structure are all zeros
+ if (info == NULL) return;
+ struct emmc_info zero_cmp;
+ memset(&zero_cmp, 0, sizeof(zero_cmp));
+ if (memcmp(&zero_cmp, info, sizeof(struct emmc_info)) == 0) return;
+
+ if (fd_dmesg >= 0) {
+ std::string info_message = android::base::StringPrintf(
+ "%s MMC %s eol:%d, lifetime typA:%d, typB:%d\n",
+ kmsg_info_prefix, info->mmc_ver, info->eol, info->lifetime_a, info->lifetime_b);
+
+ write(fd_dmesg, info_message.c_str(), info_message.length());
+ }
+}
+
+void log_event_disk_stats(struct disk_stats* stats, const char* type) {
+ // skip if the input structure are all zeros
+ if (stats == NULL) return;
+ struct disk_stats zero_cmp;
+ memset(&zero_cmp, 0, sizeof(zero_cmp));
+ // skip event logging diskstats when it is zero increment (all first 11 entries are zero)
+ if (memcmp(&zero_cmp, stats, sizeof(uint64_t) * DISK_STATS_SIZE) == 0) return;
+
+ // Construct eventlog list
+ android_log_context ctx = create_android_logger(EVENTLOGTAG_DISKSTATS);
+
+ android_log_write_string8(ctx, type);
+ android_log_write_int64(ctx, stats->start_time);
+ android_log_write_int64(ctx, stats->end_time);
+ android_log_write_int64(ctx, stats->read_ios);
+ android_log_write_int64(ctx, stats->read_merges);
+ android_log_write_int64(ctx, stats->read_sectors);
+ android_log_write_int64(ctx, stats->read_ticks);
+ android_log_write_int64(ctx, stats->write_ios);
+ android_log_write_int64(ctx, stats->write_merges);
+ android_log_write_int64(ctx, stats->write_sectors);
+ android_log_write_int64(ctx, stats->write_ticks);
+ android_log_write_int64(ctx, (uint64_t)stats->io_avg);
+ android_log_write_int64(ctx, stats->io_ticks);
+ android_log_write_int64(ctx, stats->io_in_queue);
+
+ android_log_write_list(ctx, LOG_ID_EVENTS);
+ android_log_destroy(&ctx);
+}
+
+void log_event_emmc_info(struct emmc_info* info) {
+ // skip if the input structure are all zeros
+ if (info == NULL) return;
+ struct emmc_info zero_cmp;
+ memset(&zero_cmp, 0, sizeof(zero_cmp));
+ if (memcmp(&zero_cmp, info, sizeof(struct emmc_info)) == 0) return;
+
+ android_log_context ctx = create_android_logger(EVENTLOGTAG_EMMCINFO);
+
+ android_log_write_string8(ctx, info->mmc_ver);
+ android_log_write_int32(ctx, info->eol);
+ android_log_write_int32(ctx, info->lifetime_a);
+ android_log_write_int32(ctx, info->lifetime_b);
+
+ android_log_write_list(ctx, LOG_ID_EVENTS);
+ android_log_destroy(&ctx);
+}
diff --git a/storaged/tests/Android.mk b/storaged/tests/Android.mk
new file mode 100644
index 0000000..4a0e45c
--- /dev/null
+++ b/storaged/tests/Android.mk
@@ -0,0 +1,45 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+test_module_prefix := storaged-
+test_tags := tests
+
+# -----------------------------------------------------------------------------
+# Unit tests.
+# -----------------------------------------------------------------------------
+
+test_c_flags := \
+ -fstack-protector-all \
+ -g \
+ -Wall -Wextra \
+ -Werror \
+ -fno-builtin \
+
+test_src_files := \
+ storaged_test.cpp \
+
+# Build tests for the logger. Run with:
+# adb shell /data/nativetest/storaged-unit-tests/storaged-unit-tests
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(test_module_prefix)unit-tests
+LOCAL_MODULE_TAGS := $(test_tags)
+LOCAL_CFLAGS += $(test_c_flags)
+LOCAL_STATIC_LIBRARIES := libstoraged
+LOCAL_SHARED_LIBRARIES := libbase libcutils liblog
+LOCAL_SRC_FILES := $(test_src_files)
+include $(BUILD_NATIVE_TEST)
diff --git a/storaged/tests/storaged_test.cpp b/storaged/tests/storaged_test.cpp
new file mode 100644
index 0000000..4ca9839
--- /dev/null
+++ b/storaged/tests/storaged_test.cpp
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2016 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 <deque>
+#include <fcntl.h>
+#include <random>
+#include <string.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+//#include <private/android_logger.h>
+
+#include <storaged.h> // data structures
+#include <storaged_utils.h> // functions to test
+
+#define MMC_DISK_STATS_PATH "/sys/block/mmcblk0/stat"
+#define SDA_DISK_STATS_PATH "/sys/block/sda/stat"
+#define EMMC_EXT_CSD_PATH "/d/mmc0/mmc0:0001/ext_csd"
+#define INIT_TASK_IO_PATH "/proc/1/io"
+
+static void pause(uint32_t sec) {
+ const char* path = "/cache/test";
+ int fd = open(path, O_WRONLY | O_CREAT);
+ ASSERT_LT(-1, fd);
+ char buffer[2048];
+ memset(buffer, 1, sizeof(buffer));
+ int loop_size = 100;
+ for (int i = 0; i < loop_size; ++i) {
+ ASSERT_EQ(2048, write(fd, buffer, sizeof(buffer)));
+ }
+ fsync(fd);
+ close(fd);
+
+ fd = open(path, O_RDONLY);
+ ASSERT_LT(-1, fd);
+ for (int i = 0; i < loop_size; ++i) {
+ ASSERT_EQ(2048, read(fd, buffer, sizeof(buffer)));
+ }
+ close(fd);
+
+ sleep(sec);
+}
+
+// the return values of the tested functions should be the expected ones
+const char* DISK_STATS_PATH;
+TEST(storaged_test, retvals) {
+ struct disk_stats stats;
+ struct emmc_info info;
+ memset(&stats, 0, sizeof(struct disk_stats));
+ memset(&info, 0, sizeof(struct emmc_info));
+
+ int emmc_fd = open(EMMC_EXT_CSD_PATH, O_RDONLY);
+ if (emmc_fd >= 0) {
+ EXPECT_TRUE(parse_emmc_ecsd(emmc_fd, &info));
+ }
+
+ if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
+ DISK_STATS_PATH = MMC_DISK_STATS_PATH;
+ } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
+ DISK_STATS_PATH = SDA_DISK_STATS_PATH;
+ } else {
+ return;
+ }
+
+ EXPECT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+
+ struct disk_stats old_stats;
+ memset(&old_stats, 0, sizeof(struct disk_stats));
+ old_stats = stats;
+
+ const char wrong_path[] = "/this/is/wrong";
+ EXPECT_FALSE(parse_disk_stats(wrong_path, &stats));
+
+ // reading a wrong path should not damage the output structure
+ EXPECT_EQ(0, memcmp(&stats, &old_stats, sizeof(disk_stats)));
+}
+
+TEST(storaged_test, disk_stats) {
+ struct disk_stats stats;
+ memset(&stats, 0, sizeof(struct disk_stats));
+
+ ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+
+ // every entry of stats (except io_in_flight) should all be greater than 0
+ for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
+ if (i == 8) continue; // skip io_in_flight which can be 0
+ EXPECT_LT((uint64_t)0, *((uint64_t*)&stats + i));
+ }
+
+ // accumulation of the increments should be the same with the overall increment
+ struct disk_stats base, tmp, curr, acc, inc[5];
+ memset(&base, 0, sizeof(struct disk_stats));
+ memset(&tmp, 0, sizeof(struct disk_stats));
+ memset(&acc, 0, sizeof(struct disk_stats));
+
+ for (uint i = 0; i < 5; ++i) {
+ ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &curr));
+ if (i == 0) {
+ base = curr;
+ tmp = curr;
+ sleep(5);
+ continue;
+ }
+ inc[i] = get_inc_disk_stats(&tmp, &curr);
+ add_disk_stats(&inc[i], &acc);
+ tmp = curr;
+ pause(5);
+ }
+ struct disk_stats overall_inc;
+ memset(&overall_inc, 0, sizeof(disk_stats));
+ overall_inc= get_inc_disk_stats(&base, &curr);
+
+ for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
+ if (i == 8) continue; // skip io_in_flight which can be 0
+ EXPECT_EQ(*((uint64_t*)&overall_inc + i), *((uint64_t*)&acc + i));
+ }
+}
+
+TEST(storaged_test, emmc_info) {
+ struct emmc_info info, void_info;
+ memset(&info, 0, sizeof(struct emmc_info));
+ memset(&void_info, 0, sizeof(struct emmc_info));
+
+ if (access(EMMC_EXT_CSD_PATH, R_OK) >= 0) {
+ int emmc_fd = open(EMMC_EXT_CSD_PATH, O_RDONLY);
+ ASSERT_GE(emmc_fd, 0);
+ ASSERT_TRUE(parse_emmc_ecsd(emmc_fd, &info));
+ // parse_emmc_ecsd() should put something in info.
+ EXPECT_NE(0, memcmp(&void_info, &info, sizeof(struct emmc_info)));
+ }
+}
+
+TEST(storaged_test, task_info) {
+ // parse_task_info should read something other than 0 from /proc/1/*
+ struct task_info task_info;
+ memset(&task_info, 0, sizeof(task_info));
+
+ if (!parse_task_info(1, &task_info)) return;
+
+ EXPECT_EQ((uint32_t)1, task_info.pid);
+ EXPECT_LT((uint64_t)0, task_info.rchar);
+ EXPECT_LT((uint64_t)0, task_info.wchar);
+ EXPECT_LT((uint64_t)0, task_info.syscr);
+ EXPECT_LT((uint64_t)0, task_info.syscw);
+ EXPECT_LT((uint64_t)0, task_info.read_bytes);
+ EXPECT_LT((uint64_t)0, task_info.write_bytes);
+ // cancelled_write_bytes of init could be 0, there is no need to test
+ EXPECT_LE((uint64_t)0, task_info.starttime);
+ EXPECT_NE((char*)NULL, strstr(task_info.cmd, "init"));
+
+ // Entries in /proc/1/io should be increasing through time
+ struct task_info task_old, task_new;
+ memset(&task_old, 0, sizeof(task_old));
+ memset(&task_new, 0, sizeof(task_new));
+
+ // parse_task_info should succeed at this point
+ ASSERT_TRUE(parse_task_info(1, &task_old));
+ sleep(1);
+ ASSERT_TRUE(parse_task_info(1, &task_new));
+
+ EXPECT_EQ(task_old.pid, task_new.pid);
+ EXPECT_LE(task_old.rchar, task_new.rchar);
+ EXPECT_LE(task_old.wchar, task_new.wchar);
+ EXPECT_LE(task_old.syscr, task_new.syscr);
+ EXPECT_LE(task_old.syscw, task_new.syscw);
+ EXPECT_LE(task_old.read_bytes, task_new.read_bytes);
+ EXPECT_LE(task_old.write_bytes, task_new.write_bytes);
+ EXPECT_LE(task_old.cancelled_write_bytes, task_new.cancelled_write_bytes);
+ EXPECT_EQ(task_old.starttime, task_new.starttime);
+ EXPECT_EQ(0, strcmp(task_old.cmd, task_new.cmd));
+}
+
+static double mean(std::deque<uint32_t> nums) {
+ double sum = 0.0;
+ for (uint32_t i : nums) {
+ sum += i;
+ }
+ return sum / nums.size();
+}
+
+static double standard_deviation(std::deque<uint32_t> nums) {
+ double sum = 0.0;
+ double avg = mean(nums);
+ for (uint32_t i : nums) {
+ sum += ((double)i - avg) * ((double)i - avg);
+ }
+ return sqrt(sum / nums.size());
+}
+
+TEST(storaged_test, stream_stats) {
+ // 100 random numbers
+ std::vector<uint32_t> data = {8147,9058,1270,9134,6324,975,2785,5469,9575,9649,1576,9706,9572,4854,8003,1419,4218,9157,7922,9595,6557,357,8491,9340,6787,7577,7431,3922,6555,1712,7060,318,2769,462,971,8235,6948,3171,9502,344,4387,3816,7655,7952,1869,4898,4456,6463,7094,7547,2760,6797,6551,1626,1190,4984,9597,3404,5853,2238,7513,2551,5060,6991,8909,9593,5472,1386,1493,2575,8407,2543,8143,2435,9293,3500,1966,2511,6160,4733,3517,8308,5853,5497,9172,2858,7572,7537,3804,5678,759,540,5308,7792,9340,1299,5688,4694,119,3371};
+ std::deque<uint32_t> test_data;
+ stream_stats sstats;
+ for (uint32_t i : data) {
+ test_data.push_back(i);
+ sstats.add(i);
+
+ EXPECT_EQ((int)standard_deviation(test_data), (int)sstats.get_std());
+ EXPECT_EQ((int)mean(test_data), (int)sstats.get_mean());
+ }
+
+ for (uint32_t i : data) {
+ test_data.pop_front();
+ sstats.evict(i);
+
+ EXPECT_EQ((int)standard_deviation(test_data), (int)sstats.get_std());
+ EXPECT_EQ((int)mean(test_data), (int)sstats.get_mean());
+ }
+
+ // some real data
+ std::vector<uint32_t> another_data = {113875,81620,103145,28327,86855,207414,96526,52567,28553,250311};
+ test_data.clear();
+ uint32_t window_size = 2;
+ uint32_t idx;
+ stream_stats sstats1;
+ for (idx = 0; idx < window_size; ++idx) {
+ test_data.push_back(another_data[idx]);
+ sstats1.add(another_data[idx]);
+ }
+ EXPECT_EQ((int)standard_deviation(test_data), (int)sstats1.get_std());
+ EXPECT_EQ((int)mean(test_data), (int)sstats1.get_mean());
+ for (;idx < another_data.size(); ++idx) {
+ test_data.pop_front();
+ sstats1.evict(another_data[idx - window_size]);
+ test_data.push_back(another_data[idx]);
+ sstats1.add(another_data[idx]);
+ EXPECT_EQ((int)standard_deviation(test_data), (int)sstats1.get_std());
+ EXPECT_EQ((int)mean(test_data), (int)sstats1.get_mean());
+ }
+}
+
+static void expect_increasing(struct task_info told, struct task_info tnew) {
+ ASSERT_EQ(told.pid, tnew.pid);
+ ASSERT_EQ(told.starttime, tnew.starttime);
+ ASSERT_EQ(strcmp(told.cmd, tnew.cmd), 0);
+
+ EXPECT_LE(told.rchar, tnew.rchar);
+ EXPECT_LE(told.wchar, tnew.wchar);
+ EXPECT_LE(told.syscr, tnew.syscr);
+ EXPECT_LE(told.syscw, tnew.syscw);
+ EXPECT_LE(told.read_bytes, tnew.read_bytes);
+ EXPECT_LE(told.write_bytes, tnew.write_bytes);
+ EXPECT_LE(told.cancelled_write_bytes, tnew.cancelled_write_bytes);
+}
+
+static void expect_equal(struct task_info told, struct task_info tnew) {
+ ASSERT_EQ(told.pid, tnew.pid);
+ ASSERT_EQ(told.starttime, tnew.starttime);
+ ASSERT_EQ(strcmp(told.cmd, tnew.cmd), 0);
+
+ EXPECT_EQ(told.rchar, tnew.rchar);
+ EXPECT_EQ(told.wchar, tnew.wchar);
+ EXPECT_EQ(told.syscr, tnew.syscr);
+ EXPECT_EQ(told.syscw, tnew.syscw);
+ EXPECT_EQ(told.read_bytes, tnew.read_bytes);
+ EXPECT_EQ(told.write_bytes, tnew.write_bytes);
+ EXPECT_EQ(told.cancelled_write_bytes, tnew.cancelled_write_bytes);
+}
+
+static std::set<uint32_t> find_overlap(std::unordered_map<uint32_t, struct task_info> t1,
+ std::unordered_map<uint32_t, struct task_info> t2) {
+ std::set<uint32_t> retval;
+ for (auto i : t1) {
+ if (t2.find(i.first) != t2.end()) {
+ retval.insert(i.first);
+ }
+ }
+
+ return retval;
+}
+
+static std::set<std::string> find_overlap(std::unordered_map<std::string, struct task_info> t1,
+ std::unordered_map<std::string, struct task_info> t2) {
+ std::set<std::string> retval;
+ for (auto i : t1) {
+ if (t2.find(i.first) != t2.end()) {
+ retval.insert(i.first);
+ }
+ }
+
+ return retval;
+}
+
+static bool cmp_app_name(struct task_info i, struct task_info j) {
+ return strcmp(i.cmd, j.cmd) > 0;
+}
+
+static void expect_match(std::vector<struct task_info> v1, std::vector<struct task_info> v2) {
+ ASSERT_EQ(v1.size(), v2.size());
+ std::sort(v1.begin(), v1.end(), cmp_app_name);
+ std::sort(v2.begin(), v2.end(), cmp_app_name);
+
+ for (uint i = 0; i < v1.size(); ++i) {
+ expect_equal(v1[i], v2[i]);
+ }
+}
+
+static void add_task_info(struct task_info* src, struct task_info* dst) {
+ ASSERT_EQ(0, strcmp(src->cmd, dst->cmd));
+
+ dst->pid = 0;
+ dst->rchar += src->rchar;
+ dst->wchar += src->wchar;
+ dst->syscr += src->syscr;
+ dst->syscw += src->syscw;
+ dst->read_bytes += src->read_bytes;
+ dst->write_bytes += src->write_bytes;
+ dst->cancelled_write_bytes += src->cancelled_write_bytes;
+ dst->starttime = 0;
+}
+
+static std::vector<struct task_info>
+categorize_tasks(std::unordered_map<uint32_t, struct task_info> tasks) {
+ std::unordered_map<std::string, struct task_info> tasks_cmd;
+ for (auto i : tasks) {
+ std::string cmd = i.second.cmd;
+ if (tasks_cmd.find(cmd) == tasks_cmd.end()) {
+ tasks_cmd[cmd] = i.second;
+ } else {
+ add_task_info(&i.second, &tasks_cmd[cmd]);
+ }
+ }
+
+ std::vector<struct task_info> retval(tasks_cmd.size());
+ int cnt = 0;
+ for (auto i : tasks_cmd) {
+ retval[cnt++] = i.second;
+ }
+
+ return retval;
+}
+
+#define TEST_LOOPS 20
+TEST(storaged_test, tasks_t) {
+ // pass this test if /proc/[pid]/io is not readable
+ const char* test_paths[] = {"/proc/1/io", "/proc/1/comm", "/proc/1/cmdline", "/proc/1/stat"};
+ for (uint i = 0; i < sizeof(test_paths) / sizeof(const char*); ++i) {
+ if (access(test_paths[i], R_OK) < 0) return;
+ }
+
+ tasks_t tasks;
+ EXPECT_EQ((uint32_t)0, tasks.mRunning.size());
+ EXPECT_EQ((uint32_t)0, tasks.mOld.size());
+
+ tasks.update_running_tasks();
+
+ std::unordered_map<uint32_t, struct task_info> prev_running = tasks.mRunning;
+ std::unordered_map<std::string, struct task_info> prev_old = tasks.mOld;
+
+ // hashmap maintaining
+ std::unordered_map<uint32_t, struct task_info> tasks_pid = tasks.mRunning;
+
+ // get_running_tasks() should return something other than a null map
+ std::unordered_map<uint32_t, struct task_info> test = tasks.get_running_tasks();
+ EXPECT_LE((uint32_t)1, test.size());
+
+ for (int i = 0; i < TEST_LOOPS; ++i) {
+ tasks.update_running_tasks();
+
+ std::set<uint32_t> overlap_running = find_overlap(prev_running, tasks.mRunning);
+ std::set<std::string> overlap_old = find_overlap(prev_old, tasks.mOld);
+
+ // overlap_running should capture init(pid == 1), since init never get killed
+ EXPECT_LE((uint32_t)1, overlap_running.size());
+ EXPECT_NE(overlap_running.find((uint32_t)1), overlap_running.end());
+ // overlap_old should never capture init, since init never get killed
+ EXPECT_EQ(overlap_old.find("init"), overlap_old.end());
+
+ // overlapping entries in previous and current running-tasks map should have increasing contents
+ for (uint32_t i : overlap_running) {
+ expect_increasing(prev_running[i], tasks.mRunning[i]);
+ }
+
+ // overlapping entries in previous and current killed-tasks map should have increasing contents
+ // and the map size should also be increasing
+ for (std::string i : overlap_old) {
+ expect_increasing(prev_old[i], tasks.mOld[i]);
+ }
+ EXPECT_LE(prev_old.size(), tasks.mRunning.size());
+
+ // update app name & tasks_pid
+ for (auto i : tasks.mRunning) {
+ // test will fail if the pid got wrapped
+ if (tasks_pid.find(i.first) != tasks_pid.end()) {
+ expect_increasing(tasks_pid[i.first], i.second);
+ tasks_pid[i.first] = i.second;
+ } else {
+ tasks_pid[i.first] = i.second;
+ }
+ }
+
+ // get maintained tasks
+ std::vector<struct task_info> test_tasks = categorize_tasks(tasks_pid);
+ std::vector<struct task_info> real_tasks = tasks.get_tasks();
+
+ expect_match(test_tasks, real_tasks);
+
+ prev_running = tasks.mRunning;
+ prev_old = tasks.mOld;
+
+ pause(5);
+ }
+}
+
+static struct disk_perf disk_perf_multiply(struct disk_perf perf, double mul) {
+ struct disk_perf retval;
+ retval.read_perf = (double)perf.read_perf * mul;
+ retval.read_ios = (double)perf.read_ios * mul;
+ retval.write_perf = (double)perf.write_perf * mul;
+ retval.write_ios = (double)perf.write_ios * mul;
+ retval.queue = (double)perf.queue * mul;
+
+ return retval;
+}
+
+static struct disk_stats disk_stats_add(struct disk_stats stats1, struct disk_stats stats2) {
+ struct disk_stats retval;
+ retval.read_ios = stats1.read_ios + stats2.read_ios;
+ retval.read_merges = stats1.read_merges + stats2.read_merges;
+ retval.read_sectors = stats1.read_sectors + stats2.read_sectors;
+ retval.read_ticks = stats1.read_ticks + stats2.read_ticks;
+ retval.write_ios = stats1.write_ios + stats2.write_ios;
+ retval.write_merges = stats1.write_merges + stats2.write_merges;
+ retval.write_sectors = stats1.write_sectors + stats2.write_sectors;
+ retval.write_ticks = stats1.write_ticks + stats2.write_ticks;
+ retval.io_in_flight = stats1.io_in_flight + stats2.io_in_flight;
+ retval.io_ticks = stats1.io_ticks + stats2.io_ticks;
+ retval.io_in_queue = stats1.io_in_queue + stats2.io_in_queue;
+ retval.end_time = stats1.end_time + stats2.end_time;
+
+ return retval;
+}
+
+TEST(storaged_test, disk_stats_monitor) {
+ // asserting that there is one file for diskstats
+ ASSERT_TRUE(access(MMC_DISK_STATS_PATH, R_OK) >= 0 || access(SDA_DISK_STATS_PATH, R_OK) >= 0);
+ // testing if detect() will return the right value
+ disk_stats_monitor dsm_detect;
+ // feed monitor with constant perf data for io perf baseline
+ // using constant perf is reasonable since the functionality of stream_stats
+ // has already been tested
+ struct disk_perf norm_perf = {
+ .read_perf = 10 * 1024,
+ .read_ios = 50,
+ .write_perf = 5 * 1024,
+ .write_ios = 25,
+ .queue = 5
+ };
+
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_real_distribution<> rand(0.8, 1.2);
+
+ for (uint i = 0; i < dsm_detect.mWindow; ++i) {
+ struct disk_perf perf = disk_perf_multiply(norm_perf, rand(gen));
+
+ dsm_detect.add(&perf);
+ dsm_detect.mBuffer.push(perf);
+ EXPECT_EQ(dsm_detect.mBuffer.size(), (uint64_t)i + 1);
+ }
+
+ dsm_detect.mValid = true;
+ dsm_detect.update_mean();
+ dsm_detect.update_std();
+
+ for (double i = 0; i < 2 * dsm_detect.mSigma; i += 0.5) {
+ struct disk_perf test_perf;
+ struct disk_perf test_mean = dsm_detect.mMean;
+ struct disk_perf test_std = dsm_detect.mStd;
+
+ test_perf.read_perf = (double)test_mean.read_perf - i * test_std.read_perf;
+ test_perf.read_ios = (double)test_mean.read_ios - i * test_std.read_ios;
+ test_perf.write_perf = (double)test_mean.write_perf - i * test_std.write_perf;
+ test_perf.write_ios = (double)test_mean.write_ios - i * test_std.write_ios;
+ test_perf.queue = (double)test_mean.queue + i * test_std.queue;
+
+ EXPECT_EQ((i > dsm_detect.mSigma), dsm_detect.detect(&test_perf));
+ }
+
+ // testing if stalled disk_stats can be correctly accumulated in the monitor
+ disk_stats_monitor dsm_acc;
+ struct disk_stats norm_inc = {
+ .read_ios = 200,
+ .read_merges = 0,
+ .read_sectors = 200,
+ .read_ticks = 200,
+ .write_ios = 100,
+ .write_merges = 0,
+ .write_sectors = 100,
+ .write_ticks = 100,
+ .io_in_flight = 0,
+ .io_ticks = 600,
+ .io_in_queue = 300,
+ .start_time = 0,
+ .end_time = 100,
+ .counter = 0,
+ .io_avg = 0
+ };
+
+ struct disk_stats stall_inc = {
+ .read_ios = 200,
+ .read_merges = 0,
+ .read_sectors = 20,
+ .read_ticks = 200,
+ .write_ios = 100,
+ .write_merges = 0,
+ .write_sectors = 10,
+ .write_ticks = 100,
+ .io_in_flight = 0,
+ .io_ticks = 600,
+ .io_in_queue = 1200,
+ .start_time = 0,
+ .end_time = 100,
+ .counter = 0,
+ .io_avg = 0
+ };
+
+ struct disk_stats stats_base;
+ memset(&stats_base, 0, sizeof(stats_base));
+
+ int loop_size = 100;
+ for (int i = 0; i < loop_size; ++i) {
+ stats_base = disk_stats_add(stats_base, norm_inc);
+ dsm_acc.update(&stats_base);
+ EXPECT_EQ(dsm_acc.mValid, (uint32_t)(i + 1) >= dsm_acc.mWindow);
+ EXPECT_FALSE(dsm_acc.mStall);
+ }
+
+ stats_base = disk_stats_add(stats_base, stall_inc);
+ dsm_acc.update(&stats_base);
+ EXPECT_TRUE(dsm_acc.mValid);
+ EXPECT_TRUE(dsm_acc.mStall);
+
+ for (int i = 0; i < 10; ++i) {
+ stats_base = disk_stats_add(stats_base, norm_inc);
+ dsm_acc.update(&stats_base);
+ EXPECT_TRUE(dsm_acc.mValid);
+ EXPECT_FALSE(dsm_acc.mStall);
+ }
+}
+
+static void expect_increasing(struct disk_stats stats1, struct disk_stats stats2) {
+ EXPECT_LE(stats1.read_ios, stats2.read_ios);
+ EXPECT_LE(stats1.read_merges, stats2.read_merges);
+ EXPECT_LE(stats1.read_sectors, stats2.read_sectors);
+ EXPECT_LE(stats1.read_ticks, stats2.read_ticks);
+
+ EXPECT_LE(stats1.write_ios, stats2.write_ios);
+ EXPECT_LE(stats1.write_merges, stats2.write_merges);
+ EXPECT_LE(stats1.write_sectors, stats2.write_sectors);
+ EXPECT_LE(stats1.write_ticks, stats2.write_ticks);
+
+ EXPECT_LE(stats1.io_ticks, stats2.io_ticks);
+ EXPECT_LE(stats1.io_in_queue, stats2.io_in_queue);
+}
+
+TEST(storaged_test, disk_stats_publisher) {
+ // asserting that there is one file for diskstats
+ ASSERT_TRUE(access(MMC_DISK_STATS_PATH, R_OK) >= 0 || access(SDA_DISK_STATS_PATH, R_OK) >= 0);
+ disk_stats_publisher dsp;
+ struct disk_stats prev;
+ memset(&prev, 0, sizeof(prev));
+
+ for (int i = 0; i < TEST_LOOPS; ++i) {
+ dsp.update();
+ expect_increasing(prev, dsp.mPrevious);
+ prev = dsp.mPrevious;
+ pause(10);
+ }
+}
+