diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/libdlog/log.c | 305 | ||||
-rw-r--r-- | src/libdlog/logconfig.c | 174 | ||||
-rw-r--r-- | src/libdlog/loglimiter.c | 419 | ||||
-rw-r--r-- | src/logger/logger.c | 1040 | ||||
-rwxr-xr-x | src/logutil/logutil.c | 777 | ||||
-rwxr-xr-x | src/shared/logprint.c | 735 |
6 files changed, 3450 insertions, 0 deletions
diff --git a/src/libdlog/log.c b/src/libdlog/log.c new file mode 100755 index 0000000..46fa2d1 --- /dev/null +++ b/src/libdlog/log.c @@ -0,0 +1,305 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*- + * DLOG + * Copyright (c) 2005-2008, The Android Open Source Project + * Copyright (c) 2012-2013 Samsung Electronics Co., Ltd. + * + * 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 "config.h" +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <fcntl.h> +#include <sys/uio.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <dlog.h> +#include "loglimiter.h" +#include "logconfig.h" +#ifdef HAVE_SYSTEMD_JOURNAL +#define SD_JOURNAL_SUPPRESS_LOCATION 1 +#include <syslog.h> +#include <systemd/sd-journal.h> +#endif +#ifdef FATAL_ON +#include <assert.h> +#endif + +#define LOG_BUF_SIZE 1024 + +#define LOG_MAIN "log_main" +#define LOG_RADIO "log_radio" +#define LOG_SYSTEM "log_system" +#define LOG_APPS "log_apps" + +#define VALUE_MAX 2 +#define LOG_CONFIG_FILE "/opt/etc/dlog.conf" + +#ifndef HAVE_SYSTEMD_JOURNAL +static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1, -1 }; +#endif +static int (*write_to_log)(log_id_t, log_priority, const char *tag, const char *msg) = NULL; +static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER; +static struct log_config config; + +#ifdef HAVE_SYSTEMD_JOURNAL +static inline int dlog_pri_to_journal_pri(log_priority prio) +{ + static int pri_table[DLOG_PRIO_MAX] = { + [DLOG_UNKNOWN] = LOG_DEBUG, + [DLOG_DEFAULT] = LOG_DEBUG, + [DLOG_VERBOSE] = LOG_DEBUG, + [DLOG_DEBUG] = LOG_DEBUG, + [DLOG_INFO] = LOG_INFO, + [DLOG_WARN] = LOG_WARNING, + [DLOG_ERROR] = LOG_ERR, + [DLOG_FATAL] = LOG_CRIT, + [DLOG_SILENT] = -1, + }; + + if (prio < 0 || prio >= DLOG_PRIO_MAX) + return DLOG_ERROR_INVALID_PARAMETER; + + return pri_table[prio]; +} + +static inline const char* dlog_id_to_string(log_id_t log_id) +{ + static const char* id_table[LOG_ID_MAX] = { + [LOG_ID_MAIN] = LOG_MAIN, + [LOG_ID_RADIO] = LOG_RADIO, + [LOG_ID_SYSTEM] = LOG_SYSTEM, + [LOG_ID_APPS] = LOG_APPS, + }; + + if (log_id < 0 || log_id >= LOG_ID_MAX || !id_table[log_id]) + return "UNKNOWN"; + + return id_table[log_id]; +} + +static int __write_to_log_sd_journal(log_id_t log_id, log_priority prio, const char *tag, const char *msg) +{ + pid_t tid = (pid_t)syscall(SYS_gettid); + /* XXX: sd_journal_sendv() with manually filed iov-s might be faster */ + return sd_journal_send("MESSAGE=%s", msg, + "PRIORITY=%i", dlog_pri_to_journal_pri(prio), + "LOG_TAG=%s", tag, + "LOG_ID=%s", dlog_id_to_string(log_id), + "TID=%d", tid, + NULL); +} + +#else +static int __write_to_log_null(log_id_t log_id, log_priority prio, const char *tag, const char *msg) +{ + return DLOG_ERROR_NOT_PERMITTED; +} + +static int __write_to_log_kernel(log_id_t log_id, log_priority prio, const char *tag, const char *msg) +{ + ssize_t ret; + int log_fd; + struct iovec vec[3]; + + if (log_id < LOG_ID_MAX) + log_fd = log_fds[log_id]; + else + return DLOG_ERROR_INVALID_PARAMETER; + + if (prio < DLOG_VERBOSE || prio >= DLOG_PRIO_MAX) + return DLOG_ERROR_INVALID_PARAMETER; + + if (!tag) + tag = ""; + + if (!msg) + return DLOG_ERROR_INVALID_PARAMETER; + + vec[0].iov_base = (unsigned char *) &prio; + vec[0].iov_len = 1; + vec[1].iov_base = (void *) tag; + vec[1].iov_len = strlen(tag) + 1; + vec[2].iov_base = (void *) msg; + vec[2].iov_len = strlen(msg) + 1; + + ret = writev(log_fd, vec, 3); + if (ret < 0) + ret = errno; + + return ret; +} +#endif + +static void __configure(void) +{ + if (0 > __log_config_read(LOG_CONFIG_FILE, &config)) { + config.lc_limiter = 0; + config.lc_plog = 0; + } + + if (config.lc_limiter) { + if (0 > __log_limiter_initialize()) { + config.lc_limiter = 0; + } + } +} + +static void __dlog_init(void) +{ + pthread_mutex_lock(&log_init_lock); + /* configuration */ + __configure(); +#ifdef HAVE_SYSTEMD_JOURNAL + write_to_log = __write_to_log_sd_journal; +#else + /* open device */ + log_fds[LOG_ID_MAIN] = open("/dev/"LOG_MAIN, O_WRONLY); + log_fds[LOG_ID_SYSTEM] = open("/dev/"LOG_SYSTEM, O_WRONLY); + log_fds[LOG_ID_RADIO] = open("/dev/"LOG_RADIO, O_WRONLY); + log_fds[LOG_ID_APPS] = open("/dev/"LOG_APPS, O_WRONLY); + if (log_fds[LOG_ID_MAIN] < 0) { + write_to_log = __write_to_log_null; + } else { + write_to_log = __write_to_log_kernel; + } + if (log_fds[LOG_ID_RADIO] < 0) + log_fds[LOG_ID_RADIO] = log_fds[LOG_ID_MAIN]; + if (log_fds[LOG_ID_SYSTEM] < 0) + log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN]; + if (log_fds[LOG_ID_APPS] < 0) + log_fds[LOG_ID_APPS] = log_fds[LOG_ID_MAIN]; +#endif + pthread_mutex_unlock(&log_init_lock); +} + +void __dlog_fatal_assert(int prio) +{ +#ifdef FATAL_ON + assert(!(prio == DLOG_FATAL)); +#endif +} + +static int dlog_should_log(log_id_t log_id, const char* tag, int prio) +{ + int should_log; + +#ifndef TIZEN_DEBUG_ENABLE + if (prio <= DLOG_DEBUG) + return DLOG_ERROR_INVALID_PARAMETER; +#endif + if (!tag) + return DLOG_ERROR_INVALID_PARAMETER; + + if (log_id < 0 || LOG_ID_MAX <= log_id) + return DLOG_ERROR_INVALID_PARAMETER; + + if (log_id != LOG_ID_APPS && !config.lc_plog) + return DLOG_ERROR_NOT_PERMITTED; + + if (config.lc_limiter) { + should_log = __log_limiter_pass_log(tag, prio); + + if (!should_log) { + return DLOG_ERROR_NOT_PERMITTED; + } else if (should_log < 0) { + write_to_log(log_id, prio, tag, + "Your log has been blocked due to limit of log lines per minute."); + return DLOG_ERROR_NOT_PERMITTED; + } + } + + return DLOG_ERROR_NONE; +} + +int __dlog_vprint(log_id_t log_id, int prio, const char *tag, const char *fmt, va_list ap) +{ + int ret; + char buf[LOG_BUF_SIZE]; + + if (write_to_log == NULL) + __dlog_init(); + + ret = dlog_should_log(log_id, tag, prio); + + if (ret < 0) + return ret; + + vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); + ret = write_to_log(log_id, prio, tag, buf); +#ifdef FATAL_ON + __dlog_fatal_assert(prio); +#endif + return ret; +} + +int __dlog_print(log_id_t log_id, int prio, const char *tag, const char *fmt, ...) +{ + int ret; + va_list ap; + char buf[LOG_BUF_SIZE]; + + if (write_to_log == NULL) + __dlog_init(); + + ret = dlog_should_log(log_id, tag, prio); + + if (ret < 0) + return ret; + + va_start(ap, fmt); + vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); + va_end(ap); + + ret = write_to_log(log_id, prio, tag, buf); +#ifdef FATAL_ON + __dlog_fatal_assert(prio); +#endif + return ret; +} + +int dlog_vprint(log_priority prio, const char *tag, const char *fmt, va_list ap) +{ + char buf[LOG_BUF_SIZE]; + + if (write_to_log == NULL) + __dlog_init(); + + vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); + + return write_to_log(LOG_ID_APPS, prio, tag, buf); +} + +int dlog_print(log_priority prio, const char *tag, const char *fmt, ...) +{ + va_list ap; + char buf[LOG_BUF_SIZE]; + + if (write_to_log == NULL) + __dlog_init(); + + va_start(ap, fmt); + vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); + va_end(ap); + + return write_to_log(LOG_ID_APPS, prio, tag, buf); +} + +void __attribute__((destructor)) __dlog_fini(void) +{ + __log_limiter_destroy(); +} diff --git a/src/libdlog/logconfig.c b/src/libdlog/logconfig.c new file mode 100644 index 0000000..6c7eabc --- /dev/null +++ b/src/libdlog/logconfig.c @@ -0,0 +1,174 @@ +/* + * DLOG + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "logconfig.h" +#include "loglimiter.h" + +/* Functions possible return value */ +#define RET_ERROR (-1) +#define RET_SUCCESS 0 + +#define CONFIG_LINE_MAX_LEN 256 +#define CONFIG_OPTION_MAX_LEN 64 + +/* Shortcut macros */ +#define isCOMMENT(c) ('#' == c) +#define isNEWLINE(c) ('\n' == c || '\r' == c) + +/* Dlog options definition */ +#define LOG_PLATFORM_STRING "PlatformLogs" +#define LOG_LIMITER_STRING "LogLimiter" + +/* Define options possible values */ +#define ALLOW_STRING "allow" +#define DENY_STRING "deny" +#define ON_STRING "on" +#define OFF_STRING "off" +#define isYES(c) (c == '1') +#define isNO(c) (c == '0') + + +static int log_config_multiplex_opt(char* opt_str, char* val_str, int prio, + struct log_config* config) +{ + int value = 0; + + /* There are only two ways to interpret the lines, so make here a short circuit */ + if (0 < prio) { /* Is it a rule or an option ? */ + /* For the filtering rule ... */ + + if (!strncasecmp(ALLOW_STRING, val_str, sizeof(ALLOW_STRING))) { + value = __LOG_LIMITER_LIMIT_MAX + 1; + } else if (!strncasecmp(DENY_STRING, val_str, + sizeof(DENY_STRING))) { + value = 0; + } else { + char* endptr = NULL; + + value = strtoul(val_str, &endptr, 0); + if (*endptr != '\0') { + return RET_ERROR; + } + } + + return __log_limiter_add_rule(opt_str, prio, value); + + } else { /* It's an option then */ + if (isYES(*val_str)) { + value = 1; + } else if (isNO(*val_str)) { + value = 0; + } else if (!strncasecmp(ON_STRING, val_str, + sizeof(ON_STRING))) { + value = 1; + } else if (!strncasecmp(OFF_STRING, val_str, + sizeof(OFF_STRING))) { + value = 0; + } else { + return RET_ERROR; + } + + if (!strncasecmp(LOG_PLATFORM_STRING, opt_str, + sizeof(LOG_PLATFORM_STRING))) { + config->lc_plog = value; + } else if (!strncasecmp(LOG_LIMITER_STRING, opt_str, + sizeof(LOG_LIMITER_STRING))) { + config->lc_limiter = value; + } else { + return RET_ERROR; + } + } + + return RET_SUCCESS; +} + +/* Function returns 0 for success or -1 when error occurred */ +int __log_config_read(const char* config_file, struct log_config* config) +{ + FILE* fconfig = NULL; + char buf[CONFIG_LINE_MAX_LEN]; + char opt[CONFIG_OPTION_MAX_LEN]; + char opt_value[CONFIG_OPTION_MAX_LEN]; + int prio = (-1); + int ret = 0; + + /* Check input */ + if (NULL == config_file || NULL == config) { + return RET_ERROR; + } + + if (NULL == (fconfig = fopen(config_file, "r"))) { + return RET_ERROR; + } + + while (1) { + memset(buf, 0, CONFIG_LINE_MAX_LEN); + errno = 0; + if (NULL == fgets(buf, CONFIG_LINE_MAX_LEN, fconfig)) { + if (!errno) { + break; + } + goto bailout; + } + + /* We ignore comments and blank lines */ + if (isCOMMENT(*buf) || isNEWLINE(*buf)) { + continue; + } + + memset(opt, 0, sizeof(opt)); + memset(opt_value, 0, sizeof(opt_value)); + prio = (-1); + /* Read configure line, sscanf() should return two tokens, + * even for tag filtering rule */ + ret = sscanf(buf, "%[A-z0-9-]\t%[A-z0-9]", + opt, opt_value); + if (ret != 2) { /* The line is malformed ? */ + char c = 0; + /* This could be rule with space inside TAG */ + ret = sscanf(buf, "\"%[]A-z0-9*\x20_+:;/-]\"\t|\t%c\t%[A-z0-9]", + opt, &c, opt_value); + if (ret != 3) { + goto bailout; + } + prio = (int)c; + } + + + if (0 > log_config_multiplex_opt(opt, opt_value, prio, config)) { + goto bailout; + } + } + + fclose(fconfig); + return RET_SUCCESS; + +bailout: + /* These actions should warranty that + we cleanly handle initialization errors */ + fclose(fconfig); + /* Clean rules list to prevent log limiter initialization, + if configuration error occured. */ + __log_limiter_rules_purge(); + + return RET_ERROR; +} diff --git a/src/libdlog/loglimiter.c b/src/libdlog/loglimiter.c new file mode 100644 index 0000000..a0e874e --- /dev/null +++ b/src/libdlog/loglimiter.c @@ -0,0 +1,419 @@ +/* + * DLOG + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * + * 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 <stddef.h> +#include <limits.h> +#include <sys/types.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <pthread.h> +#include <sys/time.h> +#include <time.h> + +/* Included for priorities level */ +#include <dlog.h> +#include "loglimiter.h" + +/* Defines maximal meaningful tag length */ +#define TAG_REASONABLE_LEN 32 + +/* Some random big odd number to make hash more diverse */ +#define HASH_MAGIC_THINGY 5237231 + +#define TIME_FRAME 60 + +struct rule{ + /* TODO: List element handle, the list could be embedded + into structure some day, like kernel lists */ + struct rule* prev; + + unsigned hash; + int prio; + int limit; + int hit; + time_t start; + char tag[TAG_REASONABLE_LEN]; +}; + +typedef int (*hash_cmp_func_t)(struct rule*, struct rule*); +typedef int (*hash_match_func_t)(struct rule*, unsigned, const char*, int); + +struct hashmap { + hash_cmp_func_t cmp; + hash_match_func_t match; + int size; + void* bucket[]; +}; + +struct hashmap* rules_hashmap = NULL; + +/* Keep rules table as single-linked list */ +struct rule* rules_table = NULL; + +#define HASHMAP_MASK(hm) ((int)(hm->size - 1)) +#define HASHMAP_LINEAR_PROBE_LEAP 1 + +static void rules_destroy(struct rule* rlist) +{ + struct rule* r; + + if (NULL == rlist) { + return; + } + + while ((r = rlist)) { + rlist = rlist->prev; + free(r); + } +} + +void __log_limiter_rules_purge(void) +{ + rules_destroy(rules_table); + rules_table = NULL; +} + +static int rule_compare(struct rule* r1, struct rule* r2) +{ + if (r1->hash == r2->hash) { + if (r1->prio == r2->prio) { + return strncmp(r1->tag, r2->tag, strlen(r2->tag)); + } else { + return (r1->prio > r2->prio ? 1 : (-1)); + } + } + + return (r1->hash > r2->hash ? 1 : (-1)); +} + +static int rule_match(struct rule* r1, unsigned key, const char* s, int prio) +{ + if (r1->hash == key) { + if (r1->prio == prio) { + return strncmp(r1->tag, s, strlen(s)); + } else { + return (r1->prio > prio ? 1 : (-1)); + } + } + + return (r1->hash > key ? 1 : (-1)); +} + +/* Translate fancy priority notation into common denominator */ +static int util_prio_to_char(int prio) +{ + static const char pri_table[DLOG_PRIO_MAX] = { + [DLOG_VERBOSE] = 'V', + [DLOG_DEBUG] = 'D', + [DLOG_INFO] = 'I', + [DLOG_WARN] = 'W', + [DLOG_ERROR] = 'E', + [DLOG_FATAL] = 'F', + [DLOG_SILENT] = 'S', + }; + + if (DLOG_PRIO_MAX > prio && prio >= 0) { + return pri_table[prio]; + } else { + switch (prio) { + case 'V': case 'v': case '1': + case 'D': case 'd': case '2': + case 'I': case 'i': case '3': + case 'W': case 'w': case '4': + case 'E': case 'e': case '5': + case 'F': case 'f': case '6': + case 'S': case 's': case '7': + case '*': + return prio; + + default: + ;; + } + } + + return '?'; +} + +/* The key is built from TAG and priority by DJB algorithm (Dan Bernstein). + * Key is only 31 bits long. Negative values are keep to hold NULL bucket */ +static unsigned util_hash_key(const char* s, int c) +{ + unsigned hash = (unsigned)c; + + hash = ((hash << 5) + hash) + c; + + if (!s || !s[0]) { + goto finish; + } + + while ('\0' != (c = *s++)) { + hash = ((hash << 5) + hash) + c; + } + +finish: + /* Makes the hash more diverse */ + hash *= HASH_MAGIC_THINGY; + + return hash; +} + +/* Create hashmap, it's internal interface. */ +static struct hashmap* hashmap_create(int size, hash_cmp_func_t cmp_func, + hash_match_func_t match_func) +{ + struct hashmap* hm = NULL; + /* please keep hashmap fill ratio around 50% */ + int internal_size = size << 1; + + if (!cmp_func || !match_func || !size) { + return NULL; + } + + + /* Round up the lines counter to next power of two. */ + internal_size--; + internal_size |= internal_size >> 1; + internal_size |= internal_size >> 2; + internal_size |= internal_size >> 4; + internal_size |= internal_size >> 8; + internal_size |= internal_size >> 16; + internal_size++; + + hm = malloc(sizeof(struct hashmap) + internal_size * sizeof(void*)); + if (!hm) { + return NULL; + } + /* Initialize hash field to correct value */ + memset((void*)hm, 0, sizeof(struct hashmap) + internal_size * sizeof(void*)); + + hm->size = internal_size; + hm->cmp = cmp_func; + hm->match = match_func; + + return hm; +} + +static void hashmap_destroy(struct hashmap* hm) +{ + if (hm) { + hm->size = 0; + free(hm); + } +} + +static void hashmap_add(struct hashmap* hm, struct rule* r) +{ + unsigned b = (r->hash & HASHMAP_MASK(hm)); + + while (hm->bucket[b]) { + if (!hm->cmp(r, (struct rule*)hm->bucket[b])) { + break; + } + b = (b + 1) & HASHMAP_MASK(hm); + } + + hm->bucket[b] = r; +} + +static struct rule* hashmap_search(struct hashmap* hm, unsigned key, + const char* tag, int prio) +{ + unsigned b = (key & HASHMAP_MASK(hm)); + unsigned b0 = b; + + while (hm->bucket[b]) { + if (!hm->match(hm->bucket[b], key, tag, prio)) { + break; + } + + b = (b + 1) & HASHMAP_MASK(hm); + + if (b0 == b) { + return NULL; + } + } + + if (!hm->bucket[b]) { + return NULL; + } + + return hm->bucket[b]; +} + +/* Must be always executed after __log_config_read() */ +int __log_limiter_initialize(void) +{ + int hm_size; + struct rule* rlist = NULL; + + /* logconfig.c module had to initialize this correctly */ + if (NULL == rules_table) { + return (-1); + } + + /* Count rules in the table */ + hm_size = 0; + rlist = rules_table; + while (rlist) { + hm_size++; + rlist = rlist->prev; + } + + /* Allocate hashmap */ + rules_hashmap = (struct hashmap*) hashmap_create(hm_size, + &rule_compare, + &rule_match); + if (NULL == rules_hashmap || !rules_hashmap->size) { + goto bailout; + } + + /* Add rule to hashmap */ + rlist = rules_table; + while (rlist) { + hashmap_add(rules_hashmap, rlist); + rlist = rlist->prev; + } + + return 0; + +bailout: + hashmap_destroy(rules_hashmap); + rules_destroy(rules_table); + rules_table = NULL; + rules_hashmap = NULL; + + return (-1); +} + +void __log_limiter_destroy(void) +{ + hashmap_destroy(rules_hashmap); + rules_destroy(rules_table); + rules_table = NULL; + rules_hashmap = NULL; +} + +int __log_limiter_add_rule(const char* tag, int prio, int limit) +{ + struct rule* r; + + if (!tag) { + return (-1); + } + + r = (struct rule*) malloc(sizeof(struct rule)); + if (NULL == r) { + return (-1); + } + memset(r, 0, sizeof(struct rule)); + + snprintf(r->tag, TAG_REASONABLE_LEN, "%s", tag); + r->prio = util_prio_to_char(prio); + r->hash = util_hash_key(tag, r->prio); + r->limit = limit; + r->start = time(NULL); + r->hit = 0; + + r->prev = rules_table; + rules_table = r; + + return 0; +} + + +/* Function implement logic needed to decide, + whenever message is written to log or not. + + Possible return values are: + 0 - to indicate that message is deny to write into log. + (-1) - to indicate that limit of the messages is reached. + 1 - to indicate that message is allowed to write into log. +*/ +int __log_limiter_pass_log(const char* tag, int prio) +{ + unsigned key = 0; + struct rule* r = NULL; + time_t now = 0; + + key = util_hash_key(tag, util_prio_to_char(prio)); + r = hashmap_search(rules_hashmap, key, tag, util_prio_to_char(prio)); + + if (!r) { + /* Rule not found, let's check general rule TAG:* */ + key = util_hash_key(tag, '*'); + r = hashmap_search(rules_hashmap, key, tag, '*'); + if (!r) { + /* Rule TAG:* not found, + let check general rule *:priority */ + key = util_hash_key("*", util_prio_to_char(prio)); + r = hashmap_search(rules_hashmap, key, "*", + util_prio_to_char(prio)); + if (!r) { + /* All known paths were exhausted, + use global rule *:* */ + key = util_hash_key("*", '*'); + r = hashmap_search(rules_hashmap, key, "*", '*'); + + /* *:* is not defined, so pass message through */ + if (!r) { + return 1; + } + } + } + } + + if (!r->limit) { + return 0; + } else if (__LOG_LIMITER_LIMIT_MAX < r->limit) { + return 1; + } + + /* Decide, if it should go through or stop */ + now = time(NULL); + + if (0 > now) { + return 1; + } + + if (now - r->start <= TIME_FRAME) { + if (r->hit >= 0) { + if (r->hit < r->limit) { + r->hit++; + return 1; + } + r->hit = INT_MIN+1; + return (-1); + } else { + r->hit++; + return 0; + } + + } else { + r->start = now; + r->hit = 0; + return 1; + } + + /* If everything failed, then pass message through */ + return 1; +} diff --git a/src/logger/logger.c b/src/logger/logger.c new file mode 100644 index 0000000..6b3a5cf --- /dev/null +++ b/src/logger/logger.c @@ -0,0 +1,1040 @@ +/* + * Copyright (c) 2005-2008, The Android Open Source Project + * Copyright (c) 2009-2013, Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <signal.h> +#include <fcntl.h> +#include <time.h> +#include <sys/time.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <sys/stat.h> +#include <arpa/inet.h> + +#include <logger.h> +#include <logprint.h> + +#ifdef DEBUG_ON +#define _D(...) printf(__VA_ARGS__) +#else +#define _D(...) do { } while (0) +#endif +#define _E(...) fprintf(stderr, __VA_ARGS__) + +#define COMMAND_MAX 5 +#define DELIMITER " " +#define FILE_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) +#define MAX_ARGS 16 +#define MAX_ROTATED 4 +#define MAX_QUEUED 4096 +#define BUFFER_MAX 100 +#define INTERVAL_MAX 60*60 + +#define CONFIG_FILE "/opt/etc/dlog_logger.conf" + +#define ARRAY_SIZE(name) (sizeof(name)/sizeof(name[0])) + +struct queued_entry { + union { + unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4))); + struct logger_entry entry __attribute__((aligned(4))); + }; + struct queued_entry *next; +}; + +struct log_command { + char *filename; + int option_file; + int option_buffer; + int rotate_size; + int max_rotated; + int devices[LOG_ID_MAX]; + log_format *format; +}; + +struct log_work { + char *filename; + bool printed; + int fd; + int size; + int rotate_size; + int max_rotated; + log_format *format; + struct log_work *next; +}; + +struct log_task_link { + struct log_work *work; + struct log_task_link *next; +}; + +struct log_device { + int id; + int fd; + struct queued_entry *queue; + struct log_task_link *task; + struct log_device *next; +}; + +static const char *device_path_table[] = { + [LOG_ID_MAIN] = "/dev/log_main", + [LOG_ID_RADIO] = "/dev/log_radio", + [LOG_ID_SYSTEM] = "/dev/log_system", + [LOG_ID_APPS] = "/dev/log_apps", + [LOG_ID_MAX] = NULL +}; + +static struct log_work *works; +static struct log_device *devices; +static int device_list[] = { + [LOG_ID_MAIN] = false, + [LOG_ID_RADIO] = false, + [LOG_ID_SYSTEM] = false, + [LOG_ID_APPS] = false, + [LOG_ID_MAX] = false, +}; + +static int buffer_size = 0; +static int min_interval = 0; + +/* + * get log device id from device path table by device name + */ +static int get_device_id_by_name(const char *name) +{ + int i; + + if (name == NULL) + return -1; + for (i = 0; i < ARRAY_SIZE(device_path_table); i++) { + if (strstr(device_path_table[i], name) != NULL) + return i; + } + + return -1; +} + +/* + * check device registration on watch device list + */ +static int check_device(int id) +{ + if (id < 0 || LOG_ID_MAX <= id) + return 0; + + return (device_list[id] == true) ? 0 : -1; +} + +/* + * register device to watch device list + */ +static int register_device(int id) +{ + if (id < 0 || LOG_ID_MAX <= id) + return -1; + device_list[id] = true; + + return 0; +} + +/* + * comparison function to distinct entries by time + */ +static int cmp(struct queued_entry *a, struct queued_entry *b) +{ + int n = a->entry.sec - b->entry.sec; + if (n != 0) + return n; + + return a->entry.nsec - b->entry.nsec; +} + +/* + * enqueueing the log_entry into the log_device + */ +static void enqueue(struct log_device *device, struct queued_entry *entry) +{ + if (device->queue == NULL) { + device->queue = entry; + } else { + struct queued_entry **e = &device->queue; + while (*e && cmp(entry, *e) >= 0) + e = &((*e)->next); + entry->next = *e; + *e = entry; + } +} + +/* + * open file + */ +static int open_work(const char *path) +{ + return open(path, O_WRONLY | O_APPEND | O_CREAT, FILE_PERMS); +} + +/* + * rotate log files + */ +static void rotate_logs(struct log_work *logwork) +{ + int i, ret; + char *filename; + char file0[NAME_MAX]; + char file1[NAME_MAX]; + + close(logwork->fd); + filename = logwork->filename; + for (i = logwork->max_rotated ; i > 0 ; i--) { + snprintf(file1, NAME_MAX, "%s.%d", filename, i); + if (i - 1 == 0) + snprintf(file0, NAME_MAX, "%s", filename); + else + snprintf(file0, NAME_MAX, "%s.%d", filename, i - 1); + ret = rename(file0, file1); + if (ret < 0 && errno != ENOENT) + _E("while rotating log works"); + } + /* open log file again */ + logwork->fd = open_work(filename); + if (logwork->fd < 0) { + _E("couldn't open log file"); + exit(EXIT_FAILURE); + } + logwork->size = 0; + + return; +} + +/* + * process to print log + * and check the log file size to rotate + */ +static void process_buffer(struct log_device *dev, struct logger_entry *buf) +{ + int bytes_written, err; + log_entry entry; + struct log_work *logwork; + struct log_task_link *task; + + err = log_process_log_buffer(buf, &entry); + if (err < 0) + goto exit; + + for (task = dev->task; task; task = task->next) { + logwork = task->work; + if (log_should_print_line(logwork->format, + entry.tag, entry.priority)) { + bytes_written = + log_print_log_line(logwork->format, + logwork->fd, &entry); + if (bytes_written < 0) { + _E("work error"); + exit(EXIT_FAILURE); + } + logwork->size += bytes_written; + } + if (logwork->rotate_size > 0 && + (logwork->size / 1024) >= logwork->rotate_size) { + rotate_logs(logwork); + } + } + +exit: + return; +} + +/* + * choose first device by log_entry + */ +static void choose_first(struct log_device *dev, struct log_device **firstdev) +{ + for (*firstdev = NULL; dev != NULL; dev = dev->next) { + if (dev->queue != NULL && + (*firstdev == NULL || + cmp(dev->queue, + (*firstdev)->queue) < 0)) { + *firstdev = dev; + } + } +} + +/* + * print beginnig string into the log files + */ +static void maybe_print_start(struct log_device *dev) +{ + struct log_work *logwork; + struct log_task_link *task; + char buf[1024]; + + for (task = dev->task; task; task = task->next) { + logwork = task->work; + if (!logwork->printed) { + logwork->printed = true; + snprintf(buf, sizeof(buf), + "--------- beginning of %s\n", + device_path_table[dev->id]); + if (write(logwork->fd, buf, strlen(buf)) < 0) { + _E("maybe work error"); + exit(EXIT_FAILURE); + } + } + } +} + +/* + * skip log_entry + */ +static void skip_next_entry(struct log_device *dev) +{ + maybe_print_start(dev); + struct queued_entry *entry = dev->queue; + dev->queue = entry->next; + free(entry); +} + +/* + * print log_entry + */ +static void print_next_entry(struct log_device *dev) +{ + maybe_print_start(dev); + process_buffer(dev, &dev->queue->entry); + skip_next_entry(dev); +} + +/* + * do logging + */ +static void do_logger(struct log_device *dev) +{ + time_t commit_time = 0, current_time = 0; + struct log_device *pdev; + int ret, result; + fd_set readset; + bool sleep = false; + int queued_lines = 0; + int max = 0; + + if (min_interval) + commit_time = current_time = time(NULL); + + for (pdev = dev; pdev; pdev = pdev->next) { + if (pdev->fd > max) + max = pdev->fd; + } + + while (1) { + do { + struct timeval timeout = { 0, 5000 /* 5ms */ }; + FD_ZERO(&readset); + for (pdev = dev; pdev; pdev = pdev->next) + FD_SET(pdev->fd, &readset); + result = select(max + 1, &readset, NULL, NULL, + sleep ? NULL : &timeout); + } while (result == -1 && errno == EINTR); + + if (result < 0) + continue; + + for (pdev = dev; pdev; pdev = pdev->next) { + if (FD_ISSET(pdev->fd, &readset)) { + struct queued_entry *entry = + (struct queued_entry *) + malloc(sizeof(struct queued_entry)); + if (entry == NULL) { + _E("failed to malloc queued_entry\n"); + goto exit;//exit(EXIT_FAILURE); + } + entry->next = NULL; + ret = read(pdev->fd, entry->buf, + LOGGER_ENTRY_MAX_LEN); + if (ret < 0) { + if (errno == EINTR) { + free(entry); + goto next; + } + if (errno == EAGAIN) { + free(entry); + break; + } + _E("dlogutil read"); + goto exit;//exit(EXIT_FAILURE); + } else if (!ret) { + free(entry); + _E("read: Unexpected EOF!\n"); + exit(EXIT_FAILURE); + } else if (entry->entry.len != + ret - sizeof(struct logger_entry)) { + free(entry); + _E("unexpected length. Expected %d, got %d\n", + entry->entry.len, + ret - sizeof(struct logger_entry)); + goto exit;//exit(EXIT_FAILURE); + } + + entry->entry.msg[entry->entry.len] = '\0'; + + enqueue(pdev, entry); + ++queued_lines; + + if (MAX_QUEUED < queued_lines) { + _D("queued_lines = %d\n", queued_lines); + while (true) { + choose_first(dev, &pdev); + if (pdev == NULL) + break; + print_next_entry(pdev); + --queued_lines; + } + if (min_interval) + commit_time = time(NULL); + break; + } + } + } + + if (min_interval) { + current_time = time(NULL); + if (current_time - commit_time < min_interval && + queued_lines < buffer_size) { + sleep = true; + continue; + } + } + + if (result == 0) { + sleep = true; + while (true) { + choose_first(dev, &pdev); + if (pdev == NULL) + break; + print_next_entry(pdev); + --queued_lines; + if (min_interval) + commit_time = current_time; + } + } else { + /* print all that aren't the last in their list */ + sleep = false; + while (true) { + choose_first(dev, &pdev); + if (pdev == NULL || pdev->queue->next == NULL) + break; + print_next_entry(pdev); + --queued_lines; + if (min_interval) + commit_time = current_time; + } + } +next: + ; + } +exit: + exit(EXIT_FAILURE); +} + + +/* + * create a work + */ +static struct log_work *work_new(void) +{ + struct log_work *work; + + work = malloc(sizeof(struct log_work)); + if (work == NULL) { + _E("failed to malloc log_work\n"); + return NULL; + } + work->filename = NULL; + work->fd = -1; + work->printed = false; + work->size = 0; + work->next = NULL; + _D("work alloc %p\n", work); + + return work; +} + +/* + * add a new log_work to the tail of chain + */ +static int work_add_to_tail(struct log_work *work, struct log_work *nwork) +{ + struct log_work *tail = work; + + if (!nwork) + return -1; + + if (work == NULL) { + work = nwork; + return 0; + } + + while (tail->next) + tail = tail->next; + tail->next = nwork; + + return 0; +} + +/* + * add a new work task to the tail of chain + */ +static void work_add_to_device(struct log_device *dev, struct log_work *work) +{ + struct log_task_link *tail; + + if (!dev || !work) + return; + _D("dev %p work %p\n", dev, work); + if (dev->task == NULL) { + dev->task = + (struct log_task_link *) + malloc(sizeof(struct log_task_link)); + if (dev->task == NULL) { + _E("failed to malloc log_task_link\n"); + return; + } + tail = dev->task; + } else { + tail = dev->task; + while (tail->next) + tail = tail->next; + tail->next = + (struct log_task_link *) + malloc(sizeof(struct log_task_link)); + if (tail->next == NULL) { + _E("failed to malloc log_task_link\n"); + return; + } + tail = tail->next; + } + tail->work = work; + tail->next = NULL; +} + +/* + * free work file descriptor + */ +static void work_free(struct log_work *work) +{ + if (!work) + return; + if (work->filename) { + free(work->filename); + work->filename = NULL; + if (work->fd != -1) { + close(work->fd); + work->fd = -1; + } + } + log_format_free(work->format); + work->format = NULL; + free(work); + work = NULL; +} + +/* + * free all the nodes after the "work" and includes itself + */ +static void work_chain_free(struct log_work *work) +{ + if (!work) + return; + while (work->next) { + struct log_work *tmp = work->next; + work->next = tmp->next; + work_free(tmp); + } + work_free(work); + work = NULL; +} + +/* + * create a new log_device instance + * and open device + */ +static struct log_device *device_new(int id) +{ + struct log_device *dev; + + if (LOG_ID_MAX <= id) + return NULL; + dev = malloc(sizeof(struct log_device)); + if (dev == NULL) { + _E("failed to malloc log_device\n"); + return NULL; + } + dev->id = id; + dev->fd = open(device_path_table[id], O_RDONLY); + if (dev->fd < 0) { + _E("Unable to open log device '%s': %s\n", + device_path_table[id], + strerror(errno)); + free(dev); + return NULL; + } + _D("device new id %d fd %d\n", dev->id, dev->fd); + dev->task = NULL; + dev->queue = NULL; + dev->next = NULL; + + return dev; +} + +/* + * add a new log_device to the tail of chain + */ +static int device_add_to_tail(struct log_device *dev, struct log_device *ndev) +{ + struct log_device *tail = dev; + + if (!dev || !ndev) + return -1; + + while (tail->next) + tail = tail->next; + tail->next = ndev; + + return 0; +} + +/* + * add a new log_device or add to the tail of chain + */ +static void device_add(int id) +{ + if (check_device(id) < 0) + return; + + if (!devices) { + devices = device_new(id); + if (devices == NULL) { + _E("failed to device_new: %s\n", + device_path_table[id]); + exit(EXIT_FAILURE); + } + } else { + if (device_add_to_tail(devices, device_new(id)) < 0) { + _E("failed to device_add_to_tail: %s\n", + device_path_table[id]); + exit(EXIT_FAILURE); + } + } + return; +} + +/* + * free one log_device and it doesn't take care of chain so it + * may break the chain list + */ +static void device_free(struct log_device *dev) +{ + if (!dev) + return; + if (dev->queue) { + while (dev->queue->next) { + struct queued_entry *tmp = + dev->queue->next; + dev->queue->next = tmp->next; + free(tmp); + } + free(dev->queue); + dev->queue = NULL; + } + if (dev->task) { + while (dev->task->next) { + struct log_task_link *tmp = + dev->task->next; + dev->task->next = tmp->next; + free(tmp); + } + free(dev->task); + dev->task = NULL; + } + free(dev); + dev = NULL; +} + +/* + * free all the nodes after the "dev" and includes itself + */ +static void device_chain_free(struct log_device *dev) +{ + if (!dev) + return; + while (dev->next) { + struct log_device *tmp = dev->next; + dev->next = tmp->next; + device_free(tmp); + } + device_free(dev); + dev = NULL; +} + +/* + * parse command line + * using getopt function + */ +static int parse_command_line(char *linebuffer, struct log_command *cmd) +{ + int i, ret, id, argc; + char *argv[MAX_ARGS]; + char *tok, *cmdline; + + if (linebuffer == NULL || cmd == NULL) + return -1; + /* copy command line */ + cmdline = strdup(linebuffer); + tok = strtok(cmdline, DELIMITER); + /* check the availability of command line + by comparing first word with dlogutil*/ + if (!tok || strcmp(tok, "dlogutil")) { + _D("Ignore this line (%s)\n", linebuffer); + free(cmdline); + return -1; + } + _D("Parsing this line (%s)\n", linebuffer); + /* fill the argc and argv + for extract option from command line */ + argc = 0; + while (tok && (argc < MAX_ARGS)) { + argv[argc] = strdup(tok); + tok = strtok(NULL, DELIMITER); + argc++; + } + free(cmdline); + + /* initialize the command struct with the default value */ + memset(cmd->devices, 0, sizeof(cmd->devices)); + cmd->option_file = false; + cmd->option_buffer = false; + cmd->filename = NULL; + cmd->rotate_size = 0; + cmd->max_rotated = MAX_ROTATED; + cmd->format = (log_format *)log_format_new(); + + /* get option and fill the command struct */ + while ((ret = getopt(argc, argv, "f:r:n:v:b:")) != -1) { + switch (ret) { + case 'f': + cmd->filename = strdup(optarg); + _D("command filename %s\n", cmd->filename); + cmd->option_file = true; + break; + case 'b': + id = get_device_id_by_name(optarg); + _D("command device name %s id %d\n", optarg, id); + if (id < 0 || LOG_ID_MAX <= id) + break; + cmd->option_buffer = true; + /* enable to log in device on/off struct */ + cmd->devices[id] = true; + /* enable to open in global device on/off struct */ + register_device(id); + break; + case 'r': + if (!isdigit(optarg[0])) + goto exit_free; + cmd->rotate_size = atoi(optarg); + _D("command rotate size %d\n", cmd->rotate_size); + break; + case 'n': + if (!isdigit(optarg[0])) + goto exit_free; + cmd->max_rotated = atoi(optarg); + _D("command max rotated %d\n", cmd->max_rotated); + break; + case 'v': + { + log_print_format print_format; + print_format = log_format_from_string(optarg); + if (print_format == FORMAT_OFF) { + _E("failed to add format\n"); + goto exit_free; + } + _D("command format %s\n", optarg); + log_set_print_format(cmd->format, print_format); + } + break; + default: + break; + } + } + /* add filter string, when command line have tags */ + if (argc != optind) { + for (i = optind ; i < argc ; i++) { + ret = log_add_filter_string(cmd->format, argv[i]); + _D("command add fileter string %s\n", argv[i]); + if (ret < 0) { + _E("Invalid filter expression '%s'\n", argv[i]); + goto exit_free; + } + } + } else { + ret = log_add_filter_string(cmd->format, "*:d"); + if (ret < 0) { + _E("Invalid silent filter expression\n"); + goto exit_free; + } + } + /* free argv */ + for (i = 0; i < argc; i++) + free(argv[i]); + + if (cmd->option_file == false) + goto exit_free; + /* If it have not the -b option, + set the default devices to open and log */ + if (cmd->option_buffer == false) { + _D("set default device\n"); + cmd->devices[LOG_ID_MAIN] = true; + cmd->devices[LOG_ID_SYSTEM] = true; + register_device(LOG_ID_MAIN); + register_device(LOG_ID_SYSTEM); + } + /* for use getopt again */ + optarg = NULL; + optind = 1; + optopt = 0; + + return 0; + +exit_free: + if (cmd->filename) + free(cmd->filename); + return -1; +} + +/* + * parse command from configuration file + * and return the command list with number of command + * if an command was successfully parsed, then it returns number of parsed command. + * on error or not founded, 0 is returned + */ +static int parse_command(struct log_command *command_list) +{ + FILE *fp; + int ncmd; + char *line_p; + char linebuffer[1024]; + + fp = fopen(CONFIG_FILE, "re"); + if (!fp) { + _E("no config file\n"); + goto exit; + } + ncmd = 0; + while (fgets(linebuffer, sizeof(linebuffer), fp) != NULL) { + line_p = strchr(linebuffer, '\n'); + if (line_p != NULL) + *line_p = '\0'; + if (parse_command_line(linebuffer, &command_list[ncmd]) == 0) + ncmd++; + if (COMMAND_MAX <= ncmd) + break; + } + fclose(fp); + + return ncmd; + +exit: + return 0; +} + +/* + * free dynamically allocated memory + */ +static void cleanup(void) +{ + work_chain_free(works); + device_chain_free(devices); +} + +/* + * SIGINT, SIGTERM, SIGQUIT signal handler + */ +static void sig_handler(int signo) +{ + _D("sig_handler\n"); + cleanup(); + exit(EXIT_SUCCESS); +} + +static int help(void) { + + printf("%s [OPTIONS...] \n\n" + "Logger, records log messages to files.\n\n" + " -h Show this help\n" + " -b N Set number of logs to keep logs in memory buffer bafore write files (default:0, MAX:100)\n" + " -t N Set minimum interval time to write files (default:0, MAX:3600, seconds)\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int ret = 1, option; + + while ((option = getopt(argc, argv, "hb:t:")) != -1) { + switch (option) { + case 't': + if (!isdigit(optarg[0])) { + ret = -EINVAL; + printf("Wrong argument!\n"); + help(); + goto exit; + } + min_interval = atoi(optarg); + if (min_interval < 0 || INTERVAL_MAX < min_interval) + min_interval = 0; + ret = 1; + break; + case 'b': + if (!isdigit(optarg[0])) { + ret = -EINVAL; + printf("Wrong argument!\n"); + help(); + goto exit; + } + buffer_size = atoi(optarg); + if (buffer_size < 0 || BUFFER_MAX < buffer_size) + buffer_size = 0; + ret = 1; + break; + case 'h': + help(); + ret = 0; + goto exit; + default: + ret = -EINVAL; + } + } +exit: + optarg = NULL; + optind = 1; + optopt = 0; + return ret; +} + + +int main(int argc, char **argv) +{ + int i, r, ncmd; + struct stat statbuf; + struct log_device *dev; + struct log_work *work; + struct log_command command_list[COMMAND_MAX]; + struct sigaction act; + + /* set the signal handler for free dynamically allocated memory. */ + memset(&act, 0, sizeof(struct sigaction)); + sigemptyset(&act.sa_mask); + act.sa_handler = (void *)sig_handler; + act.sa_flags = 0; + + if (sigaction(SIGQUIT, &act, NULL) < 0) + _E("failed to sigaction for SIGQUIT"); + if (sigaction(SIGINT, &act, NULL) < 0) + _E("failed to sigaction for SIGINT"); + if (sigaction(SIGTERM, &act, NULL) < 0) + _E("failed to sigaction for SIGTERM"); + + if (argc != 1) { + r = parse_argv(argc, argv); + if (r <= 0) + goto exit; + } + /* parse command from command configuration file. */ + ncmd = parse_command(command_list); + /* If it have nothing command, exit. */ + if (!ncmd) + goto exit; + + /* create log device */ + device_add(LOG_ID_MAIN); + device_add(LOG_ID_SYSTEM); + device_add(LOG_ID_RADIO); + + /* create work from the parsed command */ + for (i = 0; i < ncmd; i++) { + work = work_new(); + _D("work new\n"); + if (work == NULL) { + _E("failed to work_new\n"); + goto clean_exit; + } + /* attatch the work to global works variable */ + if (work_add_to_tail(works, work) < 0) { + _E("failed to work_add_to_tail\n"); + goto clean_exit; + } + /* 1. set filename, fd and file current size */ + work->filename = command_list[i].filename; + if (work->filename == NULL) { + _E("file name is NULL"); + goto clean_exit; + } + work->fd = open_work(work->filename); + if (work->fd < 0) { + _E("failed to open log file"); + exit(EXIT_FAILURE); + } + if (fstat(work->fd, &statbuf) == -1) + work->size = 0; + else + work->size = statbuf.st_size; + + /* 2. set size limits for log files */ + work->rotate_size = command_list[i].rotate_size; + + /* 3. set limit on the number of rotated log files */ + work->max_rotated = command_list[i].max_rotated; + + /* 4. set log_format to filter logs*/ + work->format = command_list[i].format; + + /* 5. attatch the work to device task for logging */ + dev = devices; + while (dev) { + if (command_list[i].devices[dev->id] == true) { + work_add_to_device(dev, work); + } + dev = dev->next; + } + } + + /* do log */ + do_logger(devices); + +clean_exit: + work_chain_free(works); + device_chain_free(devices); +exit: + return 0; +} diff --git a/src/logutil/logutil.c b/src/logutil/logutil.c new file mode 100755 index 0000000..ea96cfb --- /dev/null +++ b/src/logutil/logutil.c @@ -0,0 +1,777 @@ +/* + * Copyright (c) 2005-2008, The Android Open Source Project + * Copyright (c) 2009-2013, Samsung Electronics Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <sys/time.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <sys/stat.h> +#include <arpa/inet.h> + + +#include <logger.h> +#include <logprint.h> + +#define DEFAULT_LOG_ROTATE_SIZE_KBYTES 16 +#define DEFAULT_MAX_ROTATED_LOGS 4 +#define MAX_QUEUED 4096 +#define LOG_FILE_DIR "/dev/log_" + +static log_format* g_logformat; +static bool g_nonblock = false; +static int g_tail_lines = 0; + +static const char * g_output_filename = NULL; +static int g_log_rotate_size_kbytes = 0; // 0 means "no log rotation" +static int g_max_rotated_logs = DEFAULT_MAX_ROTATED_LOGS; // 0 means "unbounded" +static int g_outfd = -1; +static off_t g_out_byte_count = 0; +static int g_dev_count = 0; + +struct queued_entry_t { + union { + unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4))); + struct logger_entry entry __attribute__((aligned(4))); + }; + struct queued_entry_t* next; +}; + +static int cmp(struct queued_entry_t* a, struct queued_entry_t* b) +{ + int n = a->entry.sec - b->entry.sec; + if (n != 0) { + return n; + } + return a->entry.nsec - b->entry.nsec; +} + + +struct log_device_t { + char* device; + int fd; + bool printed; + struct queued_entry_t* queue; + struct log_device_t* next; +}; + +static void enqueue(struct log_device_t* device, struct queued_entry_t* entry) +{ + if (device->queue == NULL) { + device->queue = entry; + } else { + struct queued_entry_t** e = &device->queue; + while (*e && cmp(entry, *e) >= 0 ) { + e = &((*e)->next); + } + entry->next = *e; + *e = entry; + } +} + +static int open_logfile (const char *pathname) +{ + return open(pathname, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +} + +static void rotate_logs() +{ + int err; + int i; + char file0[256]={0}; + char file1[256]={0}; + + // Can't rotate logs if we're not outputting to a file + if (g_output_filename == NULL) { + return; + } + + close(g_outfd); + + for (i = g_max_rotated_logs ; i > 0 ; i--) { + snprintf(file1, 255, "%s.%d", g_output_filename, i); + + if (i - 1 == 0) { + snprintf(file0, 255, "%s", g_output_filename); + } else { + snprintf(file0, 255, "%s.%d", g_output_filename, i - 1); + } + + err = rename (file0, file1); + + if (err < 0 && errno != ENOENT) { + perror("while rotating log files"); + } + } + + g_outfd = open_logfile (g_output_filename); + + if (g_outfd < 0) { + perror ("couldn't open output file"); + exit(-1); + } + + g_out_byte_count = 0; + +} + + +static void processBuffer(struct log_device_t* dev, struct logger_entry *buf) +{ + int bytes_written = 0; + int err; + log_entry entry; + char mgs_buf[1024]; + + err = log_process_log_buffer(buf, &entry); + + if (err < 0) { + goto error; + } + + if (log_should_print_line(g_logformat, entry.tag, entry.priority)) { + if (false && g_dev_count > 1) { + mgs_buf[0] = dev->device[0]; + mgs_buf[1] = ' '; + bytes_written = write(g_outfd, mgs_buf, 2); + if (bytes_written < 0) { + perror("output error"); + exit(-1); + } + } + + bytes_written = log_print_log_line(g_logformat, g_outfd, &entry); + + if (bytes_written < 0) { + perror("output error"); + exit(-1); + } + } + + g_out_byte_count += bytes_written; + + if (g_log_rotate_size_kbytes > 0 && (g_out_byte_count / 1024) >= g_log_rotate_size_kbytes) { + if (g_nonblock) { + exit(0); + } else { + rotate_logs(); + } + } + +error: + return; +} + +static void chooseFirst(struct log_device_t* dev, struct log_device_t** firstdev) +{ + for (*firstdev = NULL; dev != NULL; dev = dev->next) { + if (dev->queue != NULL && (*firstdev == NULL || + cmp(dev->queue, (*firstdev)->queue) < 0)) { + *firstdev = dev; + } + } +} + +static void maybePrintStart(struct log_device_t* dev) { + if (!dev->printed) { + dev->printed = true; + if (g_dev_count > 1 ) { + char buf[1024]; + snprintf(buf, sizeof(buf), "--------- beginning of %s\n", dev->device); + if (write(g_outfd, buf, strlen(buf)) < 0) { + perror("output error"); + exit(-1); + } + } + } +} + +static void skipNextEntry(struct log_device_t* dev) { + maybePrintStart(dev); + struct queued_entry_t* entry = dev->queue; + dev->queue = entry->next; + free(entry); +} + +static void printNextEntry(struct log_device_t* dev) +{ + maybePrintStart(dev); + processBuffer(dev, &dev->queue->entry); + skipNextEntry(dev); +} + + +static void read_log_lines(struct log_device_t* devices) +{ + struct log_device_t* dev; + int max = 0; + int ret; + int queued_lines = 0; + bool sleep = false; // for exit immediately when log buffer is empty and g_nonblock value is true. + + int result; + fd_set readset; + + for (dev=devices; dev; dev = dev->next) { + if (dev->fd > max) { + max = dev->fd; + } + } + + while (1) { + do { + struct timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR. + FD_ZERO(&readset); + for (dev=devices; dev; dev = dev->next) { + FD_SET(dev->fd, &readset); + } + result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout); + } while (result == -1 && errno == EINTR); + + if (result >= 0) { + for (dev=devices; dev; dev = dev->next) { + if (FD_ISSET(dev->fd, &readset)) { + struct queued_entry_t* entry = (struct queued_entry_t *)malloc(sizeof( struct queued_entry_t)); + if (entry == NULL) { + fprintf(stderr,"Can't malloc queued_entry\n"); + exit(-1); + } + entry->next = NULL; + + /* NOTE: driver guarantees we read exactly one full entry */ + ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN); + if (ret < 0) { + if (errno == EINTR) { + free(entry); + goto next; + } + if (errno == EAGAIN) { + free(entry); + break; + } + perror("dlogutil read"); + exit(EXIT_FAILURE); + } + else if (!ret) { + free(entry); + fprintf(stderr, "read: Unexpected EOF!\n"); + exit(EXIT_FAILURE); + } + else if (entry->entry.len != ret - sizeof(struct logger_entry)) { + fprintf(stderr, "read: unexpected length. Expected %d, got %d\n", + entry->entry.len, ret - sizeof(struct logger_entry)); + free(entry); + exit(EXIT_FAILURE); + } + + + entry->entry.msg[entry->entry.len] = '\0'; + + enqueue(dev, entry); + ++queued_lines; + if (g_nonblock && MAX_QUEUED < queued_lines) { + while (true) { + chooseFirst(devices, &dev); + if (dev == NULL) + break; + printNextEntry(dev); + --queued_lines; + } + break; + } + } + } + + if (result == 0) { + /* we did our short timeout trick and there's nothing new + print everything we have and wait for more data */ + sleep = true; + while (true) { + chooseFirst(devices, &dev); + if (dev == NULL) { + break; + } + if (g_tail_lines == 0 || queued_lines <= g_tail_lines) { + printNextEntry(dev); + } else { + skipNextEntry(dev); + } + --queued_lines; + } + + /* the caller requested to just dump the log and exit */ + if (g_nonblock) { + exit(0); + } + } else { + /* print all that aren't the last in their list */ + sleep = false; + while (g_tail_lines == 0 || queued_lines > g_tail_lines) { + chooseFirst(devices, &dev); + if (dev == NULL || dev->queue->next == NULL) { + break; + } + if (g_tail_lines == 0) { + printNextEntry(dev); + } else { + skipNextEntry(dev); + } + --queued_lines; + } + } + } +next: + ; + } +} + + +static int clear_log(int logfd) +{ + return ioctl(logfd, LOGGER_FLUSH_LOG); +} + +/* returns the total size of the log's ring buffer */ +static int get_log_size(int logfd) +{ + return ioctl(logfd, LOGGER_GET_LOG_BUF_SIZE); +} + +/* returns the readable size of the log's ring buffer (that is, amount of the log consumed) */ +static int get_log_readable_size(int logfd) +{ + return ioctl(logfd, LOGGER_GET_LOG_LEN); +} + +static void setup_output() +{ + + if (g_output_filename == NULL) { + g_outfd = STDOUT_FILENO; + + } else { + struct stat statbuf; + + g_outfd = open_logfile (g_output_filename); + + if (g_outfd < 0) { + perror ("couldn't open output file"); + exit(-1); + } + if (fstat(g_outfd, &statbuf) == -1) + g_out_byte_count = 0; + else + g_out_byte_count = statbuf.st_size; + } +} + +static int set_log_format(const char * formatString) +{ + static log_print_format format; + + format = log_format_from_string(formatString); + + if (format == FORMAT_OFF) { + /* FORMAT_OFF means invalid string */ + return -1; + } + + log_set_print_format(g_logformat, format); + + return 0; +} + +static void show_help(const char *cmd) +{ + fprintf(stderr,"Usage: %s [options] [filterspecs]\n", cmd); + + fprintf(stderr, "options include:\n" + " -s Set default filter to silent.\n" + " Like specifying filterspec '*:s'\n" + " -f <filename> Log to file. Default to stdout\n" + " -r [<kbytes>] Rotate log every kbytes. (16 if unspecified). Requires -f\n" + " -n <count> Sets max number of rotated logs to <count>, default 4\n" + " -v <format> Sets the log print format, where <format> is one of:\n\n" + " brief(by default) process tag thread raw time threadtime long\n\n" + " -c clear (flush) the entire log and exit, conflicts with '-g'\n" + " -d dump the log and then exit (don't block)\n" + " -t <count> print only the most recent <count> lines (implies -d)\n" + " -g get the size of the log's ring buffer and exit, conflicts with '-c'\n" + " -b <buffer> request alternate ring buffer\n" + " ('main' (default), 'radio', 'system')"); + + + fprintf(stderr,"\nfilterspecs are a series of \n" + " <tag>[:priority]\n\n" + "where <tag> is a log component tag (or * for all) and priority is:\n" + " V Verbose\n" + " D Debug\n" + " I Info\n" + " W Warn\n" + " E Error\n" + " F Fatal\n" + " S Silent (supress all output)\n" + "\n'*' means '*:D' and <tag> by itself means <tag>:V\n" + "If no filterspec is found, filter defaults to '*:I'\n\n"); +} + + +/* + * free one log_device_t and it doesn't take care of chain so it + * may break the chain list + */ +static void log_devices_free(struct log_device_t *dev) +{ + if (!dev) + return; + + if (dev->device) + free(dev->device); + + if (dev->queue) { + while (dev->queue->next) { + struct queued_entry_t *tmp = dev->queue->next; + dev->queue->next = tmp->next; + free(tmp); + } + free(dev->queue); + } + + free(dev); + dev = NULL; +} + + +/* + * free all the nodes after the "dev" and includes itself + */ +static void log_devices_chain_free(struct log_device_t *dev) +{ + if (!dev) + return; + + while (dev->next) { + struct log_device_t *tmp = dev->next; + dev->next = tmp->next; + log_devices_free(tmp); + } + + log_devices_free(dev); + dev = NULL; +} + + +/* + * create a new log_device_t instance but don't care about + * the device node accessable or not + */ +static struct log_device_t *log_devices_new(const char *path) +{ + struct log_device_t *new; + + if (!path || strlen(path) <= 0) + return NULL; + + new = malloc(sizeof(*new)); + if (!new) { + fprintf(stderr, "out of memory\n"); + return NULL; + } + + new->device = strdup(path); + new->fd = -1; + new->printed = false; + new->queue = NULL; + new->next = NULL; + + return new; +} + + +/* + * add a new device to the tail of chain + */ +static int log_devices_add_to_tail(struct log_device_t *devices, struct log_device_t *new) +{ + struct log_device_t *tail = devices; + + if (!devices || !new) + return -1; + + while (tail->next) + tail = tail->next; + + tail->next = new; + g_dev_count++; + + return 0; +} + +int main(int argc, char **argv) +{ + int err; + int has_set_log_format = 0; + int is_clear_log = 0; + int getLogSize = 0; + int mode = O_RDONLY; + int accessmode = R_OK; + int i; + struct log_device_t* devices = NULL; + struct log_device_t* dev; + + g_logformat = (log_format *)log_format_new(); + + if (argc == 2 && 0 == strcmp(argv[1], "--test")) { + logprint_run_tests(); + exit(0); + } + + if (argc == 2 && 0 == strcmp(argv[1], "--help")) { + show_help(argv[0]); + exit(0); + } + + for (;;) { + int ret; + + ret = getopt(argc, argv, "cdt:gsf:r:n:v:b:D"); + + if (ret < 0) { + break; + } + + switch(ret) { + case 's': + /* default to all silent */ + log_add_filter_rule(g_logformat, "*:s"); + break; + + case 'c': + is_clear_log = 1; + mode = O_WRONLY; + break; + + case 'd': + g_nonblock = true; + break; + + case 't': + g_nonblock = true; + g_tail_lines = atoi(optarg); + break; + + + case 'g': + getLogSize = 1; + break; + + case 'b': { + char *buf; + if (asprintf(&buf, LOG_FILE_DIR "%s", optarg) == -1) { + fprintf(stderr,"Can't malloc LOG_FILE_DIR\n"); + exit(-1); + } + + dev = log_devices_new(buf); + if (dev == NULL) { + fprintf(stderr,"Can't add log device: %s\n", buf); + exit(-1); + } + if (devices) { + if (log_devices_add_to_tail(devices, dev)) { + fprintf(stderr, "Open log device %s failed\n", buf); + exit(-1); + } + } else { + devices = dev; + g_dev_count = 1; + } + } + break; + + case 'f': + /* redirect output to a file */ + g_output_filename = optarg; + + break; + + case 'r': + if (!isdigit(optarg[0])) { + fprintf(stderr,"Invalid parameter to -r\n"); + show_help(argv[0]); + exit(-1); + } + g_log_rotate_size_kbytes = atoi(optarg); + break; + + case 'n': + if (!isdigit(optarg[0])) { + fprintf(stderr,"Invalid parameter to -r\n"); + show_help(argv[0]); + exit(-1); + } + + g_max_rotated_logs = atoi(optarg); + break; + + case 'v': + err = set_log_format (optarg); + if (err < 0) { + fprintf(stderr,"Invalid parameter to -v\n"); + show_help(argv[0]); + exit(-1); + } + + has_set_log_format = 1; + break; + + default: + fprintf(stderr,"Unrecognized Option\n"); + show_help(argv[0]); + exit(-1); + break; + } + } + + /* get log size conflicts with write mode */ + if (getLogSize && mode != O_RDONLY) { + show_help(argv[0]); + exit(-1); + } + + if (!devices) { + devices = log_devices_new("/dev/"LOGGER_LOG_MAIN); + if (devices == NULL) { + fprintf(stderr,"Can't add log device: %s\n", LOGGER_LOG_MAIN); + exit(-1); + } + g_dev_count = 1; + + if (mode == O_WRONLY) + accessmode = W_OK; + + /* only add this if it's available */ + if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) { + if (log_devices_add_to_tail(devices, log_devices_new("/dev/"LOGGER_LOG_SYSTEM))) { + fprintf(stderr,"Can't add log device: %s\n", LOGGER_LOG_SYSTEM); + exit(-1); + } + } + if (0 == access("/dev/"LOGGER_LOG_APPS, accessmode)) { + if (log_devices_add_to_tail(devices, log_devices_new("/dev/"LOGGER_LOG_APPS))) { + fprintf(stderr,"Can't add log device: %s\n", LOGGER_LOG_APPS); + exit(-1); + } + } + + } + + if (g_log_rotate_size_kbytes != 0 && g_output_filename == NULL) + { + fprintf(stderr,"-r requires -f as well\n"); + show_help(argv[0]); + exit(-1); + } + + setup_output(); + + + if (has_set_log_format == 0) { + err = set_log_format("brief"); + } + fprintf(stderr,"arc = %d, optind = %d ,Kb %d, rotate %d\n", argc, optind,g_log_rotate_size_kbytes,g_max_rotated_logs); + + if(argc == optind ) { + /* Add from environment variable + char *env_tags_orig = getenv("DLOG_TAGS");*/ + log_add_filter_string(g_logformat, "*:d"); + } else { + + for (i = optind ; i < argc ; i++) { + err = log_add_filter_string(g_logformat, argv[i]); + + if (err < 0) { + fprintf (stderr, "Invalid filter expression '%s'\n", argv[i]); + show_help(argv[0]); + exit(-1); + } + } + } + dev = devices; + while (dev) { + dev->fd = open(dev->device, mode); + if (dev->fd < 0) { + fprintf(stderr, "Unable to open log device '%s': %s\n", + dev->device, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (is_clear_log) { + int ret; + ret = clear_log(dev->fd); + if (ret) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + } + + if (getLogSize) { + int size, readable; + + size = get_log_size(dev->fd); + if (size < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + + readable = get_log_readable_size(dev->fd); + if (readable < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + + printf("%s: ring buffer is %dKb (%dKb consumed), " + "max entry is %db, max payload is %db\n", dev->device, + size / 1024, readable / 1024, + (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD); + } + + dev = dev->next; + } + + if (getLogSize) { + return 0; + } + + if (is_clear_log) { + return 0; + } + + read_log_lines(devices); + + log_devices_chain_free(devices); + + return 0; +} diff --git a/src/shared/logprint.c b/src/shared/logprint.c new file mode 100755 index 0000000..6ab4d72 --- /dev/null +++ b/src/shared/logprint.c @@ -0,0 +1,735 @@ +/* + * DLOG + * Copyright (c) 2005-2008, The Android Open Source Project + * Copyright (c) 2012-2013 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> + +#include <logprint.h> + + +typedef struct FilterInfo_t { + char *mTag; + log_priority mPri; + struct FilterInfo_t *p_next; +} FilterInfo; + +struct log_format_t { + log_priority global_pri; + FilterInfo *filters; + log_print_format format; +}; + +static FilterInfo * filterinfo_new(const char *tag, log_priority pri) +{ + FilterInfo *p_ret; + + if (!tag) + return NULL; + + p_ret = (FilterInfo *)calloc(1, sizeof(FilterInfo)); + if (!p_ret) + return NULL; + + p_ret->mTag = strdup(tag); + p_ret->mPri = pri; + + return p_ret; +} + +static void filterinfo_free(FilterInfo *p_info) +{ + if (p_info == NULL) { + return; + } + + free(p_info->mTag); + p_info->mTag = NULL; +} + +/* + * Note: also accepts 0-9 priorities + * returns DLOG_UNKNOWN if the character is unrecognized + */ +static log_priority filter_char_to_pri (char c) +{ + log_priority pri; + + c = tolower(c); + + if (c >= '0' && c <= '9') { + if (c >= ('0'+DLOG_SILENT)) { + pri = DLOG_VERBOSE; + } else { + pri = (log_priority)(c - '0'); + } + } else if (c == 'v') { + pri = DLOG_VERBOSE; + } else if (c == 'd') { + pri = DLOG_DEBUG; + } else if (c == 'i') { + pri = DLOG_INFO; + } else if (c == 'w') { + pri = DLOG_WARN; + } else if (c == 'e') { + pri = DLOG_ERROR; + } else if (c == 'f') { + pri = DLOG_FATAL; + } else if (c == 's') { + pri = DLOG_SILENT; + } else if (c == '*') { + pri = DLOG_DEFAULT; + } else { + pri = DLOG_UNKNOWN; + } + + return pri; +} + +static char filter_pri_to_char (log_priority pri) +{ + switch (pri) { + case DLOG_VERBOSE: + return 'V'; + case DLOG_DEBUG: + return 'D'; + case DLOG_INFO: + return 'I'; + case DLOG_WARN: + return 'W'; + case DLOG_ERROR: + return 'E'; + case DLOG_FATAL: + return 'F'; + case DLOG_SILENT: + return 'S'; + case DLOG_DEFAULT: + case DLOG_UNKNOWN: + default: + return '?'; + } +} + +static log_priority filter_pri_for_tag(log_format *p_format, const char *tag) +{ + FilterInfo *p_curFilter; + + for (p_curFilter = p_format->filters; p_curFilter != NULL; p_curFilter = p_curFilter->p_next ) + { + if (0 == strcmp(tag, p_curFilter->mTag)) + { + if (p_curFilter->mPri == DLOG_DEFAULT) { + return p_format->global_pri; + } else { + return p_curFilter->mPri; + } + } + } + return p_format->global_pri; +} + +/** for debugging */ +void dump_filters(log_format *p_format) +{ + FilterInfo *p_fi; + + for (p_fi = p_format->filters ; p_fi != NULL ; p_fi = p_fi->p_next) { + char cPri = filter_pri_to_char(p_fi->mPri); + if (p_fi->mPri == DLOG_DEFAULT) { + cPri = filter_pri_to_char(p_format->global_pri); + } + fprintf(stderr, "%s:%c\n", p_fi->mTag, cPri); + } + + fprintf(stderr, "*:%c\n", filter_pri_to_char(p_format->global_pri)); + +} + +/** + * returns 1 if this log line should be printed based on its priority + * and tag, and 0 if it should not + */ +int log_should_print_line (log_format *p_format, const char *tag, log_priority pri) +{ + return pri >= filter_pri_for_tag(p_format, tag); +} + +log_format *log_format_new() +{ + log_format *p_ret; + + p_ret = calloc(1, sizeof(log_format)); + + if (!p_ret) + return NULL; + p_ret->global_pri = DLOG_SILENT; + p_ret->format = FORMAT_BRIEF; + + return p_ret; +} + +void log_format_free(log_format *p_format) +{ + FilterInfo *p_info, *p_info_old; + + p_info = p_format->filters; + + while (p_info != NULL) { + p_info_old = p_info; + p_info = p_info->p_next; + filterinfo_free(p_info_old); + } + + free(p_format); +} + +void log_set_print_format(log_format *p_format,log_print_format format) +{ + p_format->format=format; +} + +/** + * Returns FORMAT_OFF on invalid string + */ +log_print_format log_format_from_string(const char * formatString) +{ + static log_print_format format; + + if (strcmp(formatString, "brief") == 0) + format = FORMAT_BRIEF; + else if (strcmp(formatString, "process") == 0) + format = FORMAT_PROCESS; + else if (strcmp(formatString, "tag") == 0) + format = FORMAT_TAG; + else if (strcmp(formatString, "thread") == 0) + format = FORMAT_THREAD; + else if (strcmp(formatString, "raw") == 0) + format = FORMAT_RAW; + else if (strcmp(formatString, "time") == 0) + format = FORMAT_TIME; + else if (strcmp(formatString, "threadtime") == 0) + format = FORMAT_THREADTIME; + else if (strcmp(formatString, "dump") == 0) + format = FORMAT_DUMP; + else if (strcmp(formatString, "long") == 0) + format = FORMAT_LONG; + else format = FORMAT_OFF; + + return format; +} + +/** + * filterExpression: a single filter expression + * eg "AT:d" + * + * returns 0 on success and -1 on invalid expression + * + * Assumes single threaded execution + */ +int log_add_filter_rule(log_format *p_format, + const char *filterExpression) +{ + size_t tagNameLength; + log_priority pri = DLOG_DEFAULT; + + tagNameLength = strcspn(filterExpression, ":"); + + if (!p_format) { + goto error; + } + + if (tagNameLength == 0) { + goto error; + } + + if(filterExpression[tagNameLength] == ':') { + pri = filter_char_to_pri(filterExpression[tagNameLength+1]); + + if (pri == DLOG_UNKNOWN) { + goto error; + } + } + + if(0 == strncmp("*", filterExpression, tagNameLength)) { + /* This filter expression refers to the global filter + * The default level for this is DEBUG if the priority + * is unspecified + */ + if (pri == DLOG_DEFAULT) { + pri = DLOG_DEBUG; + } + + p_format->global_pri = pri; + } else { + /* for filter expressions that don't refer to the global + * filter, the default is verbose if the priority is unspecified + */ + if (pri == DLOG_DEFAULT) { + pri = DLOG_VERBOSE; + } + + char *tagName; + tagName = strndup(filterExpression, tagNameLength); + + FilterInfo *p_fi = filterinfo_new(tagName, pri); + free(tagName); + + if (!p_fi) + goto error; + p_fi->p_next = p_format->filters; + p_format->filters = p_fi; + } + + return 0; +error: + return -1; +} + +/** + * filterString: a comma/whitespace-separated set of filter expressions + * + * eg "AT:d *:i" + * + * returns 0 on success and -1 on invalid expression + * + * Assumes single threaded execution + * + */ +int log_add_filter_string(log_format *p_format, + const char *filterString) +{ + char *filterStringCopy = strdup (filterString); + char *p_cur = filterStringCopy; + char *p_ret; + int err; + + while (NULL != (p_ret = strsep(&p_cur, " \t,"))) { + /* ignore whitespace-only entries */ + if(p_ret[0] != '\0') { + err = log_add_filter_rule(p_format, p_ret); + + if (err < 0) { + goto error; + } + } + } + + free (filterStringCopy); + return 0; +error: + free (filterStringCopy); + return -1; +} + +static inline char * strip_end(char *str) +{ + char *end = str + strlen(str) - 1; + + while (end >= str && isspace(*end)) + *end-- = '\0'; + return str; +} + +/** + * Splits a wire-format buffer into an LogEntry + * entry allocated by caller. Pointers will point directly into buf + * + * Returns 0 on success and -1 on invalid wire format (entry will be + * in unspecified state) + */ +int log_process_log_buffer(struct logger_entry *buf, log_entry *entry) +{ + int i, start = -1, end = -1; + + if (buf->len < 3) { + fprintf(stderr, "Entry too small\n"); + return -1; + } + + entry->tv_sec = buf->sec; + entry->tv_nsec = buf->nsec; + entry->pid = buf->pid; + entry->tid = buf->tid; + + entry->priority = buf->msg[0]; + if (entry->priority < 0 || entry->priority > DLOG_SILENT) { + fprintf(stderr, "Wrong priority message\n"); + return -1; + } + + entry->tag = buf->msg + 1; + if (!strlen(entry->tag)) { + fprintf(stderr, "No tag message\n"); + return -1; + } + + for (i = 0; i < buf->len; i++) { + if (buf->msg[i] == '\0') { + if (start == -1) { + start = i + 1; + } else { + end = i; + break; + } + } + } + if (start == -1) { + fprintf(stderr, "Malformed log message\n"); + return -1; + } + if (end == -1) { + end = buf->len - 1; + buf->msg[end] = '\0'; + } + + entry->message = buf->msg + start; + entry->messageLen = end - start; + + return 0; +} + +/** + * Formats a log message into a buffer + * + * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer + * If return value != defaultBuffer, caller must call free() + * Returns NULL on malloc error + */ +char *log_format_log_line ( + log_format *p_format, + char *defaultBuffer, + size_t defaultBufferSize, + const log_entry *entry, + size_t *p_outLength) +{ +#if defined(HAVE_LOCALTIME_R) + struct tm tmBuf; +#endif + struct tm* ptm; + char timeBuf[32], tzBuf[16]; + char prefixBuf[128], suffixBuf[128]; + char priChar; + int prefixSuffixIsHeaderFooter = 0; + char * ret = NULL; + + priChar = filter_pri_to_char(entry->priority); + + /* + * Get the current date/time in pretty form + * + * It's often useful when examining a log with "less" to jump to + * a specific point in the file by searching for the date/time stamp. + * For this reason it's very annoying to have regexp meta characters + * in the time stamp. Don't use forward slashes, parenthesis, + * brackets, asterisks, or other special chars here. + */ +#if defined(HAVE_LOCALTIME_R) + ptm = localtime_r(&(entry->tv_sec), &tmBuf); +#else + ptm = localtime(&(entry->tv_sec)); +#endif + if (!ptm) + return ret; + + strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); + strftime(tzBuf, sizeof(tzBuf), "%z", ptm); + + /* + * Construct a buffer containing the log header and log message. + */ + size_t prefixLen, suffixLen; + + switch (p_format->format) { + case FORMAT_TAG: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c/%-8s: ", priChar, entry->tag); + strcpy(suffixBuf, "\n"); suffixLen = 1; + break; + case FORMAT_PROCESS: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c(%5d) ", priChar, (int)entry->pid); + suffixLen = snprintf(suffixBuf, sizeof(suffixBuf), + " (%s)\n", entry->tag); + break; + case FORMAT_THREAD: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c(%5d:%5d) ", priChar, (int)entry->pid, (int)entry->tid); + strcpy(suffixBuf, "\n"); + suffixLen = 1; + break; + case FORMAT_RAW: + prefixBuf[0] = 0; + prefixLen = 0; + strcpy(suffixBuf, "\n"); + suffixLen = 1; + break; + case FORMAT_TIME: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%s.%03ld%s %c/%-8s(%5d): ", timeBuf, entry->tv_nsec / 1000000, + tzBuf, priChar, entry->tag, (int)entry->pid); + strcpy(suffixBuf, "\n"); + suffixLen = 1; + break; + case FORMAT_THREADTIME: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%s.%03ld%s %5d %5d %c %-8s: ", timeBuf, entry->tv_nsec / 1000000, + tzBuf, (int)entry->pid, (int)entry->tid, priChar, entry->tag); + strcpy(suffixBuf, "\n"); + suffixLen = 1; + break; + case FORMAT_DUMP: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%s.%03ld%s %5d %5d %c %-8s: ", timeBuf, + entry->tv_nsec / 1000000, tzBuf, (int)entry->pid, + (int)entry->tid, priChar, entry->tag); + strcpy(suffixBuf, "\n"); + suffixLen = 1; + break; + case FORMAT_LONG: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "[ %s.%03ld %5d:%5d %c/%-8s ]\n", + timeBuf, entry->tv_nsec / 1000000, (int)entry->pid, + (int)entry->tid, priChar, entry->tag); + strcpy(suffixBuf, "\n\n"); + suffixLen = 2; + prefixSuffixIsHeaderFooter = 1; + break; + case FORMAT_BRIEF: + default: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c/%-8s(%5d): ", priChar, entry->tag, (int)entry->pid); + strcpy(suffixBuf, "\n"); + suffixLen = 1; + break; + } + /* snprintf has a weird return value. It returns what would have been + * written given a large enough buffer. In the case that the prefix is + * longer then our buffer(128), it messes up the calculations below + * possibly causing heap corruption. To avoid this we double check and + * set the length at the maximum (size minus null byte) + */ + if(prefixLen >= sizeof(prefixBuf)) + prefixLen = sizeof(prefixBuf) - 1; + if(suffixLen >= sizeof(suffixBuf)) + suffixLen = sizeof(suffixBuf) - 1; + + /* the following code is tragically unreadable */ + + size_t numLines; + char *p; + size_t bufferSize; + const char *pm; + + if (prefixSuffixIsHeaderFooter) { + /* we're just wrapping message with a header/footer */ + numLines = 1; + } else { + pm = entry->message; + numLines = 0; + + /* The line-end finding here must match the line-end finding + * in for ( ... numLines...) loop below + */ + while (pm < (entry->message + entry->messageLen)) { + if (*pm++ == '\n') numLines++; + } + /* plus one line for anything not newline-terminated at the end */ + if (pm > entry->message && *(pm-1) != '\n') numLines++; + } + + /* this is an upper bound--newlines in message may be counted + * extraneously + */ + bufferSize = (numLines * (prefixLen + suffixLen)) + entry->messageLen + 1; + + if (defaultBufferSize >= bufferSize) { + ret = defaultBuffer; + } else { + ret = (char *)malloc(bufferSize); + + if (ret == NULL) { + return ret; + } + } + + ret[0] = '\0'; /* to start strcat off */ + + p = ret; + pm = entry->message; + + if (prefixSuffixIsHeaderFooter) { + strcat(p, prefixBuf); + p += prefixLen; + strncat(p, entry->message, entry->messageLen); + p += entry->messageLen; + strcat(p, suffixBuf); + p += suffixLen; + } else { + while (pm < (entry->message + entry->messageLen)) { + const char *lineStart; + size_t lineLen; + + lineStart = pm; + + /* Find the next end-of-line in message */ + while (pm < (entry->message + entry->messageLen) + && *pm != '\n') pm++; + lineLen = pm - lineStart; + + strcat(p, prefixBuf); + p += prefixLen; + strncat(p, lineStart, lineLen); + p += lineLen; + strcat(p, suffixBuf); + p += suffixLen; + + if (*pm == '\n') + pm++; + } + } + + if (p_outLength != NULL) { + *p_outLength = p - ret; + } + + return ret; +} + +/** + * Either print or do not print log line, based on filter + * + * Returns count bytes written + */ + +int log_print_log_line( + log_format *p_format, + int fd, + const log_entry *entry) +{ + int ret; + char defaultBuffer[512]; + char *outBuffer = NULL; + size_t totalLen; + + outBuffer = log_format_log_line(p_format, + defaultBuffer, sizeof(defaultBuffer), entry, &totalLen); + + if (!outBuffer) + return -1; + + do { + ret = write(fd, outBuffer, totalLen); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno); + ret = 0; + goto done; + } + + if (((size_t)ret) < totalLen) { + fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", ret, + (int)totalLen); + goto done; + } + +done: + if (outBuffer != defaultBuffer) { + free(outBuffer); + } + + return ret; +} + + + +void logprint_run_tests() +{ + int err; + const char *tag; + log_format *p_format; + + p_format = log_format_new(); + if (!p_format) { + fprintf(stderr, "create log_foramt failed\n"); + return; + } + + fprintf(stderr, "running tests\n"); + + tag = "random"; + + log_add_filter_rule(p_format,"*:i"); + + assert (DLOG_INFO == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0); + log_add_filter_rule(p_format, "*"); + assert (DLOG_DEBUG == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0); + log_add_filter_rule(p_format, "*:v"); + assert (DLOG_VERBOSE == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0); + log_add_filter_rule(p_format, "*:i"); + assert (DLOG_INFO == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0); + + log_add_filter_rule(p_format, "random"); + assert (DLOG_VERBOSE == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0); + log_add_filter_rule(p_format, "random:v"); + assert (DLOG_VERBOSE == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0); + log_add_filter_rule(p_format, "random:d"); + assert (DLOG_DEBUG == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) > 0); + log_add_filter_rule(p_format, "random:w"); + assert (DLOG_WARN == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0); + + log_add_filter_rule(p_format, "crap:*"); + assert (DLOG_VERBOSE== filter_pri_for_tag(p_format, "crap")); + assert(log_should_print_line(p_format, "crap", DLOG_VERBOSE) > 0); + + /* invalid expression */ + err = log_add_filter_rule(p_format, "random:z"); + assert (err < 0); + assert (DLOG_WARN == filter_pri_for_tag(p_format, "random")); + assert(log_should_print_line(p_format, tag, DLOG_DEBUG) == 0); + + /* Issue #550946 */ + err = log_add_filter_string(p_format, " "); + assert(err == 0); + assert(DLOG_WARN == filter_pri_for_tag(p_format, "random")); + + /* note trailing space */ + err = log_add_filter_string(p_format, "*:s random:d "); + assert(err == 0); + assert(DLOG_DEBUG == filter_pri_for_tag(p_format, "random")); + + err = log_add_filter_string(p_format, "*:s random:z"); + assert(err < 0); + + log_format_free(p_format); + + fprintf(stderr, "tests complete\n"); +} + |