summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/libdlog/log.c305
-rw-r--r--src/libdlog/logconfig.c174
-rw-r--r--src/libdlog/loglimiter.c419
-rw-r--r--src/logger/logger.c1040
-rwxr-xr-xsrc/logutil/logutil.c777
-rwxr-xr-xsrc/shared/logprint.c735
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");
+}
+