diff options
Diffstat (limited to 'liblog/properties.cpp')
-rw-r--r-- | liblog/properties.cpp | 668 |
1 files changed, 668 insertions, 0 deletions
diff --git a/liblog/properties.cpp b/liblog/properties.cpp new file mode 100644 index 000000000..37670ecd6 --- /dev/null +++ b/liblog/properties.cpp @@ -0,0 +1,668 @@ +/* +** Copyright 2014, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <log/log_properties.h> + +#include <ctype.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <algorithm> + +#include <private/android_logger.h> + +#include "logger_write.h" + +#ifdef __ANDROID__ +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include <sys/_system_properties.h> + +static pthread_mutex_t lock_loggable = PTHREAD_MUTEX_INITIALIZER; + +static int lock() { + /* + * If we trigger a signal handler in the middle of locked activity and the + * signal handler logs a message, we could get into a deadlock state. + */ + /* + * Any contention, and we can turn around and use the non-cached method + * in less time than the system call associated with a mutex to deal with + * the contention. + */ + return pthread_mutex_trylock(&lock_loggable); +} + +static void unlock() { + pthread_mutex_unlock(&lock_loggable); +} + +struct cache { + const prop_info* pinfo; + uint32_t serial; +}; + +struct cache_char { + struct cache cache; + unsigned char c; +}; + +static int check_cache(struct cache* cache) { + return cache->pinfo && __system_property_serial(cache->pinfo) != cache->serial; +} + +#define BOOLEAN_TRUE 0xFF +#define BOOLEAN_FALSE 0xFE + +static void refresh_cache(struct cache_char* cache, const char* key) { + char buf[PROP_VALUE_MAX]; + + if (!cache->cache.pinfo) { + cache->cache.pinfo = __system_property_find(key); + if (!cache->cache.pinfo) { + return; + } + } + cache->cache.serial = __system_property_serial(cache->cache.pinfo); + __system_property_read(cache->cache.pinfo, 0, buf); + switch (buf[0]) { + case 't': + case 'T': + cache->c = strcasecmp(buf + 1, "rue") ? buf[0] : BOOLEAN_TRUE; + break; + case 'f': + case 'F': + cache->c = strcasecmp(buf + 1, "alse") ? buf[0] : BOOLEAN_FALSE; + break; + default: + cache->c = buf[0]; + } +} + +static int __android_log_level(const char* tag, size_t len) { + /* sizeof() is used on this array below */ + static const char log_namespace[] = "persist.log.tag."; + static const size_t base_offset = 8; /* skip "persist." */ + + if (tag == nullptr || len == 0) { + auto& tag_string = GetDefaultTag(); + tag = tag_string.c_str(); + len = tag_string.size(); + } + + /* sizeof(log_namespace) = strlen(log_namespace) + 1 */ + char key[sizeof(log_namespace) + len]; + char* kp; + size_t i; + char c = 0; + /* + * Single layer cache of four properties. Priorities are: + * log.tag.<tag> + * persist.log.tag.<tag> + * log.tag + * persist.log.tag + * Where the missing tag matches all tags and becomes the + * system global default. We do not support ro.log.tag* . + */ + static char* last_tag; + static size_t last_tag_len; + static uint32_t global_serial; + /* some compilers erroneously see uninitialized use. !not_locked */ + uint32_t current_global_serial = 0; + static struct cache_char tag_cache[2]; + static struct cache_char global_cache[2]; + int change_detected; + int global_change_detected; + int not_locked; + + strcpy(key, log_namespace); + + global_change_detected = change_detected = not_locked = lock(); + + if (!not_locked) { + /* + * check all known serial numbers to changes. + */ + for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) { + if (check_cache(&tag_cache[i].cache)) { + change_detected = 1; + } + } + for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) { + if (check_cache(&global_cache[i].cache)) { + global_change_detected = 1; + } + } + + current_global_serial = __system_property_area_serial(); + if (current_global_serial != global_serial) { + change_detected = 1; + global_change_detected = 1; + } + } + + if (len) { + int local_change_detected = change_detected; + if (!not_locked) { + if (!last_tag || !last_tag[0] || (last_tag[0] != tag[0]) || + strncmp(last_tag + 1, tag + 1, last_tag_len - 1)) { + /* invalidate log.tag.<tag> cache */ + for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) { + tag_cache[i].cache.pinfo = NULL; + tag_cache[i].c = '\0'; + } + if (last_tag) last_tag[0] = '\0'; + local_change_detected = 1; + } + if (!last_tag || !last_tag[0]) { + if (!last_tag) { + last_tag = static_cast<char*>(calloc(1, len + 1)); + last_tag_len = 0; + if (last_tag) last_tag_len = len + 1; + } else if (len >= last_tag_len) { + last_tag = static_cast<char*>(realloc(last_tag, len + 1)); + last_tag_len = 0; + if (last_tag) last_tag_len = len + 1; + } + if (last_tag) { + strncpy(last_tag, tag, len); + last_tag[len] = '\0'; + } + } + } + strncpy(key + sizeof(log_namespace) - 1, tag, len); + key[sizeof(log_namespace) - 1 + len] = '\0'; + + kp = key; + for (i = 0; i < (sizeof(tag_cache) / sizeof(tag_cache[0])); ++i) { + struct cache_char* cache = &tag_cache[i]; + struct cache_char temp_cache; + + if (not_locked) { + temp_cache.cache.pinfo = NULL; + temp_cache.c = '\0'; + cache = &temp_cache; + } + if (local_change_detected) { + refresh_cache(cache, kp); + } + + if (cache->c) { + c = cache->c; + break; + } + + kp = key + base_offset; + } + } + + switch (toupper(c)) { /* if invalid, resort to global */ + case 'V': + case 'D': + case 'I': + case 'W': + case 'E': + case 'F': /* Not officially supported */ + case 'A': + case 'S': + case BOOLEAN_FALSE: /* Not officially supported */ + break; + default: + /* clear '.' after log.tag */ + key[sizeof(log_namespace) - 2] = '\0'; + + kp = key; + for (i = 0; i < (sizeof(global_cache) / sizeof(global_cache[0])); ++i) { + struct cache_char* cache = &global_cache[i]; + struct cache_char temp_cache; + + if (not_locked) { + temp_cache = *cache; + if (temp_cache.cache.pinfo != cache->cache.pinfo) { /* check atomic */ + temp_cache.cache.pinfo = NULL; + temp_cache.c = '\0'; + } + cache = &temp_cache; + } + if (global_change_detected) { + refresh_cache(cache, kp); + } + + if (cache->c) { + c = cache->c; + break; + } + + kp = key + base_offset; + } + break; + } + + if (!not_locked) { + global_serial = current_global_serial; + unlock(); + } + + switch (toupper(c)) { + /* clang-format off */ + case 'V': return ANDROID_LOG_VERBOSE; + case 'D': return ANDROID_LOG_DEBUG; + case 'I': return ANDROID_LOG_INFO; + case 'W': return ANDROID_LOG_WARN; + case 'E': return ANDROID_LOG_ERROR; + case 'F': /* FALLTHRU */ /* Not officially supported */ + case 'A': return ANDROID_LOG_FATAL; + case BOOLEAN_FALSE: /* FALLTHRU */ /* Not Officially supported */ + case 'S': return ANDROID_LOG_SILENT; + /* clang-format on */ + } + return -1; +} + +int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio) { + int minimum_log_priority = __android_log_get_minimum_priority(); + int property_log_level = __android_log_level(tag, len); + + if (property_log_level >= 0 && minimum_log_priority != ANDROID_LOG_DEFAULT) { + return prio >= std::min(property_log_level, minimum_log_priority); + } else if (property_log_level >= 0) { + return prio >= property_log_level; + } else if (minimum_log_priority != ANDROID_LOG_DEFAULT) { + return prio >= minimum_log_priority; + } else { + return prio >= default_prio; + } +} + +int __android_log_is_loggable(int prio, const char* tag, int default_prio) { + auto len = tag ? strlen(tag) : 0; + return __android_log_is_loggable_len(prio, tag, len, default_prio); +} + +int __android_log_is_debuggable() { + static uint32_t serial; + static struct cache_char tag_cache; + static const char key[] = "ro.debuggable"; + int ret; + + if (tag_cache.c) { /* ro property does not change after set */ + ret = tag_cache.c == '1'; + } else if (lock()) { + struct cache_char temp_cache = {{NULL, 0xFFFFFFFF}, '\0'}; + refresh_cache(&temp_cache, key); + ret = temp_cache.c == '1'; + } else { + int change_detected = check_cache(&tag_cache.cache); + uint32_t current_serial = __system_property_area_serial(); + if (current_serial != serial) { + change_detected = 1; + } + if (change_detected) { + refresh_cache(&tag_cache, key); + serial = current_serial; + } + ret = tag_cache.c == '1'; + + unlock(); + } + + return ret; +} + +/* + * For properties that are read often, but generally remain constant. + * Since a change is rare, we will accept a trylock failure gracefully. + * Use a separate lock from is_loggable to keep contention down b/25563384. + */ +struct cache2_char { + pthread_mutex_t lock; + uint32_t serial; + const char* key_persist; + struct cache_char cache_persist; + const char* key_ro; + struct cache_char cache_ro; + unsigned char (*const evaluate)(const struct cache2_char* self); +}; + +static inline unsigned char do_cache2_char(struct cache2_char* self) { + uint32_t current_serial; + int change_detected; + unsigned char c; + + if (pthread_mutex_trylock(&self->lock)) { + /* We are willing to accept some race in this context */ + return self->evaluate(self); + } + + change_detected = check_cache(&self->cache_persist.cache) || check_cache(&self->cache_ro.cache); + current_serial = __system_property_area_serial(); + if (current_serial != self->serial) { + change_detected = 1; + } + if (change_detected) { + refresh_cache(&self->cache_persist, self->key_persist); + refresh_cache(&self->cache_ro, self->key_ro); + self->serial = current_serial; + } + c = self->evaluate(self); + + pthread_mutex_unlock(&self->lock); + + return c; +} + +static unsigned char evaluate_persist_ro(const struct cache2_char* self) { + unsigned char c = self->cache_persist.c; + + if (c) { + return c; + } + + return self->cache_ro.c; +} + +/* + * Timestamp state generally remains constant, but can change at any time + * to handle developer requirements. + */ +clockid_t android_log_clockid() { + static struct cache2_char clockid = {PTHREAD_MUTEX_INITIALIZER, 0, + "persist.logd.timestamp", {{NULL, 0xFFFFFFFF}, '\0'}, + "ro.logd.timestamp", {{NULL, 0xFFFFFFFF}, '\0'}, + evaluate_persist_ro}; + + return (tolower(do_cache2_char(&clockid)) == 'm') ? CLOCK_MONOTONIC : CLOCK_REALTIME; +} + +/* + * Security state generally remains constant, but the DO must be able + * to turn off logging should it become spammy after an attack is detected. + */ +static unsigned char evaluate_security(const struct cache2_char* self) { + unsigned char c = self->cache_ro.c; + + return (c != BOOLEAN_FALSE) && c && (self->cache_persist.c == BOOLEAN_TRUE); +} + +int __android_log_security() { + static struct cache2_char security = { + PTHREAD_MUTEX_INITIALIZER, 0, + "persist.logd.security", {{NULL, 0xFFFFFFFF}, BOOLEAN_FALSE}, + "ro.organization_owned", {{NULL, 0xFFFFFFFF}, BOOLEAN_FALSE}, + evaluate_security}; + + return do_cache2_char(&security); +} + +/* + * Interface that represents the logd buffer size determination so that others + * need not guess our intentions. + */ + +/* Property helper */ +static bool check_flag(const char* prop, const char* flag) { + const char* cp = strcasestr(prop, flag); + if (!cp) { + return false; + } + /* We only will document comma (,) */ + static const char sep[] = ",:;|+ \t\f"; + if ((cp != prop) && !strchr(sep, cp[-1])) { + return false; + } + cp += strlen(flag); + return !*cp || !!strchr(sep, *cp); +} + +/* cache structure */ +struct cache_property { + struct cache cache; + char property[PROP_VALUE_MAX]; +}; + +static void refresh_cache_property(struct cache_property* cache, const char* key) { + if (!cache->cache.pinfo) { + cache->cache.pinfo = __system_property_find(key); + if (!cache->cache.pinfo) { + return; + } + } + cache->cache.serial = __system_property_serial(cache->cache.pinfo); + __system_property_read(cache->cache.pinfo, 0, cache->property); +} + +/* get boolean with the logger twist that supports eng adjustments */ +bool __android_logger_property_get_bool(const char* key, int flag) { + struct cache_property property = {{NULL, 0xFFFFFFFF}, {0}}; + if (flag & BOOL_DEFAULT_FLAG_PERSIST) { + char newkey[strlen("persist.") + strlen(key) + 1]; + snprintf(newkey, sizeof(newkey), "ro.%s", key); + refresh_cache_property(&property, newkey); + property.cache.pinfo = NULL; + property.cache.serial = 0xFFFFFFFF; + snprintf(newkey, sizeof(newkey), "persist.%s", key); + refresh_cache_property(&property, newkey); + property.cache.pinfo = NULL; + property.cache.serial = 0xFFFFFFFF; + } + + refresh_cache_property(&property, key); + + if (check_flag(property.property, "true")) { + return true; + } + if (check_flag(property.property, "false")) { + return false; + } + if (property.property[0]) { + flag &= ~(BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE); + } + if (check_flag(property.property, "eng")) { + flag |= BOOL_DEFAULT_FLAG_ENG; + } + /* this is really a "not" flag */ + if (check_flag(property.property, "svelte")) { + flag |= BOOL_DEFAULT_FLAG_SVELTE; + } + + /* Sanity Check */ + if (flag & (BOOL_DEFAULT_FLAG_SVELTE | BOOL_DEFAULT_FLAG_ENG)) { + flag &= ~BOOL_DEFAULT_FLAG_TRUE_FALSE; + flag |= BOOL_DEFAULT_TRUE; + } + + if ((flag & BOOL_DEFAULT_FLAG_SVELTE) && + __android_logger_property_get_bool("ro.config.low_ram", BOOL_DEFAULT_FALSE)) { + return false; + } + if ((flag & BOOL_DEFAULT_FLAG_ENG) && !__android_log_is_debuggable()) { + return false; + } + + return (flag & BOOL_DEFAULT_FLAG_TRUE_FALSE) != BOOL_DEFAULT_FALSE; +} + +bool __android_logger_valid_buffer_size(unsigned long value) { + static long pages, pagesize; + unsigned long maximum; + + if ((value < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < value)) { + return false; + } + + if (!pages) { + pages = sysconf(_SC_PHYS_PAGES); + } + if (pages < 1) { + return true; + } + + if (!pagesize) { + pagesize = sysconf(_SC_PAGESIZE); + if (pagesize <= 1) { + pagesize = PAGE_SIZE; + } + } + + /* maximum memory impact a somewhat arbitrary ~3% */ + pages = (pages + 31) / 32; + maximum = pages * pagesize; + + if ((maximum < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < maximum)) { + return true; + } + + return value <= maximum; +} + +struct cache2_property_size { + pthread_mutex_t lock; + uint32_t serial; + const char* key_persist; + struct cache_property cache_persist; + const char* key_ro; + struct cache_property cache_ro; + unsigned long (*const evaluate)(const struct cache2_property_size* self); +}; + +static inline unsigned long do_cache2_property_size(struct cache2_property_size* self) { + uint32_t current_serial; + int change_detected; + unsigned long v; + + if (pthread_mutex_trylock(&self->lock)) { + /* We are willing to accept some race in this context */ + return self->evaluate(self); + } + + change_detected = check_cache(&self->cache_persist.cache) || check_cache(&self->cache_ro.cache); + current_serial = __system_property_area_serial(); + if (current_serial != self->serial) { + change_detected = 1; + } + if (change_detected) { + refresh_cache_property(&self->cache_persist, self->key_persist); + refresh_cache_property(&self->cache_ro, self->key_ro); + self->serial = current_serial; + } + v = self->evaluate(self); + + pthread_mutex_unlock(&self->lock); + + return v; +} + +static unsigned long property_get_size_from_cache(const struct cache_property* cache) { + char* cp; + unsigned long value = strtoul(cache->property, &cp, 10); + + switch (*cp) { + case 'm': + case 'M': + value *= 1024; + [[fallthrough]]; + case 'k': + case 'K': + value *= 1024; + [[fallthrough]]; + case '\0': + break; + + default: + value = 0; + } + + if (!__android_logger_valid_buffer_size(value)) { + value = 0; + } + + return value; +} + +static unsigned long evaluate_property_get_size(const struct cache2_property_size* self) { + unsigned long size = property_get_size_from_cache(&self->cache_persist); + if (size) { + return size; + } + return property_get_size_from_cache(&self->cache_ro); +} + +unsigned long __android_logger_get_buffer_size(log_id_t logId) { + static const char global_tunable[] = "persist.logd.size"; /* Settings App */ + static const char global_default[] = "ro.logd.size"; /* BoardConfig.mk */ + static struct cache2_property_size global = { + /* clang-format off */ + PTHREAD_MUTEX_INITIALIZER, 0, + global_tunable, { { NULL, 0xFFFFFFFF }, {} }, + global_default, { { NULL, 0xFFFFFFFF }, {} }, + evaluate_property_get_size + /* clang-format on */ + }; + char key_persist[strlen(global_tunable) + strlen(".security") + 1]; + char key_ro[strlen(global_default) + strlen(".security") + 1]; + struct cache2_property_size local = { + /* clang-format off */ + PTHREAD_MUTEX_INITIALIZER, 0, + key_persist, { { NULL, 0xFFFFFFFF }, {} }, + key_ro, { { NULL, 0xFFFFFFFF }, {} }, + evaluate_property_get_size + /* clang-format on */ + }; + unsigned long property_size, default_size; + + default_size = do_cache2_property_size(&global); + if (!default_size) { + default_size = __android_logger_property_get_bool("ro.config.low_ram", BOOL_DEFAULT_FALSE) + ? LOG_BUFFER_MIN_SIZE /* 64K */ + : LOG_BUFFER_SIZE; /* 256K */ + } + + snprintf(key_persist, sizeof(key_persist), "%s.%s", global_tunable, + android_log_id_to_name(logId)); + snprintf(key_ro, sizeof(key_ro), "%s.%s", global_default, android_log_id_to_name(logId)); + property_size = do_cache2_property_size(&local); + + if (!property_size) { + property_size = default_size; + } + + if (!property_size) { + property_size = LOG_BUFFER_SIZE; + } + + return property_size; +} + +#else + +int __android_log_is_loggable(int prio, const char*, int) { + int minimum_priority = __android_log_get_minimum_priority(); + if (minimum_priority == ANDROID_LOG_DEFAULT) { + minimum_priority = ANDROID_LOG_INFO; + } + return prio >= minimum_priority; +} + +int __android_log_is_loggable_len(int prio, const char*, size_t, int def) { + return __android_log_is_loggable(prio, nullptr, def); +} + +int __android_log_is_debuggable() { + return 1; +} + +#endif
\ No newline at end of file |